Skip to content

How to Define Controllers as Services

Warning: You are browsing the documentation for Symfony 2.x, which is no longer maintained.

Read the updated version of this page for Symfony 7.2 (the current stable version).

Caution

Defining controllers as services is not officially recommended by Symfony. They are used by some developers for very specific use cases, such as DDD (domain-driven design) and Hexagonal Architecture applications.

In the Controller guide, you've learned how easily a controller can be used when it extends the base Controller class. While this works fine, controllers can also be specified as services. Even if you don't specify your controllers as services, you might see them being used in some open-source Symfony bundles, so it may be useful to understand both approaches.

These are the main advantages of defining controllers as services:

  • The entire controller and any service passed to it can be modified via the service container configuration. This is useful when developing reusable bundles;
  • Your controllers are more "sandboxed". By looking at the constructor arguments, it's easy to see what types of things this controller may or may not do;
  • If you're not passing some required dependencies or if you are injecting some non-existent services, you'll get errors during the container compilation instead of during runtime execution;
  • Since dependencies must be injected manually, it's more obvious when your controller is becoming too big (i.e. if you have many constructor arguments).

These are the main drawbacks of defining controllers as services:

  • It takes more work to create the controllers and they become more verbose because they don't have automatic access to the services and the base controller shortcuts;
  • The constructor of the controllers can rapidly become too complex because you must inject every single dependency needed by them.

The recommendation from the best practices is also valid for controllers defined as services: avoid putting your business logic into the controllers. Instead, inject services that do the bulk of the work.

Defining the Controller as a Service

A controller can be defined as a service in the same way as any other class. For example, if you have the following simple controller:

1
2
3
4
5
6
7
8
9
10
11
12
// src/AppBundle/Controller/HelloController.php
namespace AppBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class HelloController
{
    public function indexAction($name)
    {
        return new Response('<html><body>Hello '.$name.'!</body></html>');
    }
}

Then you can define it as a service as follows:

1
2
3
4
# app/config/services.yml
services:
    app.hello_controller:
        class: AppBundle\Controller\HelloController

Referring to the Service

To refer to a controller that's defined as a service, use the single colon (:) notation. For example, to forward to the indexAction() method of the service defined above with the id app.hello_controller:

1
$this->forward('app.hello_controller:indexAction', array('name' => $name));

Note

Make sure the method name in your route (e.g. indexAction) matches the method name exactly. Unlike the traditional Bundle:Controller:method notation, the Action suffix is not automatically added for you.

You can also route to the service by using the same notation when defining the route _controller value:

1
2
3
4
# app/config/routing.yml
hello:
    path:     /hello
    defaults: { _controller: app.hello_controller:indexAction }

Tip

You can also use annotations to configure routing using a controller defined as a service. Make sure you specify the service ID in the @Route annotation. See the FrameworkExtraBundle documentation for details.

Tip

If your controller implements the __invoke() method, you can simply refer to the service id (app.hello_controller).

Alternatives to base Controller Methods

When using a controller defined as a service, it will most likely not extend the base Controller class. Instead of relying on its shortcut methods, you'll interact directly with the services that you need. Fortunately, this is usually pretty easy and the base Controller class source code is a great source on how to perform many common tasks.

For example, if you want to render a template instead of creating the Response object directly, then your code would look like this if you were extending Symfony's base controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/AppBundle/Controller/HelloController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class HelloController extends Controller
{
    public function indexAction($name)
    {
        return $this->render(
            'hello/index.html.twig',
            array('name' => $name)
        );
    }
}

If you look at the source code for the render() function in Symfony's base Controller class, you'll see that this method actually uses the templating service:

1
2
3
4
public function render($view, array $parameters = array(), Response $response = null)
{
    return $this->container->get('templating')->renderResponse($view, $parameters, $response);
}

In a controller that's defined as a service, you can instead inject the templating service and use it directly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/AppBundle/Controller/HelloController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;

class HelloController
{
    private $templating;

    public function __construct(EngineInterface $templating)
    {
        $this->templating = $templating;
    }

    public function indexAction($name)
    {
        return $this->templating->renderResponse(
            'hello/index.html.twig',
            array('name' => $name)
        );
    }
}

The service definition also needs modifying to specify the constructor argument:

1
2
3
4
5
# app/config/services.yml
services:
    app.hello_controller:
        class:     AppBundle\Controller\HelloController
        arguments: ['@templating']

Rather than fetching the templating service from the container, you can inject only the exact service(s) that you need directly into the controller.

Note

This does not mean that you cannot extend these controllers from your own base controller. The move away from the standard base controller is because its helper methods rely on having the container available which is not the case for controllers that are defined as services. It may be a good idea to extract common code into a service that's injected rather than place that code into a base controller that you extend. Both approaches are valid, exactly how you want to organize your reusable code is up to you.

Base Controller Methods and Their Service Replacements

This list explains how to replace the convenience methods of the base controller:

createForm() (service: form.factory)
1
$formFactory->create($type, $data, $options);
createFormBuilder() (service: form.factory)
1
$formFactory->createBuilder('form', $data, $options);
createNotFoundException()
1
new NotFoundHttpException($message, $previous);
forward() (service: http_kernel)
1
2
3
4
5
6
7
use Symfony\Component\HttpKernel\HttpKernelInterface;
// ...

$request = ...;
$attributes = array_merge($path, array('_controller' => $controller));
$subRequest = $request->duplicate($query, null, $attributes);
$httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
generateUrl() (service: router)
1
$router->generate($route, $params, $referenceType);

Note

The $referenceType argument must be one of the constants defined in the UrlGeneratorInterface.

getDoctrine() (service: doctrine)
Simply inject doctrine instead of fetching it from the container.
getUser() (service: security.token_storage)
1
2
3
4
5
$user = null;
$token = $tokenStorage->getToken();
if (null !== $token && is_object($token->getUser())) {
     $user = $token->getUser();
}
isGranted() (service: security.authorization_checker)
1
$authChecker->isGranted($attributes, $object);
redirect()
1
2
3
use Symfony\Component\HttpFoundation\RedirectResponse;

return new RedirectResponse($url, $status);
render() (service: templating)
1
$templating->renderResponse($view, $parameters, $response);
renderView() (service: templating)
1
$templating->render($view, $parameters);
stream() (service: templating)
1
2
3
4
5
6
7
8
use Symfony\Component\HttpFoundation\StreamedResponse;

$templating = $this->templating;
$callback = function () use ($templating, $view, $parameters) {
    $templating->stream($view, $parameters);
};

return new StreamedResponse($callback);

Tip

getRequest() has been deprecated. Instead, have an argument to your controller action method called Request $request. The order of the parameters is not important, but the typehint must be provided.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version