Files
archived-symfony-docs/service_container.rst
Javier Eguiluz 3f3f9415a0 Merge branch '6.4' into 7.3
* 6.4:
  Minor tweak
  [DependencyInjection] Add example for `#[Exclude]` attribute
2026-01-16 17:38:25 +01:00

1741 lines
61 KiB
ReStructuredText

Service Container
=================
.. admonition:: Screencast
:class: 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 :doc:`log </logging>` something? No problem::
// 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\Attribute\Route;
class ProductController extends AbstractController
{
#[Route('/products')]
public function list(LoggerInterface $logger): Response
{
$logger->info('Look, I just used a service!');
// ...
}
}
What other services are available? Find out by running:
.. code-block:: terminal
$ php bin/console debug:autowiring
# this is just a *small* sample of the output...
Autowirable Types
=================
The following classes & interfaces can be used as type-hints when autowiring:
Describes a logger instance.
Psr\Log\LoggerInterface - alias:logger
Request stack that controls the lifecycle of requests.
Symfony\Component\HttpFoundation\RequestStack - alias:request_stack
RouterInterface is the interface that all Router classes must implement.
Symfony\Component\Routing\RouterInterface - alias:router.default
[...]
When you use these type-hints in your controller methods or inside your
:ref:`own services <service-container-creating-service>`, 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 :ref:`how to choose a specific service
<services-wire-specific-service>`. See :doc:`/service_container/debug`.
.. _service-container-creating-service:
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::
// 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\Attribute\Route;
class ProductController extends AbstractController
{
#[Route('/products/new')]
public function new(MessageGenerator $messageGenerator): Response
{
// thanks to the type-hint, the container will instantiate a
// new MessageGenerator and pass it to you!
// ...
$message = $messageGenerator->getHappyMessage();
$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.
.. _service-container-services-load-example:
.. sidebar:: 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:
.. configuration-block::
.. code-block:: yaml
# 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 subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
# order is important in this file because service definitions
# always *replace* previous ones; add your own service configuration below
# ...
.. code-block:: xml
<!-- 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
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<!-- Default configuration for services in *this* file -->
<defaults autowire="true" autoconfigure="true"/>
<!-- makes classes in src/ available to be used as services -->
<!-- this creates a service per class whose id is the fully-qualified class name -->
<prototype namespace="App\" resource="../src/"/>
<!-- order is important in this file because service definitions
always *replace* previous ones; add your own service configuration below -->
<!-- ... -->
</services>
</container>
.. code-block:: php
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return function(ContainerConfigurator $container): void {
// default configuration for services in *this* file
$services = $container->services()
->defaults()
->autowire() // Automatically injects dependencies in your services.
->autoconfigure() // Automatically registers your services as commands, event subscribers, etc.
;
// makes classes in src/ available to be used as services
// this creates a service per class whose id is the fully-qualified class name
$services->load('App\\', '../src/');
// order is important in this file because service definitions
// always *replace* previous ones; add your own service configuration below
};
.. tip::
The value of the ``resource`` option can be any valid `glob pattern`_.
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 how to :ref:`import many services at once
<service-psr4-loader>` with resource.
If some files or directories in your project should not become services, you
can exclude them using the ``exclude`` option:
.. configuration-block::
.. code-block:: yaml
# config/services.yaml
services:
# ...
App\:
resource: '../src/'
exclude:
- '../src/SomeDirectory/'
- '../src/AnotherDirectory/'
- '../src/SomeFile.php'
.. code-block:: xml
<!-- 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
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<prototype namespace="App\" resource="../src/" exclude="../src/{SomeDirectory,AnotherDirectory,Kernel.php}"/>
<!-- ... -->
</services>
</container>
.. code-block:: php
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return function(ContainerConfigurator $container): void {
// ...
$services->load('App\\', '../src/')
->exclude('../src/{SomeDirectory,AnotherDirectory,Kernel.php}');
};
If you'd prefer to manually wire your service, you can
:ref:`use explicit configuration <services-explicitly-configure-wire-services>`.
.. _service-container_limiting-to-env:
Limiting Services to a specific Symfony Environment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can use the ``#[When]`` attribute to only register the class
as a service in some environments::
use Symfony\Component\DependencyInjection\Attribute\When;
// SomeClass is only registered in the "dev" environment
#[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
{
// ...
}
If you want to exclude a service from being registered in a specific
environment, you can use the ``#[WhenNot]`` attribute::
use Symfony\Component\DependencyInjection\Attribute\WhenNot;
// SomeClass is registered in all environments except "dev"
#[WhenNot(env: 'dev')]
class SomeClass
{
// ...
}
// you can apply more than one WhenNot attribute to the same class
#[WhenNot(env: 'dev')]
#[WhenNot(env: 'test')]
class AnotherClass
{
// ...
}
.. versionadded:: 7.2
The ``#[WhenNot]`` attribute was introduced in Symfony 7.2.
.. _services-constructor-injection:
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 ``$logger`` argument that has
the ``LoggerInterface`` type-hint. Set this on a new ``$logger`` property
and use it later::
// src/Service/MessageGenerator.php
namespace App\Service;
use Psr\Log\LoggerInterface;
class MessageGenerator
{
public function __construct(
private LoggerInterface $logger,
) {
}
public function getHappyMessage(): string
{
$this->logger->info('About to find a happy message!');
// ...
}
}
That's it! The container will *automatically* know to pass the ``logger`` service
when instantiating the ``MessageGenerator``. How does it know to do this?
:ref:`Autowiring <services-autowire>`. 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*.
.. _services-debug-container-types:
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:
.. code-block:: terminal
$ php bin/console debug:autowiring
# this is just a *small* sample of the output...
Describes a logger instance.
Psr\Log\LoggerInterface - alias:monolog.logger
Request stack that controls the lifecycle of requests.
Symfony\Component\HttpFoundation\RequestStack - alias:request_stack
RouterInterface is the interface that all Router classes must implement.
Symfony\Component\Routing\RouterInterface - alias:router.default
[...]
In addition to injecting services, you can also pass scalar values and collections
as arguments of other services:
.. configuration-block::
.. code-block:: yaml
# config/services.yaml
services:
App\Service\SomeService:
arguments:
# string, numeric and boolean arguments can be passed "as is"
- 'Foo'
- true
- 7
- 3.14
# constants can be built-in, user-defined, or Enums
- !php/const E_ALL
- !php/const PDO::FETCH_NUM
- !php/const Symfony\Component\HttpKernel\Kernel::VERSION
- !php/const App\Config\SomeEnum::SomeCase
# when not using autowiring, you can pass service arguments explicitly
- '@some-service-id' # the leading '@' tells this is a service ID, not a string
- '@?some-service-id' # using '?' means to pass null if service doesn't exist
# binary contents are passed encoded as base64 strings
- !!binary VGhpcyBpcyBhIEJlbGwgY2hhciAH
# collections (arrays) can include any type of argument
-
first: !php/const true
second: 'Foo'
.. code-block:: xml
<!-- 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"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<services>
<service id="App\Service\SomeService">
<!-- arguments without a type can be strings or numbers -->
<argument>Foo</argument>
<argument>7</argument>
<argument>3.14</argument>
<!-- explicitly declare a string argument -->
<argument type="string">Foo</argument>
<!-- booleans are passed as constants -->
<argument type="constant">true</argument>
<!-- constants can be built-in, user-defined, or Enums -->
<argument type="constant">E_ALL</argument>
<argument type="constant">PDO::FETCH_NUM</argument>
<argument type="constant">Symfony\Component\HttpKernel\Kernel::VERSION</argument>
<argument type="constant">App\Config\SomeEnum::SomeCase</argument>
<!-- when not using autowiring, you can pass service arguments explicitly -->
<argument type="service"
id="some-service-id"
on-invalid="dependency_injection-ignore"/>
<!-- binary contents are passed encoded as base64 strings -->
<argument type="binary">VGhpcyBpcyBhIEJlbGwgY2hhciAH</argument>
<!-- collections (arrays) can include any type of argument -->
<argument type="collection">
<argument key="first" type="constant">true</argument>
<argument key="second" type="string">Foo</argument>
</argument>
</service>
<!-- ... -->
</services>
</container>
.. code-block:: php
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
return static function (ContainerConfigurator $container) {
$services = $container->services();
$services->set(App\Service\SomeService::class)
// string, numeric and boolean arguments can be passed "as is"
->arg(0, 'Foo')
->arg(1, true)
->arg(2, 7)
->arg(3, 3.14)
// constants: built-in, user-defined, or Enums
->arg(4, E_ALL)
->arg(5, \PDO::FETCH_NUM)
->arg(6, Symfony\Component\HttpKernel\Kernel::VERSION)
->arg(7, App\Config\SomeEnum::SomeCase)
// when not using autowiring, you can pass service arguments explicitly
->arg(8, service('some-service-id')) # fails if service doesn't exist
# passes null if service doesn't exist
->arg(9, new Reference('some-service-id', Reference::IGNORE_ON_INVALID_REFERENCE))
// collection with mixed argument types
->arg(10, [
'first' => true,
'second' => 'Foo',
]);
// ...
};
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::
// 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,
) {
}
public function notifyOfSiteUpdate(): bool
{
$happyMessage = $this->messageGenerator->getHappyMessage();
$email = (new Email())
->from('admin@example.com')
->to('manager@example.com')
->subject('Site update just happened!')
->text('Someone just updated the site. We told them: '.$happyMessage);
$this->mailer->send($email);
// ...
return true;
}
}
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;
// ...
class SiteController extends AbstractController
{
public function new(SiteUpdateManager $siteUpdateManager): Response
{
// ...
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.
.. _services-manually-wire-args:
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:
.. code-block:: diff
// src/Service/SiteUpdateManager.php
// ...
class SiteUpdateManager
{
// ...
public function __construct(
private MessageGenerator $messageGenerator,
private MailerInterface $mailer,
+ private string $adminEmail
) {
}
public function notifyOfSiteUpdate(): bool
{
// ...
$email = (new Email())
// ...
- ->to('manager@example.com')
+ ->to($this->adminEmail)
// ...
;
// ...
}
}
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:
.. configuration-block::
.. code-block:: yaml
# config/services.yaml
services:
# ... same as before
# same as before
App\:
resource: '../src/'
exclude: '../src/{DependencyInjection,Entity,Kernel.php}'
# explicitly configure the service
App\Service\SiteUpdateManager:
arguments:
$adminEmail: 'manager@example.com'
.. code-block:: xml
<!-- 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
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<!-- ... same as before -->
<!-- Same as before -->
<prototype namespace="App\"
resource="../src/"
exclude="../src/{DependencyInjection,Entity,Kernel.php}"
/>
<!-- Explicitly configure the service -->
<service id="App\Service\SiteUpdateManager">
<argument key="$adminEmail">manager@example.com</argument>
</service>
</services>
</container>
.. code-block:: php
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\Service\SiteUpdateManager;
return function(ContainerConfigurator $container): void {
// ...
// same as before
$services->load('App\\', '../src/')
->exclude('../src/{DependencyInjection,Entity,Kernel.php}');
$services->set(SiteUpdateManager::class)
->arg('$adminEmail', 'manager@example.com')
;
};
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-container-parameters:
Service Parameters
------------------
In addition to holding service objects, the container also holds configuration,
called **parameters**. The main article about Symfony configuration explains the
:ref:`configuration parameters <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:
.. configuration-block::
.. code-block:: yaml
# 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'
.. code-block:: xml
<!-- 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
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="App\Service\MessageGenerator">
<argument type="service" id="logger"/>
</service>
</services>
</container>
.. code-block:: php
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\Service\MessageGenerator;
return function(ContainerConfigurator $container): void {
$services = $container->services();
$services->set(MessageGenerator::class)
->args([service('logger')])
;
};
Working with container parameters is straightforward using the container's
accessor methods for parameters::
// checks if a parameter is defined (parameter names are case-sensitive)
$container->hasParameter('mailer.transport');
// gets value of a parameter
$container->getParameter('mailer.transport');
// adds a new parameter
$container->setParameter('mailer.transport', 'sendmail');
.. warning::
The used ``.`` notation is a
:ref:`Symfony convention <service-naming-conventions>` 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
:doc:`/components/dependency_injection/compilation`.
.. _services-wire-specific-service:
Choose a Specific Service
-------------------------
The ``MessageGenerator`` service created earlier requires a ``LoggerInterface`` argument::
// 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 :ref:`service-autowiring-alias`).
But, you can control this and pass in a different logger:
.. configuration-block::
.. code-block:: yaml
# config/services.yaml
services:
# ... same code as before
# explicitly configure the service
App\Service\MessageGenerator:
arguments:
# the '@' symbol is important: that's what tells the container
# you want to pass the *service* whose id is 'monolog.logger.request',
# and not just the *string* 'monolog.logger.request'
$logger: '@monolog.logger.request'
.. code-block:: xml
<!-- 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
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<!-- ... same code as before -->
<!-- Explicitly configure the service -->
<service id="App\Service\MessageGenerator">
<argument key="$logger" type="service" id="monolog.logger.request"/>
</service>
</services>
</container>
.. code-block:: php
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\Service\MessageGenerator;
return function(ContainerConfigurator $container): void {
// ... same code as before
// explicitly configure the service
$services->set(MessageGenerator::class)
->arg('$logger', service('monolog.logger.request'))
;
};
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:
.. code-block:: terminal
$ php bin/console debug:autowiring logger
.. _container-debug-container:
For a full list of *all* possible services in the container, run:
.. code-block:: terminal
$ php bin/console debug:container
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 :ref:`configuration environment <configuration-environments>`
(e.g. in the ``test`` environment):
.. configuration-block::
.. code-block:: php
// config/services_test.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\RemovedService;
return function(ContainerConfigurator $containerConfigurator) {
$services = $containerConfigurator->services();
$services->remove(RemovedService::class);
};
Now, the container will not contain the ``App\RemovedService`` in the ``test``
environment.
.. _container_closure-as-argument:
Injecting a Closure as an Argument
----------------------------------
It is possible to inject a callable as an argument of a service.
Let's add an argument to our ``MessageGenerator`` constructor::
// src/Service/MessageGenerator.php
namespace App\Service;
use Psr\Log\LoggerInterface;
class MessageGenerator
{
private string $messageHash;
public function __construct(
private LoggerInterface $logger,
callable $generateMessageHash,
) {
$this->messageHash = $generateMessageHash();
}
// ...
}
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
}
}
Our configuration looks like this:
.. configuration-block::
.. code-block:: yaml
# config/services.yaml
services:
# ... same code as before
# explicitly configure the service
App\Service\MessageGenerator:
arguments:
$logger: '@monolog.logger.request'
$generateMessageHash: !closure '@App\Hash\MessageHashGenerator'
.. code-block:: xml
<!-- 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
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<!-- ... same code as before -->
<!-- Explicitly configure the service -->
<service id="App\Service\MessageGenerator">
<argument key="$logger" type="service" id="monolog.logger.request"/>
<argument key="$generateMessageHash" type="closure" id="App\Hash\MessageHashGenerator"/>
</service>
</services>
</container>
.. code-block:: php
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\Service\MessageGenerator;
return function(ContainerConfigurator $containerConfigurator): void {
// ... same code as before
// explicitly configure the service
$services->set(MessageGenerator::class)
->arg('$logger', service('monolog.logger.request'))
->arg('$generateMessageHash', closure('App\Hash\MessageHashGenerator'))
;
};
.. seealso::
Closures can be injected :ref:`by using autowiring <autowiring_closures>`
and its dedicated attributes.
.. _services-binding:
Binding Arguments by Name or Type
---------------------------------
You can also use the ``bind`` keyword to bind specific arguments by name or type:
.. configuration-block::
.. code-block:: yaml
# 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: 'manager@example.com'
# pass this service to any $requestLogger argument for any
# service that's defined in this file
$requestLogger: '@monolog.logger.request'
# pass this service for any LoggerInterface type-hint for any
# service that's defined in this file
Psr\Log\LoggerInterface: '@monolog.logger.request'
# optionally you can define both the name and type of the argument to match
string $adminEmail: 'manager@example.com'
Psr\Log\LoggerInterface $requestLogger: '@monolog.logger.request'
iterable $rules: !tagged_iterator app.foo.rule
# ...
.. code-block:: xml
<!-- 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
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults autowire="true" autoconfigure="true" public="false">
<bind key="$adminEmail">manager@example.com</bind>
<bind key="$requestLogger"
type="service"
id="monolog.logger.request"
/>
<bind key="Psr\Log\LoggerInterface"
type="service"
id="monolog.logger.request"
/>
<!-- optionally you can define both the name and type of the argument to match -->
<bind key="string $adminEmail">manager@example.com</bind>
<bind key="Psr\Log\LoggerInterface $requestLogger"
type="service"
id="monolog.logger.request"
/>
<bind key="iterable $rules"
type="tagged_iterator"
tag="app.foo.rule"
/>
</defaults>
<!-- ... -->
</services>
</container>
.. code-block:: php
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Psr\Log\LoggerInterface;
return function(ContainerConfigurator $container): void {
$services = $container->services()
->defaults()
// pass this value to any $adminEmail argument for any service
// that's defined in this file (including controller arguments)
->bind('$adminEmail', 'manager@example.com')
// pass this service to any $requestLogger argument for any
// service that's defined in this file
->bind('$requestLogger', service('monolog.logger.request'))
// pass this service for any LoggerInterface type-hint for any
// service that's defined in this file
->bind(LoggerInterface::class, service('monolog.logger.request'))
// optionally you can define both the name and type of the argument to match
->bind('string $adminEmail', 'manager@example.com')
->bind(LoggerInterface::class.' $requestLogger', service('monolog.logger.request'))
->bind('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
:ref:`loading many services at once <service-psr4-loader>`).
Abstract Service Arguments
--------------------------
Sometimes, the values of some service arguments can't be defined in the
configuration files because they are calculated at runtime using a
:doc:`compiler pass </service_container/compiler_passes>`
or :doc:`bundle extension </bundles/extension>`.
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:
.. configuration-block::
.. code-block:: yaml
# config/services.yaml
services:
# ...
App\Service\MyService:
arguments:
$rootNamespace: !abstract 'should be defined by Pass'
# ...
.. code-block:: xml
<!-- 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
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="App\Service\MyService" class="App\Service\MyService">
<argument key="$rootNamespace" type="abstract">should be defined by Pass</argument>
</service>
<!-- ... -->
</services>
</container>
.. code-block:: php
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\Service\MyService;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
return function(ContainerConfigurator $container) {
$services = $container->services();
$services->set(MyService::class)
->arg('$rootNamespace', abstract_arg('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.``
.. _services-autowire:
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 :doc:`/service_container/autowiring`.
.. _services-autoconfigure:
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 :doc:`tag </service_container/tags>` it with ``twig.extension``.
But, with ``autoconfigure: true``, you don't need the tag. In fact, if you're using
the :ref:`default services.yaml config <service-container-services-load-example>`,
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.
Autoconfiguration also works with attributes. Some attributes like
:class:`Symfony\\Component\\Messenger\\Attribute\\AsMessageHandler`,
:class:`Symfony\\Component\\EventDispatcher\\Attribute\\AsEventListener` and
:class:`Symfony\\Component\\Console\\Attribute\\AsCommand` are registered
for autoconfiguration. Any class using these attributes will have tags applied
to them.
Linting Service Definitions
---------------------------
The ``lint:container`` command performs additional checks to ensure the container
is properly configured. It is useful to run this command before deploying your
application to production (e.g. in your continuous integration server):
.. code-block:: terminal
$ php bin/console lint:container
# optionally, you can force the resolution of environment variables;
# the command will fail if any of those environment variables are missing
$ php bin/console lint:container --resolve-env-vars
.. versionadded:: 7.2
The ``--resolve-env-vars`` option was introduced in Symfony 7.2.
Performing those checks whenever the container is compiled can hurt performance.
That's why they are implemented in :doc:`compiler passes </service_container/compiler_passes>`
called ``CheckTypeDeclarationsPass`` and ``CheckAliasValidityPass``, which are
disabled by default and enabled only when executing the ``lint:container`` command.
If you don't mind the performance loss, you can enable these compiler passes in
your application.
.. versionadded:: 7.1
The ``CheckAliasValidityPass`` compiler pass was introduced in Symfony 7.1.
.. _container-public:
Public Versus Private Services
------------------------------
Every service defined is private by default. When a service is private, you
cannot access it directly from the container using ``$container->get()``. As a
best practice, you should only create *private* services and you should fetch
services using dependency injection instead of using ``$container->get()``.
If you need to fetch services lazily, instead of using public services you
should consider using a :ref:`service locator <service-locators>`.
But, if you *do* need to make a service public, override the ``public``
setting:
.. configuration-block::
.. code-block:: yaml
# config/services.yaml
services:
# ... same code as before
# explicitly configure the service
App\Service\PublicService:
public: true
.. code-block:: xml
<!-- 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
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<!-- ... same code as before -->
<!-- Explicitly configure the service -->
<service id="App\Service\PublicService" public="true"></service>
</services>
</container>
.. code-block:: php
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\Service\PublicService;
return function(ContainerConfigurator $container): void {
// ... same as code before
// explicitly configure the service
$services->set(Service\PublicService::class)
->public()
;
};
It is also possible to define a service as public thanks to the ``#[Autoconfigure]``
attribute. This attribute must be used directly on the class of the service
you want to configure::
// src/Service/PublicService.php
namespace App\Service;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
#[Autoconfigure(public: true)]
class PublicService
{
// ...
}
.. _service-psr4-loader:
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:
.. configuration-block::
.. code-block:: yaml
# config/services.yaml
services:
# ... same as before
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude: '../src/{DependencyInjection,Entity,Kernel.php}'
.. code-block:: xml
<!-- 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
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<!-- ... same as before -->
<prototype namespace="App\" resource="../src/" exclude="../src/{DependencyInjection,Entity,Kernel.php}"/>
</services>
</container>
.. code-block:: php
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return function(ContainerConfigurator $container): void {
// ...
// makes classes in src/ available to be used as services
// this creates a service per class whose id is the fully-qualified class name
$services->load('App\\', '../src/')
->exclude('../src/{DependencyInjection,Entity,Kernel.php}');
};
.. 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 :class:`Symfony\\Component\\DependencyInjection\\Attribute\\Exclude`
attribute directly on your class to exclude it::
// src/Service/SomeService.php
namespace App\Service;
#[Exclude]
class SomeService
{
// ...
}
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 :ref:`how to manually wire arguments <services-manually-wire-args>`).
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 :ref:`private <container-public>`, 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:
.. configuration-block::
.. code-block:: yaml
# config/services.yaml
services:
App\Domain\:
resource: '../src/Domain/*'
# ...
.. code-block:: xml
<!-- 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
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<prototype namespace="App\Domain"
resource="../src/App/Domain/*"/>
<!-- ... -->
</services>
</container>
.. code-block:: php
// config/services.php
use Symfony\Component\DependencyInjection\Definition;
$defaults = new Definition();
// $this is a reference to the current loader
$this->registerClasses(
$defaults,
'App\\Domain\\',
'../src/App/Domain/*'
);
// ...
In order to have multiple definitions, add the ``namespace`` option and use any
unique string as the key of each service config:
.. code-block:: yaml
# config/services.yaml
services:
command_handlers:
namespace: App\Domain\
resource: '../src/Domain/*/CommandHandler'
tags: [command_handler]
event_subscribers:
namespace: App\Domain\
resource: '../src/Domain/*/EventSubscriber'
tags: [event_subscriber]
.. _services-explicitly-configure-wire-services:
Explicitly Configuring Services and Arguments
---------------------------------------------
:ref:`Loading services automatically <service-container-services-load-example>`
and :ref:`autowiring <services-autowire>` 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:
.. configuration-block::
.. code-block:: yaml
# config/services.yaml
services:
# ...
# this is the service's id
site_update_manager.superadmin:
class: App\Service\SiteUpdateManager
# you CAN still use autowiring: we just want to show what it looks like without
autowire: false
# manually wire all arguments
arguments:
- '@App\Service\MessageGenerator'
- '@mailer'
- 'superadmin@example.com'
site_update_manager.normal_users:
class: App\Service\SiteUpdateManager
autowire: false
arguments:
- '@App\Service\MessageGenerator'
- '@mailer'
- 'contact@example.com'
# Create an alias, so that - by default - if you type-hint SiteUpdateManager,
# the site_update_manager.superadmin will be used
App\Service\SiteUpdateManager: '@site_update_manager.superadmin'
.. code-block:: xml
<!-- 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
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<!-- ... -->
<service id="site_update_manager.superadmin" class="App\Service\SiteUpdateManager" autowire="false">
<argument type="service" id="App\Service\MessageGenerator"/>
<argument type="service" id="mailer"/>
<argument>superadmin@example.com</argument>
</service>
<service id="site_update_manager.normal_users" class="App\Service\SiteUpdateManager" autowire="false">
<argument type="service" id="App\Service\MessageGenerator"/>
<argument type="service" id="mailer"/>
<argument>contact@example.com</argument>
</service>
<service id="App\Service\SiteUpdateManager" alias="site_update_manager.superadmin"/>
</services>
</container>
.. code-block:: php
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\Service\MessageGenerator;
use App\Service\SiteUpdateManager;
return function(ContainerConfigurator $container): void {
// ...
// site_update_manager.superadmin is the service's id
$services->set('site_update_manager.superadmin', SiteUpdateManager::class)
// you CAN still use autowiring: we just want to show what it looks like without
->autowire(false)
// manually wire all arguments
->args([
service(MessageGenerator::class),
service('mailer'),
'superadmin@example.com',
]);
$services->set('site_update_manager.normal_users', SiteUpdateManager::class)
->autowire(false)
->args([
service(MessageGenerator::class),
service('mailer'),
'contact@example.com',
]);
// Create an alias, so that - by default - if you type-hint SiteUpdateManager,
// the site_update_manager.superadmin will be used
$services->alias(SiteUpdateManager::class, 'site_update_manager.superadmin');
};
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 :ref:`manually wire the service <services-wire-specific-service>`
or to create a named :ref:`autowiring alias <autowiring-alias>`.
.. warning::
If you do *not* create the alias and are :ref:`loading all services from src/ <service-container-services-load-example>`,
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;
return function(ContainerConfigurator $containerConfigurator, string $env): void {
// `$env` is automatically filled in, so you can configure your
// services depending on which environment you're on
};
Generating Adapters for Functional Interfaces
---------------------------------------------
Functional interfaces are interfaces with a single method.
They are conceptually very similar to a closure except that their only method
has a name. Moreover, they can be used as type-hints across your code.
The :class:`Symfony\\Component\\DependencyInjection\\Attribute\\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;
}
You also have a service that defines many methods and one of them is the same
``format()`` method of the previous interface::
// src/Service/MessageUtils.php
namespace App\Service;
class MessageUtils
{
// other methods...
public function format(string $message, array $parameters): string
{
// ...
}
}
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: 'format')]
private MessageFormatterInterface $formatter
) {
}
public function sendMail(string $message, array $parameters): string
{
$formattedMessage = $this->formatter->format($message, $parameters);
// ...
}
}
Instead of using the ``#[AutowireCallable]`` attribute, you can also generate
an adapter for a functional interface through configuration:
.. configuration-block::
.. code-block:: yaml
# config/services.yaml
services:
# ...
app.message_formatter:
class: App\Service\MessageFormatterInterface
from_callable: [!service {class: 'App\Service\MessageUtils'}, 'format']
.. code-block:: xml
<!-- 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
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<!-- ... -->
<service id="app.message_formatter" class="App\Service\MessageFormatterInterface">
<from-callable method="format">
<service class="App\Service\MessageUtils"/>
</from-callable>
</service>
</services>
</container>
.. code-block:: php
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\Service\MessageFormatterInterface;
use App\Service\MessageUtils;
return function(ContainerConfigurator $container) {
// ...
$container
->set('app.message_formatter', MessageFormatterInterface::class)
->fromCallable([inline_service(MessageUtils::class), 'format'])
->alias(MessageFormatterInterface::class, 'app.message_formatter')
;
};
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
----------
.. toctree::
:maxdepth: 1
:glob:
/service_container/*
.. _`glob pattern`: https://en.wikipedia.org/wiki/Glob_(programming)
.. _`Symfony Fundamentals screencast series`: https://symfonycasts.com/screencast/symfony-fundamentals