Advanced Service Definition Options
Most applications only need the service definition options explained in the main service container article. This article explains the less common options, used for advanced use cases.
Non-Shared Services: Getting a New Instance Every Time
In the service container, all services are shared by default. This means that each time you retrieve the service, you'll get the same instance. This is usually the behavior you want, but in some cases, you might want to always get a new instance.
The typical scenario is a stateful service: a service that stores data related to one specific task in its own properties (e.g. a service that builds a report and accumulates partial results while building it). If that service was shared, all the code using it would read and modify the same data, mixing unrelated tasks.
In order to always get a new instance, set the shared setting to false
in your service definition:
1 2 3 4 5 6 7 8 9 10
// src/SomeNonSharedService.php
namespace App;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
#[Autoconfigure(shared: false)]
class SomeNonSharedService
{
// ...
}
Now, whenever you request the App\SomeNonSharedService from the container,
you will be passed a new instance.
Before making a service non-shared, consider its drawbacks:
- Creating a new instance every time has a performance cost, and that cost grows with the number of dependencies of the service, because they must be resolved and injected on every instantiation;
- The new instance is created each time you ask the container for the service, not each time you use it. If some service receives a non-shared service as a constructor argument, it gets one instance and keeps reusing it during its entire lifetime. To really get a new instance on each call, inject a service closure instead.
Deprecating Services and Aliases
Once you have decided to deprecate the use of a service (because it is outdated or you decided not to maintain it anymore), you can deprecate its definition:
1 2 3 4 5 6
# config/services.yaml
App\Service\OldService:
deprecated:
package: 'vendor-name/package-name'
version: '2.8'
message: The "%%service_id%%" service is deprecated since vendor-name/package-name 2.8 and will be removed in 3.0.
Now, every time this service is used, a deprecation warning is triggered, advising you to stop or to change your uses of that service.
The message is actually a message template, which replaces occurrences of the
%service_id% placeholder by the service's id. You must have at least one
occurrence of the %service_id% placeholder in your template.
Note
The deprecation message is optional. If not set, Symfony will show this default
message: The "%service_id%" service is deprecated. You should stop using it,
as it will soon be removed..
Tip
It is strongly recommended that you define a custom message because the default one is too generic. A good message informs when this service was deprecated, until when it will be maintained and the alternative services to use (if any).
For service decorators, if the definition does not modify the deprecated status, it will inherit the status from the definition that is decorated.
Service aliases can be deprecated in the same way:
1 2 3 4 5 6 7 8 9
# config/services.yaml
app.mailer:
alias: 'App\Mail\PhpMailer'
# this outputs the following generic deprecation message:
# Since acme/package 1.2: The "app.mailer" service alias is deprecated. You should stop using it, as it will be removed in the future
deprecated:
package: 'acme/package'
version: '1.2'
When deprecating aliases, you can also define a custom message and the
%alias_id% placeholder will be replaced by the alias id. You must have
at least one occurrence of the %alias_id% placeholder in your template:
1 2 3 4 5 6 7 8
# config/services.yaml
app.mailer:
alias: 'App\Mail\PhpMailer'
deprecated:
package: 'acme/package'
version: '1.2'
message: 'The "%%alias_id%%" alias is deprecated. Do not use it anymore.'
Removing Service Definitions
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). Removing definitions is only possible when using the
PHP configuration format:
1 2 3 4 5 6 7 8 9 10
// 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.
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 compiler pass or bundle 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:
1 2 3 4 5 6 7 8 9
# 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.
Anonymous Services
In some cases, you may want to prevent a service being used as a dependency of other services. This can be achieved by creating an anonymous service. These services are like regular services but they don't define an ID and they are created where they are used.
The following example shows how to inject an anonymous service into another service:
1 2 3 4 5 6
# config/services.yaml
services:
App\Foo:
arguments:
- !service
class: App\AnonymousBar
Note
Anonymous services do NOT inherit the definitions provided from the
defaults defined in the configuration. So you'll need to explicitly mark
service as autowired or
autoconfigured when doing an anonymous
service e.g.: inline_service(Foo::class)->autowire()->autoconfigure().
Anonymous services can also be used as factories:
1 2 3 4
# config/services.yaml
services:
App\Foo:
factory: [ !service { class: App\FooFactory }, 'constructFoo' ]
Tip
In PHP classes, you can declare and inject anonymous services using the #[AutowireInline] attribute.
Service Configurators
A service configurator is a callable that the container runs on a service right after creating it. Use a configurator when a service needs a complex setup based on settings coming from different sources, or when several services must be configured in the same way at runtime. Unlike a factory, a configurator doesn't create the object; it only prepares an object that was already created.
For example, suppose your application sends different types of emails and that
each email is processed by formatters that can be enabled or disabled depending
on dynamic application settings. The App\Mail\NewsletterSender and
App\Mail\GreetingCardSender classes both implement an
EmailFormatterAwareInterface with a setEnabledFormatters() method, and a
separate App\Mail\EmailFormatterRegistry service knows which formatters are
enabled.
To avoid coupling both sender classes to EmailFormatterRegistry, create a
configurator class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// src/Mail/EmailConfigurator.php
namespace App\Mail;
class EmailConfigurator
{
public function __construct(
private EmailFormatterRegistry $formatterRegistry,
) {
}
public function configure(EmailFormatterAwareInterface $emailSender): void
{
$emailSender->setEnabledFormatters(
$this->formatterRegistry->getEnabledFormatters()
);
}
// ...
}
The EmailConfigurator's job is to inject the enabled formatters into the
sender classes, because they are not aware of where those formatters come from.
On the other hand, EmailFormatterRegistry holds the knowledge about the
enabled formatters and how to load them, keeping the single responsibility
principle.
Tip
While this example uses a PHP class method, configurators can be any valid PHP callable, including functions, static methods and methods of services.
Define the configurator of a service with the configurator option. If you're
using the default services.yaml configuration,
all these classes are already loaded as services, so you only need to set the
configurator option:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# config/services.yaml
services:
# ...
# registers all classes as services, including App\Mail\EmailConfigurator
App\:
resource: '../src/'
# ...
# override the services to set the configurator
App\Mail\NewsletterSender:
configurator: ['@App\Mail\EmailConfigurator', 'configure']
App\Mail\GreetingCardSender:
configurator: ['@App\Mail\EmailConfigurator', 'configure']
When requesting the App\Mail\NewsletterSender or
App\Mail\GreetingCardSender service, the created instance is first passed to
the EmailConfigurator::configure() method.
Tip
If the configurator class defines an __invoke() method, you can omit
the method name and pass only the service:
configurator: '@App\Mail\EmailConfigurator' in YAML and
'configurator' => service(EmailConfigurator::class) in PHP.
Synthetic Services: Injecting Instances into the Container
In some applications, you may need to inject a class instance as service, instead of configuring the container to create a new instance.
For instance, the kernel service in Symfony is injected into the container
from within the Kernel class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// ...
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpKernel\TerminableInterface;
abstract class Kernel implements KernelInterface, TerminableInterface
{
// ...
protected function initializeContainer(): void
{
// ...
$this->container->set('kernel', $this);
// ...
}
}
Services that are set at runtime are called synthetic services. This service
has to be configured so the container knows the service exists during compilation
(otherwise, services depending on kernel will get a "service does not exist" error).
In order to do so, mark the service as synthetic in your service definition configuration:
1 2 3 4 5
# config/services.yaml
services:
# synthetic services don't specify a class
app.synthetic_service:
synthetic: true
Now, you can inject the instance in the container using Container::set():
1 2 3
// instantiate the synthetic service
$theService = ...;
$container->set('app.synthetic_service', $theService);
Parent Services: Managing Common Dependencies
As you add more functionality to your application, you may well start to
have related classes that share some of the same dependencies. For example,
you may have multiple repository classes which need the
doctrine.orm.entity_manager service and an optional logger service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// src/Repository/BaseDoctrineRepository.php
namespace App\Repository;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
// ...
abstract class BaseDoctrineRepository
{
protected LoggerInterface $logger;
public function __construct(
protected EntityManagerInterface $entityManager,
) {
}
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
// ...
}
Your child service classes may look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// src/Repository/DoctrineUserRepository.php
namespace App\Repository;
use App\Repository\BaseDoctrineRepository;
// ...
class DoctrineUserRepository extends BaseDoctrineRepository
{
// ...
}
// src/Repository/DoctrinePostRepository.php
namespace App\Repository;
use App\Repository\BaseDoctrineRepository;
// ...
class DoctrinePostRepository extends BaseDoctrineRepository
{
// ...
}
Note
In most modern applications you don't need this feature: dependencies
shared by several classes are handled by autowiring,
the _defaults and _instanceof options and the #[Autoconfigure]
attribute. Consider those options first and use parent services only when
they don't fit your needs (e.g. when not using autowiring).
The service container allows you to extend parent services in order to avoid duplicated service definitions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# config/services.yaml
services:
App\Repository\BaseDoctrineRepository:
abstract: true
arguments: ['@doctrine.orm.entity_manager']
calls:
- setLogger: ['@logger']
App\Repository\DoctrineUserRepository:
# extend the App\Repository\BaseDoctrineRepository service
parent: App\Repository\BaseDoctrineRepository
App\Repository\DoctrinePostRepository:
parent: App\Repository\BaseDoctrineRepository
# ...
In this context, having a parent service implies that the arguments
and method calls of the parent service should be used for the child services.
Specifically, the entity manager will be injected and setLogger() will
be called when App\Repository\DoctrineUserRepository is instantiated.
All attributes on the parent service are shared with the child except for
shared, abstract and tags. These are not inherited from the parent.
Tip
In the examples shown, the classes sharing the same configuration also extend from the same parent class in PHP. This isn't necessary at all. You can also extract common parts of similar service definitions into a parent service without also extending a parent class in PHP.
Overriding Parent Dependencies
There may be times where you want to override what service is injected for one child service only. You can override most settings by specifying it in the child class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
# config/services.yaml
services:
# ...
App\Repository\DoctrineUserRepository:
parent: App\Repository\BaseDoctrineRepository
# overrides the private setting of the parent service
public: true
# appends the '@app.username_checker' argument to the parent
# argument list
arguments: ['@app.username_checker']
App\Repository\DoctrinePostRepository:
parent: App\Repository\BaseDoctrineRepository
# overrides the first argument (using the special index_N key)
arguments:
index_0: '@doctrine.custom_entity_manager'