---
title: Autoconfiguration in Symfony
date: 2025-12-14
description: About autoconfiguration in Symfony, a time-saving feature for classifying services in the container
categories:
  - symfony
---

A very powerful feature in the Symfony framework is the **autoconfiguration** system. Enabled by default, it classifies our services in the container, based on the nature of our classes.

## How it works

In the service container, we can find **a lot** of services, coming from different places: Symfony components, libraries, or even services that we have written in our application.

In the service container, it then becomes necessary to **classify** these services. The goal is simple: if I have an action to perform, it may be useful to have a collection of services that allow me to perform this action, rather than manually looking for the service that will answer my problem.

## Enabling

It only takes one line in the configuration files to enable the autoconfiguration.

In the `config/services.yaml` file, a directive is present by default: `autoconfigure: true`.

## Example: Voters

In Symfony, the [Voters](https://symfony.com/doc/7.4/security/voters.html) system allows the security component to make a decision based on a given situation, and to manage **permissions**: does the user have the right to perform an action on a resource?

To create a voter, Symfony recommends to create a class that implements `VoterInterface` or, even better, that inherits from a `Voter` abstract class.

:::note

The `Voter` abstract class implements `VoterInterface`

:::

For example, if I wanted to setup an access control for the records of a `Project` entity, I could create a `ProjectVoter` class that inherits from `Voter`:

```php
<?php

namespace App\Security\Voter;

use App\Entity\Project;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;

class ProjectVoter extends Voter
{
    protected function supports($attribute, $subject): bool
    {
        return in_array($attribute, ['VIEW', 'EDIT'])
            && $subject instanceof Project;
    }

    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
    {
        // Make a decision: return true or false
    }
}
```

Once our class is written, we can inspect it in the service container:

```bash
php bin/console debug:container ProjectVoter
```

We can see that among the properties of our service, there is a `Tags` entry, which contains `security.voter` in this case.

## The tag system

The autoconfiguration mechanism, when reading our voter, has **classified** our service with the other voters of the security component. To place it in the right place, it has simply discovered that our service inherited from the `Voter` abstract class.

The classification is done by a **tag** system: a label to better identify the nature of a service.

We mentioned it earlier, but the `Voter` abstract class implements the `VoterInterface` interface. This interface is actually **marked** for autoconfiguration. In the [Symfony security bundle](https://github.com/symfony/security-bundle/blob/7.3/DependencyInjection/SecurityExtension.php#L186-L187), we can find the `security.voter` tag:

```php
// security-bundle/DependencyInjection/SecurityExtension.php

$container->registerForAutoconfiguration(VoterInterface::class)
    ->addTag('security.voter');
```

When compiling the service container, the [`AddSecurityVotersPass`](https://github.com/symfony/security-bundle/blob/7.3/DependencyInjection/Compiler/AddSecurityVotersPass.php) class retrieves the tagged services and registers them in an `AccessDecisionManager`:

```php
// security-bundle/DependencyInjection/Compiler/AddSecurityVoterPass.php

class AddSecurityVotersPass implements CompilerPassInterface
{
    //...

    public function process(ContainerBuilder $container): void
    {
        //...

        // Retrieve the voters
        $voters = $this->findAndSortTaggedServices('security.voter', $container);

        // Build an $voterServices array containing the services (with or without debug)...

        // Transfer the array of voters to the AccessDecisionManager
        $container->getDefinition('security.access.decision_manager')
            ->replaceArgument(0, new IteratorArgument($voterServices));
    }
}
```

`AccessDecisionManager` has a `collectResults` method that will actually perform the votes and retrieve the results:

```php
// security-core/Authorization/AccessDecisionManager.php

private function collectResults(TokenInterface $token, array $attributes, mixed $object, AccessDecision $accessDecision): \Traversable
{
    foreach ($this->getVoters($attributes, $object) as $voter) {
        $vote = new Vote();
        $result = $voter->vote($token, $object, $attributes, $vote);

        //...
    }
}
```

We can see here that the `vote` method of each voter will be called!

## Polymorphism

In the `AccessDecisionManager` class, for each registered voter, we call the `vote` method and collect the results.

From the `AccessDecisionManager` class point of view, this is not a problem, since [all voters](https://github.com/symfony/security-core/blob/7.3/Authorization/AccessDecisionManager.php#L45-L50) included in this class are considered as instances of a type implementing `VoterInterface`:

```php
// security-core/Authorization/AccessDecisionManager.php

final class AccessDecisionManager implements AccessDecisionManagerInterface
{
    //...

    /**
     * @param iterable<mixed, VoterInterface> $voters An array or an iterator of VoterInterface instances
     */
    public function __construct(
        private iterable $voters = [],
        //...
    ) {
        //...
    }
}
```

So, in our case, the `Voter` abstract class implements `VoterInterface`: we therefore have a [definition for the `vote` method](https://github.com/symfony/security-core/blob/7.3/Authorization/Voter/Voter.php). And in this method, the `supports` and `voteOnAttribute` methods are eventually called.

This mechanism allows us to define our own code and hook our own permission logic into the Symfony security component. In the security component, the behavior is **polymorphic**: when calling the voters, we consider them all as instances implementing an **abstraction**: it doesn't matter how the `vote` method is written, we know that if the `VoterInterface` is implemented, then the class **must** provide an implementation of the `vote` method.

The `Voter` abstract class declares two abstract methods: `supports` and `voteOnAttribute`: this **forces** the children to provide an implementation of these two methods. With the autoconfiguration mechanism and the fact that our voters inherit from the `Voter` class, all our classes implementing a permission mechanism will be automatically registered and classified in the security component!

## Other examples

Symfony also detects other implementations and inheritances in order to classify our services. We can even use **PHP8 attributes** to trigger the autoconfiguration.

### Normalizers

Normalizers are responsible for transforming a data into an array. In the Symfony serialization component, we already have built-in normalizers. But if we want, we can create our own normalizer.

In this case, we just have to create a class that implements [`NormalizerInterface`](https://github.com/symfony/symfony/blob/7.4/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php) interface, and then implement the 3 methods of the contract: `normalize`, `supportsNormalization` and `getSupportedTypes`.

The autoconfiguration mechanism will automatically detect the classes implementing `NormalizerInterface` and register them with the `serializer.normalizer` tag to make it available during a normalization process.

### Console commands

In a Symfony application, we can create our own console commands.

To do this, we just have to create a class and annotate it with a `#[AsCommand]` PHP8 attribute. Symfony will automatically add the `console.command` tag to the class, and the command will be available with `php bin/console`.
