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

Aleksey Polyvanyi
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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

Alan Poulain
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:

1
2
3
4
5
6
7
8
9
10
11
// 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

Alexandre Daubois
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 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;
    }

    // ...
}
Nicolas Grekas
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
#[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

Nicolas Grekas
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:

1
2
#[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:

1
2
#[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:

1
2
#[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

Nicolas Grekas
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:

1
2
3
4
interface UriExpanderInterface
{
    public function expand(string $uri, array $parameters): string;
}

You can use the #[AutowireCallable] attribute to generate an adapter for it:

1
2
#[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

Nicolas Grekas
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:

1
2
#[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:

1
2
#[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

Jules Pietri
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

Grégoire Pineau
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:

1
2
3
4
5
6
7
8
9
10
// src/Kernel.php
namespace App;

use Symfony\Component\DependencyInjection\Attribute\Exclude;

#[Exclude]
class Kernel extends BaseKernel
{
    use MicroKernelTrait;
}

Allow Extending the Autowire Attribute

Kevin Bond
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:

1
2
3
4
5
6
7
8
9
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:

1
2
3
4
5
6
7
/**
 * @param ObjectRepository<User> $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.

Published in #Living on the edge