Service Container

Service Container

Screencast

Do you prefer video tutorials? Check out the Symfony Fundamentals screencast series.

Your application is full of useful objects: a “Mailer” object might help you send emails 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. The container allows you to centralize the way objects are constructed. It makes your life easier, promotes a strong architecture and is super fast!

Fetching and using Services

The moment you start a Symfony app, your container already contains many services. These are like tools: waiting for you to take advantage of them. In your controller, you can “ask” for a service from the container by type-hinting an argument with the service’s class or interface name. Want to log something? No problem:

  1. // src/Controller/ProductController.php
  2. namespace App\Controller;
  3. use Psr\Log\LoggerInterface;
  4. use Symfony\Component\HttpFoundation\Response;
  5. class ProductController
  6. {
  7. /**
  8. * @Route("/products")
  9. */
  10. public function list(LoggerInterface $logger): Response
  11. {
  12. $logger->info('Look, I just used a service!');
  13. // ...
  14. }
  15. }

What other services are available? Find out by running:

  1. $ php bin/console debug:autowiring
  2. # this is just a *small* sample of the output...
  3. Describes a logger instance.
  4. Psr\Log\LoggerInterface (monolog.logger)
  5. Request stack that controls the lifecycle of requests.
  6. Symfony\Component\HttpFoundation\RequestStack (request_stack)
  7. Interface for the session.
  8. Symfony\Component\HttpFoundation\Session\SessionInterface (session)
  9. RouterInterface is the interface that all Router classes must implement.
  10. Symfony\Component\Routing\RouterInterface (router.default)
  11. [...]

When you use these type-hints in your controller methods or inside your own services, Symfony will automatically pass you the service object matching that type.

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

Tip

There are actually many more services in the container, and each service has a unique id in the container, like session or router.default. For a full list, you can run php bin/console debug:container. But most of the time, you won’t need to worry about this. See Choose a Specific Service. See How to Debug the Service Container & List Services.

Creating/Configuring Services in the Container

You can also organize your own code into services. For example, suppose you need to show your users a random, happy message. If you put this code in your controller, it can’t be re-used. Instead, you decide to create a new class:

  1. // src/Service/MessageGenerator.php
  2. namespace App\Service;
  3. class MessageGenerator
  4. {
  5. public function getHappyMessage(): string
  6. {
  7. $messages = [
  8. 'You did it! You updated the system! Amazing!',
  9. 'That was one of the coolest updates I\'ve seen all day!',
  10. 'Great work! Keep going!',
  11. ];
  12. $index = array_rand($messages);
  13. return $messages[$index];
  14. }
  15. }

Congratulations! You’ve created your first service class! You can use it immediately inside your controller:

  1. // src/Controller/ProductController.php
  2. use App\Service\MessageGenerator;
  3. use Symfony\Component\HttpFoundation\Response;
  4. /**
  5. * @Route("/products/new")
  6. */
  7. public function new(MessageGenerator $messageGenerator): Response
  8. {
  9. // thanks to the type-hint, the container will instantiate a
  10. // new MessageGenerator and pass it to you!
  11. // ...
  12. $message = $messageGenerator->getHappyMessage();
  13. $this->addFlash('success', $message);
  14. // ...
  15. }

When you ask for the MessageGenerator service, the container constructs a new MessageGenerator object and returns it (see sidebar below). But if you never ask for the service, it’s never constructed: saving memory and speed. As a bonus, the MessageGenerator service is only created once: the same instance is returned each time you ask for it.

Automatic Service Loading in services.yaml

The documentation assumes you’re using the following service configuration, which is the default config for a new project:

  • YAML

    1. # config/services.yaml
    2. services:
    3. # default configuration for services in *this* file
    4. _defaults:
    5. autowire: true # Automatically injects dependencies in your services.
    6. autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
    7. # makes classes in src/ available to be used as services
    8. # this creates a service per class whose id is the fully-qualified class name
    9. App\:
    10. resource: '../src/*'
    11. exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
    12. # ...
  • XML

    1. <!-- config/services.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/dic/services
    6. https://symfony.com/schema/dic/services/services-1.0.xsd">
    7. <services>
    8. <!-- Default configuration for services in *this* file -->
    9. <defaults autowire="true" autoconfigure="true"/>
    10. <!-- makes classes in src/ available to be used as services -->
    11. <!-- this creates a service per class whose id is the fully-qualified class name -->
    12. <prototype namespace="App\" resource="../src/*" exclude="../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}"/>
    13. <!-- ... -->
    14. </services>
    15. </container>
  • PHP

    1. // config/services.php
    2. namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    3. return function(ContainerConfigurator $configurator) {
    4. // default configuration for services in *this* file
    5. $services = $configurator->services()
    6. ->defaults()
    7. ->autowire() // Automatically injects dependencies in your services.
    8. ->autoconfigure() // Automatically registers your services as commands, event subscribers, etc.
    9. ;
    10. // makes classes in src/ available to be used as services
    11. // this creates a service per class whose id is the fully-qualified class name
    12. $services->load('App\\', '../src/*')
    13. ->exclude('../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}');
    14. };

Tip

The value of the resource and exclude options can be any valid glob pattern). The value of the exclude option can also be an array of glob patterns.

Thanks to this configuration, you can automatically use any classes from the src/ directory as a service, without needing to manually configure it. Later, you’ll learn more about this in Importing Many Services at once with resource.

If you’d prefer to manually wire your service, that’s totally possible: see Explicitly Configuring Services and Arguments.

Injecting Services/Config into a Service

What if you need to access the logger service from within MessageGenerator? No problem! Create a __construct() method with a$loggerargument that has theLoggerInterfacetype-hint. Set this on a new$logger` property and use it later:

  1. // src/Service/MessageGenerator.php
  2. namespace App\Service;
  3. use Psr\Log\LoggerInterface;
  4. class MessageGenerator
  5. {
  6. private $logger;
  7. public function __construct(LoggerInterface $logger)
  8. {
  9. $this->logger = $logger;
  10. }
  11. public function getHappyMessage(): string
  12. {
  13. $this->logger->info('About to find a happy message!');
  14. // ...
  15. }
  16. }

That’s it! The container will automatically know to pass the logger service when instantiating the MessageGenerator. How does it know to do this? Autowiring. The key is the LoggerInterface type-hint in your __construct() method and theautowire: trueconfig inservices.yaml`. When you type-hint an argument, the container will automatically find the matching service. If it can’t, you’ll see a clear exception with a helpful suggestion.

By the way, this method of adding dependencies to your `__construct() method is called dependency injection.

How should you know to use LoggerInterface for the type-hint? You can either read the docs for whatever feature you’re using, or get a list of autowireable type-hints by running:

  1. $ php bin/console debug:autowiring
  2. # this is just a *small* sample of the output...
  3. Describes a logger instance.
  4. Psr\Log\LoggerInterface (monolog.logger)
  5. Request stack that controls the lifecycle of requests.
  6. Symfony\Component\HttpFoundation\RequestStack (request_stack)
  7. Interface for the session.
  8. Symfony\Component\HttpFoundation\Session\SessionInterface (session)
  9. RouterInterface is the interface that all Router classes must implement.
  10. Symfony\Component\Routing\RouterInterface (router.default)
  11. [...]

Handling Multiple Services

Suppose you also want to email a site administrator each time a site update is made. To do that, you create a new class:

  1. // src/Service/SiteUpdateManager.php
  2. namespace App\Service;
  3. use App\Service\MessageGenerator;
  4. use Symfony\Component\Mailer\MailerInterface;
  5. use Symfony\Component\Mime\Email;
  6. class SiteUpdateManager
  7. {
  8. private $messageGenerator;
  9. private $mailer;
  10. public function __construct(MessageGenerator $messageGenerator, MailerInterface $mailer)
  11. {
  12. $this->messageGenerator = $messageGenerator;
  13. $this->mailer = $mailer;
  14. }
  15. public function notifyOfSiteUpdate(): bool
  16. {
  17. $happyMessage = $this->messageGenerator->getHappyMessage();
  18. $email = (new Email())
  19. ->from('[email protected]')
  20. ->to('[email protected]')
  21. ->subject('Site update just happened!')
  22. ->text('Someone just updated the site. We told them: '.$happyMessage);
  23. $this->mailer->send($email);
  24. // ...
  25. return true;
  26. }
  27. }

This needs the MessageGenerator and the Mailer service. That’s no problem, we ask them by type hinting their class and interface names! Now, this new service is ready to be used. In a controller, for example, you can type-hint the new SiteUpdateManager class and use it:

  1. // src/Controller/SiteController.php
  2. namespace App\Controller;
  3. use App\Service\SiteUpdateManager;
  4. // ...
  5. class SiteController extends AbstractController
  6. {
  7. public function new(SiteUpdateManager $siteUpdateManager)
  8. {
  9. // ...
  10. if ($siteUpdateManager->notifyOfSiteUpdate()) {
  11. $this->addFlash('success', 'Notification mail was sent successfully.');
  12. }
  13. // ...
  14. }
  15. }

Thanks to autowiring and your type-hints in __construct(), the container creates theSiteUpdateManager` object and passes it the correct argument. In most cases, this works perfectly.

Manually Wiring Arguments

But there are a few cases when an argument to a service cannot be autowired. For example, suppose you want to make the admin email configurable:

  1. // src/Service/SiteUpdateManager.php
  2. // ...
  3. class SiteUpdateManager
  4. {
  5. // ...
  6. + private $adminEmail;
  7. - public function __construct(MessageGenerator $messageGenerator, MailerInterface $mailer)
  8. + public function __construct(MessageGenerator $messageGenerator, MailerInterface $mailer, string $adminEmail)
  9. {
  10. // ...
  11. + $this->adminEmail = $adminEmail;
  12. }
  13. public function notifyOfSiteUpdate(): bool
  14. {
  15. // ...
  16. $email = (new Email())
  17. // ...
  18. - ->to('[email protected]')
  19. + ->to($this->adminEmail)
  20. // ...
  21. ;
  22. // ...
  23. }
  24. }

If you make this change and refresh, you’ll see an error:

Cannot autowire service “App\Service\SiteUpdateManager”: argument “$adminEmail” of method “__construct()” must have a type-hint or be given a value explicitly.

That makes sense! There is no way that the container knows what value you want to pass here. No problem! In your configuration, you can explicitly set this argument:

  • YAML

    1. # config/services.yaml
    2. services:
    3. # ... same as before
    4. # same as before
    5. App\:
    6. resource: '../src/*'
    7. exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
    8. # explicitly configure the service
    9. App\Service\SiteUpdateManager:
    10. arguments:
    11. $adminEmail: '[email protected]'
  • XML

    1. <!-- config/services.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/dic/services
    6. https://symfony.com/schema/dic/services/services-1.0.xsd">
    7. <services>
    8. <!-- ... same as before -->
    9. <!-- Same as before -->
    10. <prototype namespace="App\"
    11. resource="../src/*"
    12. exclude="../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}"
    13. />
    14. <!-- Explicitly configure the service -->
    15. <service id="App\Service\SiteUpdateManager">
    16. <argument key="$adminEmail">[email protected]</argument>
    17. </service>
    18. </services>
    19. </container>
  • PHP

    1. // config/services.php
    2. namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    3. use App\Service\SiteUpdateManager;
    4. return function(ContainerConfigurator $configurator) {
    5. // ...
    6. // same as before
    7. $services->load('App\\', '../src/*')
    8. ->exclude('../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}');
    9. $services->set(SiteUpdateManager::class)
    10. ->arg('$adminEmail', '[email protected]')
    11. ;
    12. };

Thanks to this, the container will pass manager@example.com to the $adminEmail argument of __construct when creating the SiteUpdateManager service. The other arguments will still be autowired.

But, isn’t this fragile? Fortunately, no! If you rename the $adminEmail argument to something else - e.g. $mainEmail - you will get a clear exception when you reload the next page (even if that page doesn’t use this service).

Service Parameters

In addition to holding service objects, the container also holds configuration, called parameters. The main article about Symfony configuration explains the configuration parameters in detail and shows all their types (string, boolean, array, binary and PHP constant parameters).

However, there is another type of parameter related to services. In YAML config, any string which starts with @ is considered as the ID of a service, instead of a regular string. In XML config, use the type="service" type for the parameter and in PHP config use the ref function:

  • YAML

    1. # config/services.yaml
    2. services:
    3. App\Service\MessageGenerator:
    4. arguments:
    5. # this is not a string, but a reference to a service called 'logger'
    6. - '@logger'
    7. # if the value of a string argument starts with '@', you need to escape
    8. # it by adding another '@' so Symfony doesn't consider it a service
    9. # the following example would be parsed as the string '@securepassword'
    10. # - '@@securepassword'
  • XML

    1. <!-- config/services.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/dic/services
    6. https://symfony.com/schema/dic/services/services-1.0.xsd">
    7. <services>
    8. <service id="App\Service\MessageGenerator">
    9. <argument type="service" id="logger"/>
    10. </service>
    11. </services>
    12. </container>
  • PHP

    1. // config/services.php
    2. namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    3. use App\Service\MessageGenerator;
    4. return function(ContainerConfigurator $configurator) {
    5. $services = $configurator->services();
    6. $services->set(MessageGenerator::class)
    7. ->args([ref('logger')])
    8. ;
    9. };

Working with container parameters is straightforward using the container’s accessor methods for parameters:

  1. // checks if a parameter is defined (parameter names are case-sensitive)
  2. $container->hasParameter('mailer.transport');
  3. // gets value of a parameter
  4. $container->getParameter('mailer.transport');
  5. // adds a new parameter
  6. $container->setParameter('mailer.transport', 'sendmail');

Caution

The used . notation is a Symfony convention to make parameters easier to read. Parameters are flat key-value elements, they can’t be organized into a nested array

Note

You can only set a parameter before the container is compiled, not at run-time. To learn more about compiling the container see Compiling the Container.

Choose a Specific Service

The MessageGenerator service created earlier requires a LoggerInterface argument:

  1. // src/Service/MessageGenerator.php
  2. namespace App\Service;
  3. use Psr\Log\LoggerInterface;
  4. class MessageGenerator
  5. {
  6. private $logger;
  7. public function __construct(LoggerInterface $logger)
  8. {
  9. $this->logger = $logger;
  10. }
  11. // ...
  12. }

However, there are multiple services in the container that implement LoggerInterface, such as logger, monolog.logger.request, monolog.logger.php, etc. How does the container know which one to use?

In these situations, the container is usually configured to automatically choose one of the services - logger in this case (read more about why in Using Aliases to Enable Autowiring). But, you can control this and pass in a different logger:

  • YAML

    1. # config/services.yaml
    2. services:
    3. # ... same code as before
    4. # explicitly configure the service
    5. App\Service\MessageGenerator:
    6. arguments:
    7. # the '@' symbol is important: that's what tells the container
    8. # you want to pass the *service* whose id is 'monolog.logger.request',
    9. # and not just the *string* 'monolog.logger.request'
    10. $logger: '@monolog.logger.request'
  • XML

    1. <!-- config/services.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/dic/services
    6. https://symfony.com/schema/dic/services/services-1.0.xsd">
    7. <services>
    8. <!-- ... same code as before -->
    9. <!-- Explicitly configure the service -->
    10. <service id="App\Service\MessageGenerator">
    11. <argument key="$logger" type="service" id="monolog.logger.request"/>
    12. </service>
    13. </services>
    14. </container>
  • PHP

    1. // config/services.php
    2. namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    3. use App\Service\MessageGenerator;
    4. return function(ContainerConfigurator $configurator) {
    5. // ... same code as before
    6. // explicitly configure the service
    7. $services->set(MessageGenerator::class)
    8. ->arg('$logger', ref('monolog.logger.request'))
    9. ;
    10. };

This tells the container that the $logger argument to __construct should use service whose id is monolog.logger.request.

For a full list of all possible services in the container, run:

  1. $ php bin/console debug:container

Binding Arguments by Name or Type

You can also use the bind keyword to bind specific arguments by name or type:

  • YAML

    1. # config/services.yaml
    2. services:
    3. _defaults:
    4. bind:
    5. # pass this value to any $adminEmail argument for any service
    6. # that's defined in this file (including controller arguments)
    7. $adminEmail: '[email protected]'
    8. # pass this service to any $requestLogger argument for any
    9. # service that's defined in this file
    10. $requestLogger: '@monolog.logger.request'
    11. # pass this service for any LoggerInterface type-hint for any
    12. # service that's defined in this file
    13. Psr\Log\LoggerInterface: '@monolog.logger.request'
    14. # optionally you can define both the name and type of the argument to match
    15. string $adminEmail: '[email protected]'
    16. Psr\Log\LoggerInterface $requestLogger: '@monolog.logger.request'
    17. iterable $rules: !tagged_iterator app.foo.rule
    18. # ...
  • XML

    1. <!-- config/services.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/dic/services
    6. https://symfony.com/schema/dic/services/services-1.0.xsd">
    7. <services>
    8. <defaults autowire="true" autoconfigure="true" public="false">
    9. <bind key="$adminEmail">[email protected]</bind>
    10. <bind key="$requestLogger"
    11. type="service"
    12. id="monolog.logger.request"
    13. />
    14. <bind key="Psr\Log\LoggerInterface"
    15. type="service"
    16. id="monolog.logger.request"
    17. />
    18. <!-- optionally you can define both the name and type of the argument to match -->
    19. <bind key="string $adminEmail">[email protected]</bind>
    20. <bind key="Psr\Log\LoggerInterface $requestLogger"
    21. type="service"
    22. id="monolog.logger.request"
    23. />
    24. <bind key="iterable $rules"
    25. type="tagged_iterator"
    26. tag="app.foo.rule"
    27. />
    28. </defaults>
    29. <!-- ... -->
    30. </services>
    31. </container>
  • PHP

    1. // config/services.php
    2. namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    3. use App\Controller\LuckyController;
    4. use Psr\Log\LoggerInterface;
    5. use Symfony\Component\DependencyInjection\Definition;
    6. use Symfony\Component\DependencyInjection\Reference;
    7. return function(ContainerConfigurator $configurator) {
    8. $services = $configurator->services()
    9. ->defaults()
    10. // pass this value to any $adminEmail argument for any service
    11. // that's defined in this file (including controller arguments)
    12. ->bind('$adminEmail', '[email protected]')
    13. // pass this service to any $requestLogger argument for any
    14. // service that's defined in this file
    15. ->bind('$requestLogger', ref('monolog.logger.request'))
    16. // pass this service for any LoggerInterface type-hint for any
    17. // service that's defined in this file
    18. ->bind(LoggerInterface::class, ref('monolog.logger.request'))
    19. // optionally you can define both the name and type of the argument to match
    20. ->bind('string $adminEmail', '[email protected]')
    21. ->bind(LoggerInterface::class.' $requestLogger', ref('monolog.logger.request'))
    22. ->bind('iterable $rules', tagged_iterator('app.foo.rule'))
    23. ;
    24. // ...
    25. };

New in version 4.4: The feature to bind tagged services was introduced in Symfony 4.4.

By putting the bind key under _defaults, you can specify the value of any argument for any service defined in this file! You can bind arguments by name (e.g. $adminEmail), by type (e.g. Psr\Log\LoggerInterface) or both (e.g. Psr\Log\LoggerInterface $requestLogger).

The bind config can also be applied to specific services or when loading many services at once (i.e. Importing Many Services at once with resource).

The autowire Option

Above, the services.yaml file has autowire: true in the _defaults section so that it applies to all services defined in that file. With this setting, you’re able to type-hint arguments in the `__construct() method of your services and the container will automatically pass you the correct arguments. This entire entry has been written around autowiring.

For more details about autowiring, check out Defining Services Dependencies Automatically (Autowiring).

The autoconfigure Option

Above, the services.yaml file has autoconfigure: true in the _defaults section so that it applies to all services defined in that file. With this setting, the container will automatically apply certain configuration to your services, based on your service’s class. This is mostly used to auto-tag your services.

For example, to create a Twig extension, you need to create a class, register it as a service, and tag it with twig.extension.

But, with autoconfigure: true, you don’t need the tag. In fact, if you’re using the default services.yaml config, you don’t need to do anything: the service will be automatically loaded. Then, autoconfigure will add the twig.extension tag for you, because your class implements Twig\Extension\ExtensionInterface. And thanks to autowire, you can even add constructor arguments without any configuration.

Linting Service Definitions

New in version 4.4: The lint:container command was introduced in Symfony 4.4.

The lint:container command checks that the arguments injected into services match their type declarations. It’s useful to run it before deploying your application to production (e.g. in your continuous integration server):

  1. $ php bin/console lint:container

Checking the types of all service arguments whenever the container is compiled can hurt performance. That’s why this type checking is implemented in a compiler pass called CheckTypeDeclarationsPass which is disabled by default and enabled only when executing the lint:container command. If you don’t mind the performance loss, enable the compiler pass in your application.

Public Versus Private Services

From Symfony 4.0, every service defined is private by default.

What does this mean? When a service is public, you can access it directly from the container object, which can also be injected thanks to autowiring. This is mostly useful when you want to fetch services lazily:

  1. namespace App\Generator;
  2. use Psr\Container\ContainerInterface;
  3. class MessageGenerator
  4. {
  5. private $container;
  6. public function __construct(ContainerInterface $container)
  7. {
  8. $this->container = $container;
  9. }
  10. public function generate(string $message, string $template = null, array $context = []): string
  11. {
  12. if ($template && $this->container->has('twig')) {
  13. // there IS a public "twig" service in the container
  14. $twig = $this->container->get('twig');
  15. return $twig->render($template, $context + ['message' => $message]);
  16. }
  17. // if no template is passed, the "twig" service will not be loaded
  18. // ...
  19. }
  20. }

As a best practice, you should only create private services. This allows for safe container optimizations, e.g. removing unused services. You should not use `$container->get() to fetch public services, as it will make it harder to make those services private later. Instead consider injecting services or using Service Subscribers or Locators.

But, if you do need to make a service public, override the public setting:

  • YAML

    1. # config/services.yaml
    2. services:
    3. # ... same code as before
    4. # explicitly configure the service
    5. App\Service\PublicService:
    6. public: true
  • XML

    1. <!-- config/services.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/dic/services
    6. https://symfony.com/schema/dic/services/services-1.0.xsd">
    7. <services>
    8. <!-- ... same code as before -->
    9. <!-- Explicitly configure the service -->
    10. <service id="App\Service\PublicService" public="true"></service>
    11. </services>
    12. </container>
  • PHP

    1. // config/services.php
    2. namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    3. use App\Service\PublicService;
    4. return function(ContainerConfigurator $configurator) {
    5. // ... same as code before
    6. // explicitly configure the service
    7. $services->set(Service\PublicService::class)
    8. ->public()
    9. ;
    10. };

Note

Instead of injecting the container you should consider using a service locator instead.

Importing Many Services at once with resource

You’ve already seen that you can import many services at once by using the resource key. For example, the default Symfony configuration contains this:

  • YAML

    1. # config/services.yaml
    2. services:
    3. # ... same as before
    4. # makes classes in src/ available to be used as services
    5. # this creates a service per class whose id is the fully-qualified class name
    6. App\:
    7. resource: '../src/*'
    8. exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
  • XML

    1. <!-- config/services.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/dic/services
    6. https://symfony.com/schema/dic/services/services-1.0.xsd">
    7. <services>
    8. <!-- ... same as before -->
    9. <prototype namespace="App\" resource="../src/*" exclude="../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}"/>
    10. </services>
    11. </container>
  • PHP

    1. // config/services.php
    2. namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    3. return function(ContainerConfigurator $configurator) {
    4. // ...
    5. // makes classes in src/ available to be used as services
    6. // this creates a service per class whose id is the fully-qualified class name
    7. $services->load('App\\', '../src/*')
    8. ->exclude('../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}');
    9. };

Tip

The value of the resource and exclude options can be any valid glob pattern).

This can be used to quickly make many classes available as services and apply some default configuration. The id of each service is its fully-qualified class name. You can override any service that’s imported by using its id (class name) below (e.g. see Manually Wiring Arguments). If you override a service, none of the options (e.g. public) are inherited from the import (but the overridden service does still inherit from _defaults).

You can also exclude certain paths. This is optional, but will slightly increase performance in the dev environment: excluded paths are not tracked and so modifying them will not cause the container to be rebuilt.

Note

Wait, does this mean that every class in src/ is registered as a service? Even model classes? Actually, no. As long as you keep your imported services as private, all classes in src/ that are not explicitly used as services are automatically removed from the final container. In reality, the import means that all classes are “available to be used as services” without needing to be manually configured.

Multiple Service Definitions Using the Same Namespace

If you define services using the YAML config format, the PHP namespace is used as the key of each configuration, so you can’t define different service configs for classes under the same namespace:

  • YAML

    1. # config/services.yaml
    2. services:
    3. App\Domain\:
    4. resource: '../src/Domain/*'
    5. # ...
  • XML

    1. <!-- config/services.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/dic/services
    6. https://symfony.com/schema/dic/services/services-1.0.xsd">
    7. <services>
    8. <prototype namespace="App\Domain"
    9. resource="../src/App/Domain/*"/>
    10. <!-- ... -->
    11. </services>
    12. </container>
  • PHP

    1. // config/services.php
    2. use Symfony\Component\DependencyInjection\Definition;
    3. $defaults = new Definition();
    4. // $this is a reference to the current loader
    5. $this->registerClasses(
    6. $defaults,
    7. 'App\\Domain\\',
    8. '../src/App/Domain/*'
    9. );
    10. // ...

In order to have multiple definitions, add the namespace option and use any unique string as the key of each service config:

  1. # config/services.yaml
  2. services:
  3. command_handlers:
  4. namespace: App\Domain\
  5. resource: '../src/Domain/*/CommandHandler'
  6. tags: [command_handler]
  7. event_subscribers:
  8. namespace: App\Domain\
  9. resource: '../src/Domain/*/EventSubscriber'
  10. tags: [event_subscriber]

Explicitly Configuring Services and Arguments

Prior to Symfony 3.3, all services and (typically) arguments were explicitly configured: it was not possible to load services automatically and autowiring was much less common.

Both of these features are optional. And even if you use them, there may be some cases where you want to manually wire a service. For example, suppose that you want to register 2 services for the SiteUpdateManager class - each with a different admin email. In this case, each needs to have a unique service id:

  • YAML

    1. # config/services.yaml
    2. services:
    3. # ...
    4. # this is the service's id
    5. site_update_manager.superadmin:
    6. class: App\Service\SiteUpdateManager
    7. # you CAN still use autowiring: we just want to show what it looks like without
    8. autowire: false
    9. # manually wire all arguments
    10. arguments:
    11. - '@App\Service\MessageGenerator'
    12. - '@mailer'
    13. - '[email protected]'
    14. site_update_manager.normal_users:
    15. class: App\Service\SiteUpdateManager
    16. autowire: false
    17. arguments:
    18. - '@App\Service\MessageGenerator'
    19. - '@mailer'
    20. - '[email protected]'
    21. # Create an alias, so that - by default - if you type-hint SiteUpdateManager,
    22. # the site_update_manager.superadmin will be used
    23. App\Service\SiteUpdateManager: '@site_update_manager.superadmin'
  • XML

    1. <!-- config/services.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/dic/services
    6. https://symfony.com/schema/dic/services/services-1.0.xsd">
    7. <services>
    8. <!-- ... -->
    9. <service id="site_update_manager.superadmin" class="App\Service\SiteUpdateManager" autowire="false">
    10. <argument type="service" id="App\Service\MessageGenerator"/>
    11. <argument type="service" id="mailer"/>
    12. <argument>[email protected]</argument>
    13. </service>
    14. <service id="site_update_manager.normal_users" class="App\Service\SiteUpdateManager" autowire="false">
    15. <argument type="service" id="App\Service\MessageGenerator"/>
    16. <argument type="service" id="mailer"/>
    17. <argument>[email protected]</argument>
    18. </service>
    19. <service id="App\Service\SiteUpdateManager" alias="site_update_manager.superadmin"/>
    20. </services>
    21. </container>
  • PHP

    1. // config/services.php
    2. namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    3. use App\Service\MessageGenerator;
    4. use App\Service\SiteUpdateManager;
    5. return function(ContainerConfigurator $configurator) {
    6. // ...
    7. // site_update_manager.superadmin is the service's id
    8. $services->set('site_update_manager.superadmin', SiteUpdateManager::class)
    9. // you CAN still use autowiring: we just want to show what it looks like without
    10. ->autowire(false)
    11. // manually wire all arguments
    12. ->args([
    13. ref(MessageGenerator::class),
    14. ref('mailer'),
    15. '[email protected]',
    16. ]);
    17. $services->set('site_update_manager.normal_users', SiteUpdateManager::class)
    18. ->autowire(false)
    19. ->args([
    20. ref(MessageGenerator::class),
    21. ref('mailer'),
    22. '[email protected]',
    23. ]);
    24. // Create an alias, so that - by default - if you type-hint SiteUpdateManager,
    25. // the site_update_manager.superadmin will be used
    26. $services->alias(SiteUpdateManager::class, 'site_update_manager.superadmin');
    27. };

In this case, two services are registered: site_update_manager.superadmin and site_update_manager.normal_users. Thanks to the alias, if you type-hint SiteUpdateManager the first (site_update_manager.superadmin) will be passed. If you want to pass the second, you’ll need to manually wire the service.

Caution

If you do not create the alias and are loading all services from src/, then three services have been created (the automatic service + your two services) and the automatically loaded service will be passed - by default - when you type-hint SiteUpdateManager. That’s why creating the alias is a good idea.

Learn more

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