Skip to content

Commit f23ef04

Browse files
committed
Merge branch '7.4' into 8.0
* 7.4: [FrameworkBundle] Document how to decouple controllers from Symfony
2 parents 24c0820 + c805014 commit f23ef04

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
@@ -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
936936
must 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+
9381011
Final Thoughts
9391012
--------------
9401013

0 commit comments

Comments
 (0)