Service Container

Service Container

Your application is full of useful objects: one "Mailer" object might help you deliver email messages while another object might help you save things to the database. Almost everything that your app "does" is actually done by one of these objects. And each time you install a new bundle, you get access to even more!

In Symfony, these useful objects are called services and each service lives inside a very special object called the service container. If you have the service container, then you can fetch a service by using that service's id:

$logger = $container->get('logger');
$entityManager = $container->get('doctrine.orm.entity_manager');

The container is the heart of Symfony: it allows you to standardize and centralize the way objects are constructed. It makes your life easier, is super fast, and emphasizes an architecture that promotes reusable and decoupled code. It's also a big reason that Symfony is so fast and extensible!

Finally, configuring and using the service container is easy. By the end of this article, you'll be comfortable creating your own objects via the container and customizing objects from any third-party bundle. You'll begin writing code that is more reusable, testable and decoupled, simply because the service container makes writing good code so easy.

Fetching and using Services

The moment you start a Symfony app, the container already contains many services. These are like tools, waiting for you to take advantage of them. In your controller, you have access to the container via $this->container. Want to log something? No problem:

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

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class ProductController extends Controller
{
    /**
     * @Route("/products")
     */
    public function listAction()
    {
        $logger = $this->container->get('logger');
        $logger->info('Look! I just used a service');

        // ...
    }
}

logger is a unique key for the Logger object. What other services are available? Find out by running:

1
$ php app/console debug:container

This is just a small sample of the output:

Service ID Class name
doctrine Doctrine\Bundle\DoctrineBundle\Registry
filesystem Symfony\Component\Filesystem\Filesystem
form.factory Symfony\Component\Form\FormFactory
logger Symfony\Bridge\Monolog\Logger
request_stack Symfony\Component\HttpFoundation\RequestStack
router Symfony\Bundle\FrameworkBundle\Routing\Router
security.authorization_checker Symfony\Component\Security\Core\Authorization\AuthorizationChecker
security.password_encoder Symfony\Component\Security\Core\Encoder\UserPasswordEncoder
session Symfony\Component\HttpFoundation\Session\Session
translator Symfony\Component\Translation\DataCollectorTranslator
twig Twig_Environment
validator Symfony\Component\Validator\Validator\ValidatorInterface

Throughout the docs, you'll see how to use the many different services that live in the container.

If the container holds so many useful objects (services), does that mean those objects are instantiated on every request? No! The container is lazy: it doesn't instantiate a service until (and unless) you ask for it. For example, if you never use the validator service during a request, the container will never instantiate it.

Creating/Configuring Services in the Container

You can also leverage the container to organize your own code into services. For example, suppose you want to show your users a random, happy message every time they do something. If you put this code in your controller, it can't be re-used. Instead, you decide to create a new class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// src/AppBundle/Service/MessageGenerator.php
namespace AppBundle\Service;

class MessageGenerator
{
    public function getHappyMessage()
    {
        $messages = [
            'You did it! You updated the system! Amazing!',
            'That was one of the coolest updates I\'ve seen all day!',
            'Great work! Keep going!',
        ];

        $index = array_rand($messages);

        return $messages[$index];
    }
}

Congratulations! You've just created your first service class. Next, you can teach the service container how to instantiate it:

  • YAML
    1
    2
    3
    4
    5
    # app/config/services.yml
    services:
        app.message_generator:
            class:     AppBundle\Service\MessageGenerator
            arguments: []
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <!-- app/config/services.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <service id="app.message_generator" class="AppBundle\Service\MessageGenerator">
            </service>
        </services>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    // app/config/services.php
    use AppBundle\Service\MessageGenerator;
    
    $container->register('app.message_generator', MessageGenerator::class)
        ->setArguments(array());
    

That's it! Your service - with the unique key app.message_generator - is now available in the container. You can use it immediately inside your controller:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public function newAction()
{
    // ...

    // the container will instantiate a new MessageGenerator()
    $messageGenerator = $this->container->get('app.message_generator');

    // or use this shorter syntax
    // $messageGenerator = $this->get('app.message_generator');

    $message = $messageGenerator->getHappyMessage();
    $this->addFlash('success', $message);
    // ...
}

When you ask for the app.message_generator service, the container constructs a new MessageGenerator object and returns it. If you never ask for the app.message_generator service during a request, it's never constructed, saving you memory and increasing the speed of your app. This also means that there's almost no performance overhead for defining a lot of services.

As a bonus, the app.message_generator service is only created once: the same instance is returned each time you ask for it.

Injecting Services/Config into a Service

What if you want to use the logger service from within MessageGenerator? Your service does not have a $this->container property: that's a special power only controllers have.

Instead, you should create a __construct() method, add a $logger argument and set it on a $logger property:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// src/AppBundle/Service/MessageGenerator.php
// ...

use Psr\Log\LoggerInterface;

class MessageGenerator
{
    private $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    public function getHappyMessage()
    {
        $this->logger->info('About to find a happy message!');
        // ...
    }
}

Tip

The LoggerInterface type-hint in the __construct() method is optional, but a good idea. You can find the correct type-hint by reading the docs for the service or by using the php app/console debug:container console command.

Next, tell the container the service has a constructor argument:

  • YAML
    1
    2
    3
    4
    5
    # app/config/services.yml
    services:
        app.message_generator:
            class:     AppBundle\Service\MessageGenerator
            arguments: ['@logger']
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <!-- app/config/services.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <service id="app.message_generator" class="AppBundle\Service\MessageGenerator">
                <argument type="service" id="logger" />
            </service>
        </services>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    // app/config/services.php
    use AppBundle\Service\MessageGenerator;
    use Symfony\Component\DependencyInjection\Reference;
    
    $container->register('app.message_generator', MessageGenerator::class)
        ->addArgument(new Reference('logger'));
    

That's it! The container now knows to pass the logger service as an argument when it instantiates the MessageGenerator. This is called dependency injection.

The arguments key holds an array of all of the constructor arguments to the service (just 1 so far). The @ symbol before @logger is important: it tells Symfony to pass the service named logger.

But you can pass anything as arguments. For example, suppose you want to make your class a bit more configurable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/AppBundle/Service/MessageGenerator.php
// ...

use Psr\Log\LoggerInterface;

class MessageGenerator
{
    private $logger;
    private $loggingEnabled;

    public function __construct(LoggerInterface $logger, $loggingEnabled)
    {
        $this->logger = $logger;
        $this->loggingEnabled = $loggingEnabled;
    }

    public function getHappyMessage()
    {
        if ($this->loggingEnabled) {
            $this->logger->info('About to find a happy message!');
        }
        // ...
    }
}

The class now has a second constructor argument. No problem, just update your service config:

  • YAML
    1
    2
    3
    4
    5
    # app/config/services.yml
    services:
        app.message_generator:
            class:     AppBundle\Service\MessageGenerator
            arguments: ['@logger', true]
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    <!-- app/config/services.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <service id="app.message_generator" class="AppBundle\Service\MessageGenerator">
                <argument type="service" id="logger" />
                <argument>true</argument>
            </service>
        </services>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // app/config/services.php
    use AppBundle\Service\MessageGenerator;
    use Symfony\Component\DependencyInjection\Reference;
    
    $container->register('app.message_generator', MessageGenerator::class)
        ->setArguments(array(
            new Reference('logger'),
            true,
        ));
    

You can even leverage environments to control this new value in different situations.

Service Parameters

In addition to holding service objects, the container also holds configuration, called parameters. To create a parameter, add it under the parameters key and reference it with the %parameter_name% syntax:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    # app/config/services.yml
    parameters:
        enable_generator_logging:  true
    
    services:
        app.message_generator:
            class:     AppBundle\Service\MessageGenerator
            arguments: ['@logger', '%enable_generator_logging%']
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!-- app/config/services.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <parameters>
                <parameter key="enable_generator_logging">true</parameter>
            </parameters>
    
            <service id="app.message_generator" class="AppBundle\Service\MessageGenerator">
                <argument type="service" id="logger" />
                <argument>%enable_generator_logging%</argument>
            </service>
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // app/config/services.php
    use AppBundle\Service\MessageGenerator;
    use Symfony\Component\DependencyInjection\Reference;
    
    $container->setParameter('enable_generator_logging', true);
    
    $container->register('app.message_generator', MessageGenerator::class)
        ->setArguments(array(
            new Reference('logger'),
            '%enable_generator_logging%',
        ));
    

Actually, once you define a parameter, it can be referenced via the %parameter_name% syntax in any other service configuration file - like config.yml. Many parameters are defined in a parameters.yml file.

You can then fetch the parameter in the service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class SiteUpdateManager
{
    // ...

    private $adminEmail;

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

You can also fetch parameters directly from the container:

1
2
3
4
5
6
7
8
public function newAction()
{
    // ...

    $isLoggingEnabled = $this->container
        ->getParameter('enable_generator_logging');
    // ...
}

Note

If you use a string that starts with @ or %, you need to escape it by adding another @ or %:

1
2
3
4
5
6
7
# app/config/parameters.yml
parameters:
    # This will be parsed as string '@securepass'
    mailer_password: '@@securepass'

    # Parsed as http://symfony.com/?foo=%s&amp;bar=%d
    url_pattern: 'http://symfony.com/?foo=%%s&amp;bar=%%d'

For more info about parameters, see Introduction to Parameters.

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