21 February 2013

Controllers as Services in Symfony2

Controller as a service is continually touted as the best practice.

But why would I wrap a controller into a service? That's good question.

Well, the point of doing this in general is not having to inject the DI container into your controllers (instead, you inject the dependencies into the controller directly). Thus the controller no longer depends on the container.
  1. Good: You get added flexibility. You no longer hard-code which services to use into your controller. You can specify that in the DI configuration instead. Example: You inject a UserProvider into the UserController for /profile, but inject a FacebookUserProvider into the same UserController for /profile/facebook.
  2. Bad: You must manually configure your dependencies in the DIC config. You need to manually assign the injected dependencies. Optional dependencies are no longer lazy-loaded.
This is basically just a proof of concept to show how it could be done.
  
namespace Acme\DemoBundle\Controller;

class FooController
{
    public function __construct($container, ...)
    {
        $this->container = $container;
        // ... deal with any more arguments etc here
    }

    public function foo($params)
    {
        // ...
        return $x;
    }

    protected function get($service)
    {
        return $this->container->get($service);
    }
}
    
 

Ditto for the bar() and something() methods, each in their own controller. Then, add them to your application as a service. Eg in your config.yml (other methods available):
  
services:
    my.foo.service:
        class: Acme\FooBundle\Controller\FooController
        arguments:
            container: "@service_container"
    
 

See the Symfony docs for more details about how you can construct this entry, including injecting any dependencies such as an entity manager or similar. Now, you can get an instance of this from the container:
   
public function yourMainAction()
{
    // ...

    $newData = $this->get("my.foo.service")->fooAction($someData);

    // ...

    return new Response(json_encode($newData), ...);
}
    
 

Likewise again for BarController and SomethingController. The advantage now is that this service can be made available at any point in your application (whether via the container as above, or as an injected service), across bundles, without needing to instantiate the class manually yourself and provide any dependencies.

4 comments:

  1. hmm maybe it is not so clear your explanation please edit it a bit more for clarity, what are the points you are making

    ReplyDelete
  2. Thanks for the write-up. I just wanted to point out that the official documentation covers this in the "How to define Controllers as Services" article:

    http://symfony.com/doc/master/cookbook/controller/service.html

    ReplyDelete
  3. I love SF2 and generally would consider myself a big fan, but the SF practice of adding the service container as a dependency goes again the whole idea of DIC. Essentially turning the service container into a global repository... that's not ideal. Configuring controllers as services is the correct first step away from that practice, but the next step toward good practice is to wire your dependencies up to your controller in the server container itself. So instead of ...

    protected function get($service)
    {
    return $this->container->get($service);
    }

    Good practice would be to have public getters and setters and wire up your dependencies in the service definition of you controller.
    public fuction setMyDependency($myService)...
    public function getMyDependency()...

    Every other service is built this way except for controllers.

    ReplyDelete