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