Nicolas Grekas
Contributed by Nicolas Grekas in #60857

Controllers in Symfony applications often extend the AbstractController base class to use convenient shortcuts like render(), redirectToRoute(), addFlash(), and more. Many projects do this because the base controller keeps things simple and productive. But if you prefer to write framework-agnostic controllers, Symfony 7.4 introduces a new feature that makes this easier than ever.

Introducing ControllerHelper

Symfony 7.4 introduces a new class called ControllerHelper. It exposes all the helper methods from AbstractController as standalone public methods. This allows you to use those helpers without extending the base controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use Symfony\Bundle\FrameworkBundle\Controller\ControllerHelper;
use Symfony\Component\DependencyInjection\Attribute\AutowireMethodOf;
use Symfony\Component\HttpFoundation\Response;

class MyController
{
    public function __construct(
        #[AutowireMethodOf(ControllerHelper::class)]
        private \Closure $render,
        #[AutowireMethodOf(ControllerHelper::class)]
        private \Closure $redirectToRoute,
    ) {
    }

    public function show(int $id): Response
    {
        if (!$id) {
            return ($this->redirectToRoute)('product_list');
        }

        return ($this->render)('product/show.html.twig', ['product_id' => $id]);
    }
}

Instead of inheriting from AbstractController, this example uses the AutowireMethodOf attribute to inject only the specific helpers needed to render templates and redirect responses. This gives you precise control, better testability, and smaller dependency graphs.

Working with Interfaces

Closures are great, but Symfony 7.4 goes even further. You can use interfaces to describe helper method signatures and inject them directly. This gives you static analysis and autocompletion in your IDE, without adding any boilerplate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface RenderInterface
{
    // this must match the signature of the render() helper
    public function __invoke(string $view, array $parameters = [], ?Response $response = null): Response;
}

class MyController
{
    public function __construct(
        #[AutowireMethodOf(ControllerHelper::class)]
        private RenderInterface $render,
    ) {
    }

    // ...
}

Most applications should continue using AbstractController as usual. It's simple, expressive, and requires no extra setup. The new ControllerHelper is meant for advanced software design that favor decoupled code. Note that while this post highlights ControllerHelper, the same strategy is applicable e.g. to Doctrine repositories: instead of injecting the full repository service, you can inject single query functions with the same #[AutowireMethodOf] attribute.

Published in #Living on the edge