@@ -951,6 +951,79 @@ This way, browsers can start downloading the assets immediately; like the
951951``sendEarlyHints() `` method also returns the ``Response `` object, which you
952952must use to create the full response sent from the controller action.
953953
954+ Decoupling Controllers from Symfony
955+ -----------------------------------
956+
957+ Extending the:ref: `AbstractController base class <the-base-controller-class-services >`
958+ simplifies controller development and is **recommended for most applications **.
959+ However, some advanced users prefer to fully decouple your controllers from Symfony
960+ (for example, to improve testability or to follow a more framework-agnostic design)
961+ Symfony provides tools to help you do that.
962+
963+ To decouple controllers, Symfony exposes all the helpers from ``AbstractController ``
964+ through another class called:class: `Symfony\B undle\F rameworkBundle\C ontroller\C ontrollerHelper `,
965+ where each helper is available as a public method::
966+
967+ use Symfony\Bundle\FrameworkBundle\Controller\ControllerHelper;
968+ use Symfony\Component\DependencyInjection\Attribute\AutowireMethodOf;
969+ use Symfony\Component\HttpFoundation\Response;
970+
971+ class MyController
972+ {
973+ public function __construct(
974+ #[AutowireMethodOf(ControllerHelper::class)]
975+ private \Closure $render,
976+ #[AutowireMethodOf(ControllerHelper::class)]
977+ private \Closure $redirectToRoute,
978+ ) {
979+ }
980+
981+ public function showProduct(int $id): Response
982+ {
983+ if (!$id) {
984+ return ($this->redirectToRoute)('product_list');
985+ }
986+
987+ return ($this->render)('product/show.html.twig', ['product_id' => $id]);
988+ }
989+ }
990+
991+ You can inject the entire ``ControllerHelper `` class if you prefer, but using the
992+ :ref: `AutowireMethodOf <autowiring_closures >` attribute as in the previous example,
993+ lets you inject only the exact helpers you need, making your code more efficient.
994+
995+ Since ``#[AutowireMethodOf] `` also works with interfaces, you can define interfaces
996+ for these helper methods::
997+
998+ interface RenderInterface
999+ {
1000+ // this is the signature of the render() helper
1001+ public function __invoke(string $view, array $parameters = [], ?Response $response = null): Response;
1002+ }
1003+
1004+ Then, update your controller to use the interface instead of a closure::
1005+
1006+ use Symfony\Bundle\FrameworkBundle\Controller\ControllerHelper;
1007+ use Symfony\Component\DependencyInjection\Attribute\AutowireMethodOf;
1008+
1009+ class MyController
1010+ {
1011+ public function __construct(
1012+ #[AutowireMethodOf(ControllerHelper::class)]
1013+ private RenderInterface $render,
1014+ ) {
1015+ }
1016+
1017+ // ...
1018+ }
1019+
1020+ Using interfaces like in the previous example provides full static analysis and
1021+ autocompletion benefits with no extra boilerplate code.
1022+
1023+ ..versionadded ::7.4
1024+
1025+ The ``ControllerHelper `` class was introduced in Symfony 7.4.
1026+
9541027Final Thoughts
9551028--------------
9561029