This is a big religious topic in the Symfony2 community, and if you scan
the comments in the links above, you’ll see why. In fact, it’s not something
I usually talk about: it can be a hornet’s nest :). So here we go!

In a moment, we’re going to walk through
an example and compare the approaches. But first, I’ll say that I don’t
register my controllers as services, and the reasons behind this are simple:

1) Registering a controller as a service is more work. That’s not the worst
things ever, but since it takes longer, the rewards need to outweigh this.

2) All of your logic should be pushed out into your service layer anyways.
This is the age-old skinny controllers best-practice.

3) And now that your controllers are skinny, there’s no need to unit test
them. Instead unit test the services being used by your controllers.

4) Services used by your controller are loaded lazily. This is not the
case if you’ve registered your controllers as a service and inject only what
you need. But in theory, as long as you keep your controllers focused,
then what you’re injecting will need to be used for any action anyways.

With that viewpoint, the slight increase in setup time probably doesn’t make
registering your controllers as services worth it. And when we’re teaching
beginners, it would be yet another concept to need to know early-on.

But as you dive in deeper, the topic gets more complex and the advantages
more fascinating, especially for seasoned developers that can register a
service very quickly.

// src/KnpU/QADayBundle/Controller/ContainerController.phpnamespaceKnpU\QADayBundle\Controller;useSymfony\Component\HttpFoundation\Response;useSymfony\Component\HttpFoundation\Request;useSymfony\Component\HttpFoundation\RedirectResponse;useSymfony\Component\DependencyInjection\ContainerInterface;useSymfony\Component\DependencyInjection\ContainerAwareInterface;classContainerControllerimplementsContainerAwareInterface{private$container;publicfunctionsetContainer(ContainerInterface$container=null){$this->container=$container;}publicfunctionindexAction(Request$request){if($request->isMethod('POST')){// .. do some things$url=$this->container->get('router')->generate('homepage');returnnewRedirectResponse($url);}return$this->container->get('templating')->renderResponse('QADayBundle::controllerTest.html.twig',array('type'=>'Injecting the container!'));}}

In your Symfony2 projects, you’re probably used to inheriting
Symfony2’s base Controller class. This gives you shortcut methods and
makes sure that Symfony’s container is set on a container property. To
see what’s really happening, I’ve chosen not to extend this class. Instead,
by implementing ContainerAwareInterface, we can still make sure that
Symfony calls setContainer and passes it to us. After that, we grab services
directly from the container and use them. This is all exactly what happens
behind-the-scenes in your controllers when you extend Symfony’s base Controller
class.

Notice the _controller key looks different. We haven’t yet, but in a
moment we’ll create a new service called qa_day.controller.service. Notice
that we do include the Action suffix with the method name: when you
refer to a controller as a service, none of the normal conventions are assumed
(i.e. index => indexAction).

Next, the actual controller class:

namespaceKnpU\QADayBundle\Controller;useSymfony\Component\HttpFoundation\Response;useSymfony\Component\HttpFoundation\Request;useSymfony\Component\HttpFoundation\RedirectResponse;useSymfony\Bundle\FrameworkBundle\Templating\EngineInterface;useSymfony\Component\Routing\Generator\UrlGeneratorInterface;classServiceController{private$templating;private$router;publicfunction__construct(EngineInterface$templating,UrlGeneratorInterface$router){$this->templating=$templating;$this->router=$router;}publicfunctionindexAction(Request$request){if($request->isMethod('POST')){// .. do some things$url=$this->router->generate('homepage');returnnewRedirectResponse($url);}return$this->templating->renderResponse('QADayBundle::controllerTest.html.twig',array('type'=>'Container as a service!'));}}

The class is perfectly straightforward: we need the templating and router
services, so we inject them. For extra-credit, I’ve type-hinted the interface
for each of these. Now, instead of referencing the router through the
container, we can just reference it directly. You can’t see it here,
but my IDE is also giving me auto-completion on the templating and router
objects - that’s one major advantage.

Tip

Knowing which interface to use for a specific service is not always easy.
For example, how did I know to use EngineInterface for the templating
service? If you’re not sure what to use, just look for the service in
container:debug and use the actual class name - not interface - that
is used for the service. To see if there’s an interface, open that class
up and check for it. This isn’t a science, but it’s a good path to learn
more about the interfaces that are actually behind things.

Finally, we have to do the extra step: defining the controller as a service:

This is a totally normal and underwhelming service, but it completes the
equation. The qa_day.controller.service:indexAction value used for the _controller
key of our route tells Symfony to grab this service and then execute indexAction.

Since we’ve already talked about why you might not register a controller
as as service, let’s explore the advantages of using services. Many of these
are summarized from Lukas’ blog and comments:

1) Since you’re not injecting the whole container, this is an opportunity
to document what your controller does and doesn’t do. When the controller
is a service, it’s obvious at a glance that it generates URLs and renders
templates. We also know that it doesn’t talk to the database, send emails,
or do anything else.

To make this even cooler, Lukas points out that if you use the JMSDebugginBundle,
then you can use the profiler tool to get a clear vision of what parts of
your code - including dependencies - make use of a particular service [screenshot].
That’s quite powerful.

2) Injecting specific services gives you auto-completion and clarity on
exactly what types of objects you have. When you reference the services
through the container, you don’t really know what type of object you’ll
get out. I commonly work around this by creating a private getter function
which tells my IDE what to expect:

Still, if we gain some time by not registering our controller as a service,
it’s fair to say that we lose some time doing things like this. It’s also
technically possible that someone in our code changes the router to return
something that does not implement UrlGeneratorInterface. In the service
controller, PHP would throw a very clear error if this ever happened.
In the container controller, the error would be less clear.

3) How much should your controller do? When you inject the entire container,
you could potentially have controllers that control many pages that do many
different things. As Kris points out, this is much harder if your controller
is a service, since eventually you’ll be injecting 100 different dependencies.
This is a natural way to make sure controllers stay focused.

Since not taking a side is lame, I’ll pick my winner. But the true answer
is that the best approach depends on who you are and your project.

For most people, don’t register your controllers as services. It’s simpler,
faster to develop, and avoids non-lazily-loaded service concerns.

So who should register controllers as services? If your team is very comfortable
with service-oriented-architecture and your project is quite large, where
it’s a challenge to keep track of what pieces affect other pieces, then it
starts to make more sense. Like with a lot of things in technology, by choosing
this path you’re asking to handle more complexity but understand that the
advantageous for you outweigh that concern.