Why have Controllers as Services in Symfony?

If you're just getting started with Symfony then you get bombarded with quite a few terms and concepts. Two of them are Controllers and Services. Controllers are quite straightforward in their actions (ha-ha) and simply take requests and return responses.

The concept of a services is simple too, it's technically just a PHP object that performs a task over and over again somewhere in your application. Old school PHP developers can think of it as a fancy include to include statement you can use in different files to get access to the same exact function without replicating code.

Obviously there's quite a bit more going on underneath the hood and you may want to familiarise yourself with the Symfony Service Container if you want to know more of the details. In a nutshell, though the service allows importing (or injecting) other services or settings to the scope of the service.

Using services for tasks repeating in multiple locations of your application undoubtedly makes sense, but why should you shrinkwrap your controllers into a service? If you look at the official Symfony Demo Application does not do this. So why should yours?

Controllers as Services in eZ Platform

There is a whole section in the Symfony documentation dedicated to how to define Controllers as Services in Symfony. This is without a doubt a great resource for learning the mechanisms and benefits of doing so.

The Blog Controller

The blog controller includes an action listing blog articles to render a simple list of them:

This is quite straightforward to read, even if you are unaware of how the eZ Platform Public API works, as it is yet another service and as such familiar to any Symfony Framework developer.

The Blog Controller Service Definition

If you have worked with services in Symfony in the past, the definition of the blog controller service is clearcut. First it's a common practise to define the service classes as parameters in the beginning of the YAML file, and from there on you move on to passing arguments:

In this case many of the arguments passed to the blog service are other services. There are a few parameters and there might as well be siteaccess aware dynamic settings too. Adding additional injections in the future is possible as well.

Injecting dependencies in the Blog Controller

In this case, the controller is done with constructor injection, so upon the initialization the services passed are set to be the properties of the specific object:

Now all the actions have access to whatever services or parameters were injected here. Allowing for easy access to services provided by eZ Platform domain services or any other services accessible to in your Symfony application.

Calling the Controller Actions

We've now set up a controller as a service, but obviously we need to call it somewhere. eZ Platform uses the view API and YAML configurations to match controllers and templates to dynamic routes provided by the content engine:

Calling a controller turned into a service follows the format

app.controller.blog:listBlogPostsAction

​where as a regular controller would be, in this case, called with

AppBundle:listBlog:Posts

Calling with the controller tag routes the call internally to the service mechanism, instead of calling the controller directly.

Conclusion

In the case of building a single site wrapping controllers into services might be of limited use, but if you want to reuse your blog functionality in the future it becomes more clear with the structure. And if your application contains a number of bundles with controllers, then the entrypoint of debugging is more straightforward as you can utilise the service debugging tooling.

In addition to easier extension and reuse of functionalities across different projects, services as containers also simplify modifying the application through configuration, rather than code. Or being locked into proprietary CMS extension methods, instead opening up opportunities for a Hexagonal Architecture.