Defining Symfony 2 controllers in two ways

If you start with Symfony 2 using the quick tour, you might assume there is only one way of creating a controller: by extending Symfony\Bundle\FrameworkBundle\Controller\Controller. If you follow the more in depth book you might notice a tip to a second way of creating a controller: as a service. Lately I’ve been exclusively creating my controllers in this way, and I’m starting to like it.

The big difference is the way you access services. When you extend the Symfony Controller class, you use the “get” method.

class MyController extends Symfony\Bundle\FrameworkBundle\Controller\Controller
{
    public function testAction()
    {
        $this->get('my_service')->func();
    }
}

When you compare it to a controller as a service style.

class MyController
{
    private $my_service;

    public function __construct(MyServiceInterface $my_service)
    {
        $this->my_service = $my_service;
    }

    public function testAction()
    {
        $this->my_service->func();
    }
}

The first style has some advantages: it’s shorter and it gets you working faster. Easy access to any service you might create. It’s understandable that the initial nudge of the documentation goes this way, since a great way of motivating programmers to continue using a library is working code, fast.

The encouraged way has some disadvantages as well, though. I found a nice post by Jimmy Bogard that explains my gut feeling really well.

Just like any framework, littering your code with references and usages can make it nearly impossible to change to a different container.

It is really hard to write a unit-test in the first case. You can not “click through” to where the code behind this service is located. You don’t get auto completion on the functions you call, so it’s hard to find out which other methods you could use. It’s even hard to find out which other parameters you could pass! You’re locking your code into Symfony. It’s easy for your controller to grow too bit and secretly require more services then one class should ever use.

Also, what if you want to find out where a class is used? In the first case you have to do a full text search in all source code on a service name and hope that no-one used string concatenation. In the second case you could ask the service container for all services that reference it.

Taking all this into account, I was really certain of my preference when starting to write this blog post. Defining Symfony 2 controllers should be done as a service. While doing more research, I realised there are two questions that should be answered first: is a controller a service? And should you unit-test your controllers?

Is a controller a service?

The way to get your arguments injected in your constructor is by defining your controller as a service. So lets try to answer the question: is a controller a service*? Lets first take the definition of symfony.com.

Put simply, a Service is any PHP object that performs some sort of “global” task.

And the definition Martin Fowler gave.

I use component to mean a glob of software that’s intended to be used, without change, by an application that is out of the control of the writers of the component.

It’s very hard to consider any controller a service according to either of these definitions. If you have a small component, say a menu bar, then it will be used rather often, and it would fit this definition. If you create an open source bundle, it is clearly meant to be used globally. To be used without change by some application that you do not control.

But no your average controller is, strictly seen, not a service.

Why even unit-test your controllers?

Whether a controller should be unit-testable is a bit of an ongoing debate. One the one hand you might argue, like Fabien, that a controller should be a thin connecting layer between the model and the view. It should not be unit-tested. If you test it, it should be a functional test.

Maybe this is similar to the mantra “If you feel that you need to comment your code, make it more readable”. If you feel that you need to unit-test your controller, clean your controller of all this complicated logic, move it somewhere else.

Defining Symfony 2 controllers: Which way to use?

Read, and make your own informed decision. Or read this interesting post by Lukas Kahwe Smith. Personally I still have a strong preference to define the controllers as a service. Not having an overview of my dependencies, and not being able to click through to the actual source code (or to an interface) is really annoying. It is, in both ways, perfectly possible to keep your controllers small.

So I will define my controllers as a service. The next step is of course to define our commands as a service as well. Can’t wait for 2.4.

* The thing Symfony 2 calls a service is called a component according to Inversion of control (IoC). IoC defines a service as a component that is externally accessible, just to make our wording easier here.

5 thoughts on “Defining Symfony 2 controllers in two ways

  1. Hello! I just wanted to share my experience with controllers as a service.

    First of all, the main disadvantage I’ve encountered is the high number of dependencies which can be considered normal for a controller, namely: `templating`, `translator`, `routing`, `session`, `form.factory` and `doctrine`.
    If you need to add a service specific to your project, you can end up with more than 7 services for your controller (which can mean 53 lines at the beginning of your class, before the actual actions).

    One of the reason I used controller as a service was to mock the templating service, in order to check the parameters passed to the view (I don’t like to crawl an HTML page: its structure changes so often it broke my tests continuously).
    However this can also be done with a classic controller, and checking the parameters isn’t that important.

  2. Excellent post! For the sake of completeness, both patterns have a name: your first example uses Service Locator, the second is Dependency Injection. Service Locator is widely regarded as an anti-pattern nowadays, for many of the reasons you mention: harder to test, strong coupling between your code and your framework, harder to refactor etc. In fact, I feel Symfony should not only discourage the use of container injection, but make it impossible (or at least very hard) to do in a future v3.

    As for whether controllers are services: A controller takes a request, performs an action on the model, and returns a response. That’s a global task. The fact that this task is called by the framework, instead of your own code, is irrelevant. Pretty much everything is a service, except for objects that represent concepts, like entities and value objects. It is imho better to avoid the word ‘service’ in your code, as it is in fact pretty meaningless.

    1. Whoops, that should have been IoC indeed.

      The bad thing about property injection is that type safety is thrown out of the window, i.e. any auto completion you get is by convention, rather then by enforcement.

  3. I was struggling with the API/autocomplete issue as well, but I found that when I switched to PHPStorm and the Symfony2 plugin for it, autocomplete was available. It is smart enough to introspect your service definitions..
    So that made me wonder whether this comes down to clean coding practices, or conveniences tied to our coding tools.. Good question.

Leave a Reply

Your email address will not be published. Required fields are marked *