Home / Symfony / New in Symfony 8.1: Dependency Injection Improvements

New in Symfony 8.1: Dependency Injection Improvements

The DependencyInjection component keeps evolving in Symfony 8.1 with several
quality-of-life improvements for autowiring, service decoration, tagged services,
and env vars.

Autowiring Env Vars as Closures or Stringables


Nicolas Grekas
Contributed by
Nicolas Grekas
in
#63988

When you inject an env var as a plain string, its value is baked into the
container at compile time and never changes. That’s fine for traditional
request/response applications, but it’s a problem in long-running workers
(Messenger, FrankenPHP, Roadrunner), where you may want the env var to refresh
between requests via Container::resetEnvCache().

Symfony 8.1 lets you autowire env vars (or any %env(...)% expression) as
Closure or Stringable values. Each call returns the current value:

use SymfonyComponentDependencyInjectionAttributeAutowire;

class Worker
{
    public function __construct(
        #[Autowire(env: 'DB_URL')]
        private Closure $dbUrl,

        #[Autowire(env: 'APP_NAME')]
        private string|Stringable $appName = 'default',

        // Embedded env vars in any string also work:
        #[Autowire('redis://%env(HOST)%:%env(PORT)%')]
        private Stringable $redisDsn,
    ) {
    }
}

When a default value is declared, it’s returned when the env var is missing
(it must be a string for Stringable and can be any type for Closure).
The same behavior is available in YAML through the new !env_closure tag:

services:
    AppWorker:
        arguments:
            - !env_closure '%env(DB_URL)%'
            - !env_closure ['%env(APP_NAME)%', 'default']

Service Stacks as Decorators


Nicolas Grekas
Contributed by
Nicolas Grekas
in
#63590

Service stacks let you compose multiple services into a chain where each
layer wraps the next via @.inner. Until now, stacks couldn’t decorate an
existing service, requiring manual decoration instead.

Symfony 8.1 closes that gap by adding decorates and decorates_tag
support to stack definitions. The innermost service in the stack becomes the
decorator of the target service:

services:
    my_stack:
        decorates: api_platform.serializer.context_builder
        stack:
            - class: AppDecoratorAddGroupsContextBuilder
              arguments: ['@.inner']
            - class: AppDecoratorAddFiltersContextBuilder
              arguments: ['@.inner']

When you use decorates_tag, the stack is cloned once per tagged service,
and each clone automatically decorates a different target.

Decorating Tagged Services


Mathias Arlaud
Contributed by
Mathias Arlaud
in
#62638

Decorating every service that carries a given tag previously required a custom
compiler pass. Symfony 8.1 makes this declarative through a new
decorates_tag option, available both as a service configuration key and as
the #[AsTagDecorator] PHP attribute:

use SymfonyComponentDependencyInjectionAttributeAsTagDecorator;

#[AsTagDecorator('app.handler')]
class LoggingHandler
{
    public function __construct(private object $inner)
    {
    }
}

The same configuration in YAML:

services:
    app.logging_handler:
        class: AppDecoratorLoggingHandler
        decorates_tag: app.handler

Every service tagged with app.handler is now wrapped by LoggingHandler.
This is convenient when you need to add global concerns like logging, tracing,
or caching to a family of services.

Inline Definitions as Factories and Configurators


Jérôme Tamarelle
Contributed by
Jérôme Tamarelle
in
#63910

setFactory() and setConfigurator() already accepted callables
expressed as arrays ([$ref, 'method']) or strings. In Symfony 8.1, they
also accept a Definition instance directly, which Symfony wraps as
[$definition, '__invoke']. This mirrors how Reference objects are
already handled:

use SymfonyComponentDependencyInjectionDefinition;

$container->register('app.handler', HandlerClass::class)
    ->setFactory(new Definition(InvokableFactory::class))
    ->setConfigurator(new Definition(InvokableConfigurator::class));

This is useful when the factory or configurator is a one-off invokable that
doesn’t deserve its own named service definition.

Excluding Files When Importing Services


tilaven
Contributed by
tilaven
in
#63111

When you import service definitions from a glob pattern, you can now exclude
specific matches with the new exclude argument of
ContainerConfigurator::import():

return function (ContainerConfigurator $configurator): void {
    $configurator->import('services/*.php', exclude: [
        'services/legacy/*',
        'services/dev_only.php',
    ]);
};

Previously, the only way to skip a file inside an imported directory was to
restructure the service tree.

Stronger #[Target] Support


Ayyoub AFW-ALLAH


Nicolas Grekas
Contributed by
Ayyoub AFW-ALLAH
and Nicolas Grekas
in
#63181
and #63426

#[Target] is the recommended way to disambiguate multiple implementations
of the same interface. Symfony 8.1 makes it more ergonomic on both sides.
On the declaration side, #[AsAlias] now accepts a target argument that
creates a named autowiring alias automatically:

use SymfonyComponentDependencyInjectionAttributeAsAlias;

#[AsAlias(StorageInterface::class, target: 'image')]
class ImageStorage implements StorageInterface
{
}

#[AsAlias(StorageInterface::class, target: 'document')]
class DocumentStorage implements StorageInterface
{
}

On the injection side, Symfony 8.1 deprecates the legacy behavior where the
parameter name alone could match a named alias. That fallback was fragile
because renaming a parameter could silently break the injection, and the
dependency target was invisible from the constructor signature. Starting in
Symfony 8.1, you should always make the target explicit with #[Target]:

public function __construct(
-    private StorageInterface $imageStorage,
+    #[Target('image')] private StorageInterface $storage,
 ) {
 }

Implicit name-based matching still works but emits a deprecation notice. It
will be removed in Symfony 9.0.

Setting Voter Priority with #[AsTaggedItem]


Ayyoub AFW-ALLAH
Contributed by
Ayyoub AFW-ALLAH
in
#62824

Security voters are autoconfigured with the security.voter tag. To set a
voter’s priority, you previously had to add #[AutoconfigureTag] or YAML
configuration, but both approaches created a duplicate security.voter tag,
which often caused subtle issues.

Symfony 8.1 lets you use #[AsTaggedItem] on voters instead. It writes the
priority on the existing tag without duplicating it, and it doesn’t even
require naming the tag explicitly:

use SymfonyComponentDependencyInjectionAttributeAsTaggedItem;
use SymfonyComponentSecurityCoreAuthorizationVoterVoter;

#[AsTaggedItem(priority: 10)]
final class PostVoter extends Voter
{
    // ...
}

Environment Variables with Dots


Massimiliano Baldanza
Contributed by
Massimiliano Baldanza
in
#62835

Symfony used to reject env vars whose names contained dots, even though they
are valid at the OS level. This was a problem for tooling that generates env
vars from hierarchical configuration.

Symfony 8.1 allows dots in env var names, so you can consume those variables
directly:

# .env or container environment
DATABASE.PRIMARY.URL="postgresql://..."

services:
    AppRepositoryUserRepository:
        arguments:
            $dsn: '%env(DATABASE.PRIMARY.URL)%'

Deprecating Default Index/Priority Methods


Nicolas Grekas
Contributed by
Nicolas Grekas
in
#62339

When defining tagged iterators or locators, Symfony has long supported two
magic static methods on tagged classes to provide a default index or priority:
getDefault<Name>Name() and getDefaultPriority(). These methods, along
with their defaultIndexMethod and defaultPriorityMethod configuration
counterparts, are deprecated in Symfony 8.1.

The replacement, #[AsTaggedItem], has been available since Symfony 5.3 and
makes the intent visible directly on the class:

use SymfonyComponentDependencyInjectionAttributeAsTaggedItem;

+#[AsTaggedItem(index: 'json', priority: 10)]
 class JsonHandler
 {
-    public static function getDefaultName(): string
-    {
-        return 'json';
-    }
-
-    public static function getDefaultPriority(): int
-    {
-        return 10;
-    }
 }

The deprecated method conventions will be removed in Symfony 9.0.


Sponsor the Symfony project.
Tagged:

Leave a Reply

Your email address will not be published. Required fields are marked *