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.