New in Symfony 6.3: Dependency Injection Improvements


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.

Autowiring Lazy Services

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.