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:
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:
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.