Service Container (Symfony Docs)
Service Container (Symfony Docs)
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!
// src/Controller/ProductController.php
namespace App\Controller;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
// ...
https://symfony.com/doc/current/service_container.html 1/23
27/9/23, 23:37 Service Container (Symfony Docs)
}
}
Autowirable Types
=================
The following classes & interfaces can be used as type-hints when autowiring:
[...]
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 request_stack 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 Service Container. See
How to Debug the Service Container & List Services.
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:
// src/Service/MessageGenerator.php
namespace App\Service;
class MessageGenerator
{
public function getHappyMessage(): string
{
$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 created your first service class! You can use it immediately inside your controller:
// src/Controller/ProductController.php
use App\Service\MessageGenerator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
$message = $messageGenerator->getHappyMessage();
https://symfony.com/doc/current/service_container.html 3/23
27/9/23, 23:37 Service Container (Symfony Docs)
$this->addFlash('success', $message);
// ...
}
}
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.
The documentation assumes you're using the following service configuration, which is the default
config for a new project:
# config/services.yaml
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event
# ...
Tip
https://symfony.com/doc/current/service_container.html 4/23
27/9/23, 23:37 Service Container (Symfony Docs)
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 Service Container.
If you'd prefer to manually wire your service, that's totally possible: see Service Container.
use Symfony\Component\DependencyInjection\Attribute\When;
#[When(env: 'dev')]
class SomeClass
{
// ...
}
// you can also apply more than one When attribute to the same class
#[When(env: 'dev')]
#[When(env: 'test')]
class AnotherClass
{
// ...
}
// src/Service/MessageGenerator.php
namespace App\Service;
https://symfony.com/doc/current/service_container.html 5/23
27/9/23, 23:37 Service Container (Symfony Docs)
use Psr\Log\LoggerInterface;
class MessageGenerator
{
public function __construct(
private LoggerInterface $logger,
) {
}
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 the autowire: true config in services.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:
https://symfony.com/doc/current/service_container.html 6/23
27/9/23, 23:37 Service Container (Symfony Docs)
Symfony\Component\Routing\RouterInterface (router.default)
[...]
// src/Service/SiteUpdateManager.php
namespace App\Service;
use App\Service\MessageGenerator;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
class SiteUpdateManager
{
public function __construct(
private MessageGenerator $messageGenerator,
private MailerInterface $mailer,
) {
}
$this->mailer->send($email);
// ...
return true;
}
}
https://symfony.com/doc/current/service_container.html 7/23
27/9/23, 23:37 Service Container (Symfony Docs)
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:
// src/Controller/SiteController.php
namespace App\Controller;
use App\Service\SiteUpdateManager;
// ...
if ($siteUpdateManager->notifyOfSiteUpdate()) {
$this->addFlash('success', 'Notification mail was sent successfully.');
}
// ...
}
}
Thanks to autowiring and your type-hints in __construct(), the container creates the SiteUpdateManager
object and passes it the correct argument. In most cases, this works perfectly.
// src/Service/SiteUpdateManager.php
// ...
class SiteUpdateManager
{
// ...
+ private string $adminEmail;
https://symfony.com/doc/current/service_container.html 8/23
27/9/23, 23:37 Service Container (Symfony Docs)
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:
# config/services.yaml
services:
# ... same as before
# same as before
App\:
resource: '../src/'
exclude: '../src/{DependencyInjection,Entity,Kernel.php}'
https://symfony.com/doc/current/service_container.html 9/23
27/9/23, 23:37 Service Container (Symfony Docs)
arguments:
$adminEmail: '[email protected]'
Thanks to this, the container will pass [email protected] 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 service() function:
# config/services.yaml
services:
App\Service\MessageGenerator:
arguments:
# this is not a string, but a reference to a service called 'logger'
- '@logger'
# if the value of a string argument starts with '@', you need to escape
# it by adding another '@' so Symfony doesn't consider it a service
# the following example would be parsed as the string '@securepassword'
# - '@@securepassword'
Working with container parameters is straightforward using the container's accessor methods for
parameters:
https://symfony.com/doc/current/service_container.html 10/23
27/9/23, 23:37 Service Container (Symfony Docs)
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.
// src/Service/MessageGenerator.php
namespace App\Service;
use Psr\Log\LoggerInterface;
class MessageGenerator
{
public function __construct(
private LoggerInterface $logger,
) {
}
// ...
}
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 Defining Services Dependencies Automatically (Autowiring)). But, you
can control this and pass in a different logger:
https://symfony.com/doc/current/service_container.html 11/23
27/9/23, 23:37 Service Container (Symfony Docs)
# config/services.yaml
services:
# ... same code as before
This tells the container that the $logger argument to __construct should use service whose id is
monolog.logger.request.
For a list of possible logger services that can be used with autowiring, run:
Remove Services
A service can be removed from the service container if needed. This is useful for example to make a service
unavailable in some configuration environment (e.g. in the test environment):
// config/services_test.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\RemovedService;
https://symfony.com/doc/current/service_container.html 12/23
27/9/23, 23:37 Service Container (Symfony Docs)
$services->remove(RemovedService::class);
};
Now, the container will not contain the App\RemovedService in the test environment.
// src/Service/MessageGenerator.php
namespace App\Service;
use Psr\Log\LoggerInterface;
class MessageGenerator
{
private string $messageHash;
Now, we would add a new invokable service to generate the message hash:
// src/Hash/MessageHashGenerator.php
namespace App\Hash;
class MessageHashGenerator
{
public function __invoke(): string
{
// Compute and return a message hash
https://symfony.com/doc/current/service_container.html 13/23
27/9/23, 23:37 Service Container (Symfony Docs)
}
}
# config/services.yaml
services:
# ... same code as before
See also
Closures can be injected by using autowiring and its dedicated attributes.
# config/services.yaml
services:
_defaults:
bind:
# pass this value to any $adminEmail argument for any service
# that's defined in this file (including controller arguments)
$adminEmail: '[email protected]'
https://symfony.com/doc/current/service_container.html 14/23
27/9/23, 23:37 Service Container (Symfony Docs)
# optionally you can define both the name and type of the argument to match
string $adminEmail: '[email protected]'
Psr\Log\LoggerInterface $requestLogger: '@monolog.logger.request'
iterable $rules: !tagged_iterator app.foo.rule
# ...
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. Service
Container).
In those cases, you can use the abstract argument type to define at least the name of the argument and
some short description about its purpose:
# config/services.yaml
services:
# ...
App\Service\MyService:
arguments:
$rootNamespace: !abstract 'should be defined by Pass'
# ...
If you don't replace the value of an abstract argument during runtime, a RuntimeException will be thrown
with a message like Argument "$rootNamespace" of service "App\Service\MyService" is abstract:
should be defined by Pass.
https://symfony.com/doc/current/service_container.html 15/23
27/9/23, 23:37 Service Container (Symfony Docs)
For more details about autowiring, check out Defining Services Dependencies Automatically (Autowiring).
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.
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.
https://symfony.com/doc/current/service_container.html 16/23
27/9/23, 23:37 Service Container (Symfony Docs)
If you need to fetch services lazily, instead of using public services you should consider using a service
locator.
But, if you do need to make a service public, override the public setting:
# config/services.yaml
services:
# ... same code as before
# config/services.yaml
services:
# ... same as before
Tip
The value of the resource and exclude options can be any valid glob pattern. If you want to exclude
only a few services, you may use the Exclude attribute directly on your class to exclude it.
https://symfony.com/doc/current/service_container.html 17/23
27/9/23, 23:37 Service Container (Symfony Docs)
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 Service Container). 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.
# config/services.yaml
services:
App\Domain\:
resource: '../src/Domain/*'
# ...
In order to have multiple definitions, add the namespace option and use any unique string as the key of
each service config:
# config/services.yaml
services:
command_handlers:
namespace: App\Domain\
resource: '../src/Domain/*/CommandHandler'
tags: [command_handler]
https://symfony.com/doc/current/service_container.html 18/23
27/9/23, 23:37 Service Container (Symfony Docs)
event_subscribers:
namespace: App\Domain\
resource: '../src/Domain/*/EventSubscriber'
tags: [event_subscriber]
# config/services.yaml
services:
# ...
site_update_manager.normal_users:
class: App\Service\SiteUpdateManager
autowire: false
arguments:
- '@App\Service\MessageGenerator'
- '@mailer'
- '[email protected]'
https://symfony.com/doc/current/service_container.html 19/23
27/9/23, 23:37 Service Container (Symfony Docs)
If you want to pass the second, you'll need to manually wire the service or to create a named
ref:`autowiring alias <autowiring-alias>`.
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.
When using PHP closures to configure your services, it is possible to automatically inject the current
environment value by adding a string argument named $env to the closure:
// config/packages/my_config.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
The AutowireCallable attribute can be used to generate an adapter for a functional interface. Let's say you
have the following functional interface:
// src/Service/MessageFormatterInterface.php
namespace App\Service;
interface MessageFormatterInterface
{
public function format(string $message, array $parameters): string;
}
https://symfony.com/doc/current/service_container.html 20/23
27/9/23, 23:37 Service Container (Symfony Docs)
You also have a service that defines many methods and one of them is the same format() method of the
previous interface:
// src/Service/MessageFormatterInterface.php
namespace App\Service;
class MessageUtils
{
// other methods...
Thanks to the #[AutowireCallable] attribute, you can now inject this MessageUtils service as a functional
interface implementation:
namespace App\Service\Mail;
use App\Service\MessageFormatterInterface;
use App\Service\MessageUtils;
use Symfony\Component\DependencyInjection\Attribute\AutowireCallable;
class Mailer
{
public function __construct(
#[AutowireCallable(service: MessageUtils::class, method: 'formatMessage')]
private MessageFormatterInterface $formatter
) {
}
// ...
}
}
https://symfony.com/doc/current/service_container.html 21/23
27/9/23, 23:37 Service Container (Symfony Docs)
Instead of using the #[AutowireCallable] attribute, you can also generate an adapter for a functional
interface through configuration:
# config/services.yaml
services:
# ...
app.message_formatter:
class: App\Service\MessageFormatterInterface
from_callable: [!service {class: 'App\Service\MessageUtils'}, 'formatMessage']
By doing so, Symfony will generate a class (also called an adapter) implementing
MessageFormatterInterface that will forward calls of MessageFormatterInterface::format() to your
underlying service's method MessageUtils::format(), with all its arguments.
Learn more
# How to Create Service Aliases and Mark Services as Private
# Types of Injection
# Lazy Services
https://symfony.com/doc/current/service_container.html 22/23
27/9/23, 23:37 Service Container (Symfony Docs)
# Service Closures
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
https://symfony.com/doc/current/service_container.html 23/23