Skip to content

Commit 0c0bf3d

Browse files
committed
[FrameworkBundle] Document how to decouple controllers from Symfony
1 parent df64f1d commit 0c0bf3d

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

controller.rst

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
952952
must 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\\Bundle\\FrameworkBundle\\Controller\\ControllerHelper`,
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+
9541027
Final Thoughts
9551028
--------------
9561029

0 commit comments

Comments
 (0)