Since Symfony 3.3 you can use PSR4-based service discovery and registration. It does pretty much the same thing - registers autowired controllers (and more) - and it has native support in Symfony.
I recommend using it instead!
With new autowiring feature in Symfony 2.8+, it is now easier to manage dependencies for services. But what about for controllers? Unfortunately, there are 3 annoying steps you have to do. Today I will show you, how to reduce them to 0.
The goal of this article is not to discuss pro and cons of "controller as service" (further CAS) approach. If you haven't decided yet to use CAS, I recommend checking these articles:
But now, back to the topic.
With autowire feature, managing dependencies for services is now as simple as:
services:
post.publisher:
class: PostPublisher
autowire: true # all you got to do is add this line
Managing dependencies for controllers in same way is complicated. To apply the same effect, you have to make following 3 steps:
Register controller manually as service to the config
# app/config/services.yml
services:
post_controller: # you have to use this name everywhere, so pick it wisely
class: PostController
autowire: true
Add @Route
annotation with service name
// src/AppBundle/Controller/PostController.php
namespace AppBundle\Controller\PostController;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* @Route(service="post_controller") # watch for typo here!
*/
class PostController
{
public function listAction()
{
}
}
or route using service name:
# app/config/routing.yml
post_list:
path: /post-list
defaults:
_controller: post_controller:listAction
# and not bundle like approach
# _controller: AppBundle:Post:list
This difference is so difficult to spot, that it created question on StackOverflow.
Finally, you have to use service name and single colon for referring:
// any controller
$this->forward('post_controller:listAction'));
There is nice answer on StackOverflow explaining with more details.
This process is exhausting already and difficult to remember.
Even if you do manage to finish these steps, these issues will appear:
drawback of FrameworkBundle, when it tries to autowire controller
it's complicated to apply constructor dependency injection for extended 3rd party controllers (Sonata, FOS...), due to missing step 2 and 3 (that were mentioned above) and the "bundle naming" inside the bundle's code
Author of autowiring feature and Symfony core contributor Kévin Dunglas sees similar problem and proposes solution with ADR pattern. I think it's the right direction, but it bends controllers too much.
But my goal is to keep controllers the same way they are now, and just add support for...
So I made Symplify\ControllerAutowire bundle, that solves all problems that are mentioned above by following steps:
/src
directoryLet's try it together.
composer require symplify/controller-autowire
// app/AppKernel.php
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = [
new Symplify\ControllerAutowire\SymplifyControllerAutowireBundle(),
// ...
];
}
}
// src/AppBundle/Controller/DefaultController.php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;
public function __construct(EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
/**
* @Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
$this->eventDispatcher->dispatch('someEvent');
return $this->render('default/index.html.twig', [
// ...
]);
}
}
And that's it!
For further use, just check Readme for Symplify/ControllerAutowire.
Check them out: