The Service Container is the key feature that makes Symfony applications so
fast and flexible. In Symfony 6.3 we've improved it with a lot of new features.
New Options for Autowire
Attribute
Contributed by Aleksey Polyvanyi
in #48147.
The Autowire attibute was introduced in Symfony 6.1 and allows to autowire
services, parameters and expressions. In Symfony 6.3, it can also autowire environment
variables (via the env
option). Also, parameters are now autowired using the
new param
option:
// src/Service/MessageGenerator.php
namespace App\Service;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
class MessageGenerator
{
public function __construct(
// ...
// when using 'param', you don't have to wrap the parameter name with '%'
#[Autowire(param: 'kernel.debug')]
bool $debugMode,
#[Autowire(env: 'SOME_ENV_VAR')]
string $senderName,
) {
}
// ...
}
Configure Aliases with Attributes
Contributed by Alan Poulain
in #49411.
Service aliases allow you to use services using your own custom service ID
instead of the original ID given to the service. In Symfony 6.3 we're adding a
new #[AsAlias]
attribute so you can define aliases directly in your code:
// src/Mail/PhpMailer.php
namespace App\Mail;
// ...
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
#[AsAlias(id: 'app.mailer', public: true)]
class PhpMailer
{
// ...
}
When using #[AsAlias]
attribute, you may omit passing id
argument if the
service class implements exactly one interface. In those cases, the FQCN of the
interface will be used as the alias.
New Options for Autoconfigure
Attribute
Contributed by Alexandre Daubois
in #49665.
When using some class as its own service factory, you can use the new
constructor
option of the #[Autoconfigure]
to define the name of the
class method that acts as its constructor:
// src/Email/NewsletterManager.php
namespace App\Email;
// ...
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
#[Autoconfigure(constructor: 'create')]
class NewsletterManager
{
private string $sender;
public static function create(
#[Autowire(param: 'app.newsletter_sender')] string $sender
): self
{
$newsletterManager = new self();
$newsletterManager->sender = $sender;
// ...
return $newsletterManager;
}
// ...
}
Nesting Related Attributes in Autowire
Contributed by Nicolas Grekas
in #48710.
In Symfony 6.3 we've improved the #[Autowire]
attribute so you can nest
other autowiring-related attributes into it. The following example shows this
feature in action using all the available options:
#[AsDecorator(decorates: AsDecoratorFoo::class)]
class AutowireNestedAttributes implements AsDecoratorInterface
{
public function __construct(
#[Autowire([
'decorated' => new MapDecorated(),
'iterator' => new TaggedIterator('foo'),
'locator' => new TaggedLocator('foo'),
'service' => new Autowire(service: 'bar')
])] array $options)
{
}
}
Using Autowiring to generate Closures
Contributed by Nicolas Grekas
in #49628
and #49639.
In the Dependency Injection component, "service closures" are closures that
return a service. They come in handy when dealing with laziness on the consumer side.
In Symfony 6.3, we've added support for autowiring services as closures using
the #[AutowireServiceClosure]
attribute:
#[AutowireServiceClosure('my_service')]
Closure $serviceResolver
This will generate a closure that returns the service my_service
when called.
It's also quite common to have a service accept a closure with a specific
signature as argument. In Symfony 6.3, you can generate such closures using
the #[AutowireCallable]
attribute:
#[AutowireCallable(service: 'my_service', method: 'myMethod')]
Closure $callable
This will generate a closure that will have the same signature as the method, so
that calling it will forward to the myMethod()
on the service my_service
.
This type of closure-injection is not lazy by default: my_service
will be
instantiated when creating the $callable
argument. If you want to make it
lazy, you can use the lazy
option:
#[AutowireCallable(service: 'my_service', method: 'myMethod', lazy: true)]
Closure $callable
This will generate a closure that will instantiate the my_service
service
only when the closure is called.
Generating Adapters for Functional Interfaces
Contributed by Nicolas Grekas
in #49632.
Functional interfaces are interfaces with a single method. They are conceptually
very similar to a closure except that their only method has a name, and they
can be used as type-hints.
The #[AutowireCallable]
attribute can be used to generate an adapter for a
functional interface. For example, if you have the following functional
interface:
interface UriExpanderInterface
{
public function expand(string $uri, array $parameters): string;
}
You can use the #[AutowireCallable]
attribute to generate an adapter for it:
#[AutowireCallable(service: 'my_service', method: 'myMethod')]
UriExpanderInterface $expander
Even if my_service
does not implement UriExpanderInterface
, the
$expander
argument will be an instance of UriExpanderInterface
generated
by Symfony. Calling its expand()
method will forward to the myMethod()
method of the my_service
service.
Support for generating such adapters in YAML, XML or PHP is also provided.
Contributed by Nicolas Grekas
in #49685
and #49836.
The #[Autowire]
attribute can be used to tell how services should be autowired.
In Symfony 6.3, we've added support for autowiring lazy services using the lazy
option:
#[Autowire(lazy: true)]
MyServiceInterface $service
This will generate a lazy service that will be instantiated only when the $service
argument is actually used. This works by generating a proxy class that implements
MyServiceInterface
and forwards all method calls to the actual service.
When targeting an argument with many possible types, you can use the lazy
option with a class-string value to specify which type should be generated:
#[Autowire(lazy: FirstServiceInterface::class)]
FirstServiceInterface|SecondServiceInterface $service
This will generate a proxy class that implements only FirstServiceInterface
.
This feature allows services that do not always consume their dependencies to initialize
them only when actually needed. This can provide a significant performance boost,
particularly with services that are expensive to initialize.
Deprecating Container Parameters
Contributed by Jules Pietri
in #47719.
Another new feature related to the Service Container is that you can now
deprecate parameters. This is useful e.g. for extensions who want to rename
or remove parameters, so they can warn users about this future change before doing it:
public function load(array $configs, ContainerBuilder $containerBuilder)
{
// ...
$containerBuilder->setParameter('acme_demo.database_user', $configs['db_user']);
// the parameter to deprecate must be set before marking it as deprecated
$containerBuilder->deprecateParameter(
'acme_demo.database_user',
'acme/database-package',
'1.3',
// optionally you can set a custom deprecation message
'"acme_demo.database_user" is deprecated, you should configure database credentials with the "acme_demo.database_dsn" parameter instead.'
);
}
Consider using this option if you want to transform your temporary parameters
into build parameters, another new feature introduced in Symfony 6.3.
Exclude Classes with Attributes
Contributed by Grégoire Pineau
in #49492.
When configuring services in the container, you can use the exclude
option
to tell Symfony to not create services to one or more classes. This option comes
in handy when excluding entire directories (e.g. src/Entity/
). However, if
you just want to exclude some specific classes, in Symfony 6.3 you can also do
that with the new #[Exclude]
attribute:
// src/Kernel.php
namespace App;
use Symfony\Component\Dependency\Injection\Attribute\Exclude;
#[Exclude]
class Kernel extends BaseKernel
{
use MicroKernelTrait;
}
Allow Extending the Autowire
Attribute
Contributed by Kevin Bond
in #49433.
In Symfony 6.3, the #[Autowire]
attribute can be extended to create your own
custom autowiring helpers. For example, consider this example that creates a
custom attribute to autowire repositories:
use Symfony\Component\DependencyInjection\Attribute\Autowire;
class Repository extends Autowire
{
public function __construct(string $class)
{
parent::__construct(expression: \sprintf("service('some.repository.factory').create('%s')", $class));
}
}
And then, use it like this in your project:
/**
* @param ObjectRepository $repository
*/
public function __construct(
#[Repository(User::class)] private ObjectRepository $repository
) {
}
Thanks to all contributors who improved Dependency Injection in Symfony 6.3
and special thanks to Nicolas Grekas who also contributed half of the contents
of this blog post to explain the most advanced features.
Sponsor the Symfony project.