Autowiring

Autowiring reads a constructor's type declarations and injects the matching services, so most classes need no configuration at all.

Syntaxphp bin/console debug:autowiring FeedReaderInterface

Autowiring is the step where the container reads a constructor's type declarations and works out which service to pass for each argument. You write the types; you write no configuration.

How a type becomes a service

The default services.yaml registers every class under src/ with its fully qualified class name as the service id. An argument typed App\Import\RssFeedReader therefore matches the service with that id. An interface cannot be instantiated, so it matches only through an alias: a second service id that points at a real service. One line of configuration, or the #[AsAlias] attribute on the class, says which implementation an interface means.

Autoconfiguration is a different thing

Autoconfiguration (autoconfigure: true) looks at what a class implements and which attributes it carries, then adds the tags that register it with a feature: a console command, an event subscriber, a Twig extension. Autowiring fills the arguments; autoconfiguration states what the service is for.

Values that are not services

A string, int or bool has no service to match. Use #[Autowire] on the argument: #[Autowire('%kernel.project_dir%/var')] for a literal or parameter, #[Autowire(param: 'kernel.debug')] for a parameter by name, #[Autowire(env: 'API_TOKEN')] for an environment variable, #[Autowire(service: 'some.id')] for a specific service. The longhand is the same value under arguments: in a definition.

Seeing the options

debug:autowiring lists every type you can type-hint, with the service each one resolves to.

Example

Example · php
// src/Service/ArticleImporter.php
declare(strict_types=1);

namespace App\Service;

use App\Import\FeedReaderInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

final class ArticleImporter
{
    public function __construct(
        // matched by type: the container uses the service aliased to this interface
        private FeedReaderInterface $reader,
        // a string is not a service, so say where the value comes from.
        // Longhand: arguments: { $importDir: '%kernel.project_dir%/var/import' }
        #[Autowire('%kernel.project_dir%/var/import')]
        private string $importDir,
        // the same thing naming the parameter instead of writing %...%
        #[Autowire(param: 'kernel.debug')]
        private bool $debug,
    ) {
    }

    public function describe(): string
    {
        return sprintf(
            '%s -> %s (debug: %s)',
            $this->reader->name(),
            $this->importDir,
            $this->debug ? 'yes' : 'no',
        );
    }
}

// With kernel.project_dir = /srv/app, debug on, and RssFeedReader::name() returning 'rss':
// $importer->describe()
// Output: rss -> /srv/app/var/import (debug: yes)

When to use it

  • Add a new service class and use it immediately, without touching any configuration file.
  • Find out which concrete service a framework interface resolves to before type-hinting it.
  • Inject a project directory, a feature flag or an API token into a service that otherwise autowires cleanly.

More examples

Asking what a type resolves to

Run this before guessing a type-hint. The part after the dash is the service the interface resolves to; if an interface is missing from the list, no alias exists for it yet and autowiring that argument will fail with a clear exception when the container compiles.

Example · bash
$ php bin/console debug:autowiring FeedReader

Autowirable Types
=================

 The following classes & interfaces can be used as type-hints when autowiring:
 (only showing classes/interfaces matching FeedReader)

 App\Import\FeedReaderInterface - alias:App\Import\RssFeedReader
 App\Import\RssFeedReader

Pointing an interface at an implementation

This is the one line that makes an interface autowirable. The attribute form is #[AsAlias] on the implementing class, which can detect the interface when the class implements only one. Use the configuration form when the choice of implementation belongs to the application rather than to the class.

Example · yaml
# config/services.yaml
services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\:
        resource: '../src/'

    # shorthand: a value starting with @ makes this id an alias of that service
    App\Import\FeedReaderInterface: '@App\Import\RssFeedReader'

    # longhand for exactly the same line:
    # App\Import\FeedReaderInterface:
    #     alias: App\Import\RssFeedReader

Two implementations, chosen per argument

The named alias is declared as App\Formatter\FormatterInterface $htmlFormatter: '@App\Formatter\HtmlFormatter', or on the class with #[AsAlias(FormatterInterface::class, target: 'htmlFormatter')], whose target argument was added in Symfony 8.1. #[Target] on the argument is what ties the two together; without it, Symfony 8.1 matches by parameter name and emits a deprecation.

Example · php
// src/Service/NewsletterGenerator.php
declare(strict_types=1);

namespace App\Service;

use App\Formatter\FormatterInterface;
use Symfony\Component\DependencyInjection\Attribute\Target;

final class NewsletterGenerator
{
    public function __construct(
        // FormatterInterface has two implementations; name the alias you want
        #[Target('htmlFormatter')]
        private FormatterInterface $formatter,
    ) {
    }

    public function greet(string $name): string
    {
        return $this->formatter->format('Hello '.$name);
    }
}

// With HtmlFormatter wrapping its input in a paragraph:
// $generator->greet('Ada')
// Output: <p>Hello Ada</p>

Discussion

  • Be the first to comment on this lesson.