Autowired controllers as services for lazy people

This post is deprecated since May 2017
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.

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.

Disclaimer: Why even use controllers as services?

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.

Autowire service? Easy!

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

Autowire controller? Hell!

Managing dependencies for controllers in same way is complicated. To apply the same effect, you have to make following 3 steps:

  1. 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
  2. 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.

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

Did you make it? Here comes much deeper hell

Even if you do manage to finish these steps, these issues will appear:

Is there some way back from hell to heaven?

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

Autowiring in controllers

So I made Symplify\ControllerAutowire bundle, that solves all problems that are mentioned above by following steps:

Let's try it together.

1. Install package

composer require symplify/controller-autowire

2. Register bundle

// app/AppKernel.php
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = [
            new Symplify\ControllerAutowire\SymplifyControllerAutowireBundle(),
            // ...
        ];
    }
}

3. Add some dependency for your controller via constructor

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

Best practise solution - proven in many PHP projects

Check them out:


Found a typo? Fix it to join team of 64 people that improve content here

❤️️ Do you like what I write about? Or do you hate it but enjoy discussion? 😠
You can support my writing by throwing a couple bucks at my Patreon