Symfony DependencyInjection : Les nouveautés qui vont changer votre code

Imen EZZINE

Développeuse PHP Symfony

@imenezzine

  @imenezzine1

 

Plan

  • Les principes de l'Injection de Dépendance (DI)

  • La configuration du container de services Symfony

  • L'évolution du composant dans le temps

  • L’utilisation des Attributs pour configurer les services

🤔 Qu’est-ce que l'Injection de Dépendance ?

➡️ L'Injection de Dépendance (DI) est un pattern de conception qui permet de réduire le couplage entre les différentes parties d'une application.

 

Au lieu que les classes créent leurs dépendances elles-mêmes, elles reçoivent ces dépendances de l'extérieur, généralement via leur constructeur.

🤔 Qu’est-ce que Le composant DependencyInjection?

 

➡️ Le composant DependencyInjection est un outil clé de Symfony qui permet de gérer les dépendances des services de manière centralisée et efficace.

 

➡️ Au lieu de créer et gérer manuellement les objets, Symfony s’en charge pour nous en utilisant un conteneur de services.

Pourquoi l'utiliser?

  • Séparation des préoccupations 

  • Testabilité 

  • Maintenance 

  • Compatible avec les principes SOLID

// Avant DI
class UserController {
    private $userService;

    public function __construct() {
        $this->userService = new UserService(); // ❌ Couplage fort
    }
}

💥  Avant l’injection de dépendance — quels problèmes ?

// Après DI
class UserController {
    private $userService;

    public function __construct(UserService $userService) {
        $this->userService = $userService; // Dépendance injectée via le constructeur
    }
}

Un vrai tournant dans l'histoire du framework !

✅  Après l’injection de dépendance 

Container de services ?

Configuration du conteneur de services 

 

  • YAML
  • XML
  • PHP
  • Annotations et Attributs PHP

Configuration via YAML

services:
    app.my_service:
        class: App\Service\MyService
        arguments:
            $dependency: '@app.dependency'

Symfony 3 : Simplification avec l'autowiring

 

services:
    App\Service\UserService: ~ # Autowiring activé automatiquement
		  autowire: true
namespace App\Controller;

use App\Service\UserService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class UserController extends AbstractController
{
    public function index(UserService $userService)
    {
        $users = $userService->getAllUsers();
        return $this->render('users/index.html.twig', ['users' => $users]);
    }
}

Symfony 3.3 : Simplification avec l'Autoconfiguration

 

services:
    _defaults:
        autoconfigure: true # Active l'autoconfiguration pour tous les services
<?php 

namespace AppBundle\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class ExceptionSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        // return the subscribed events, their methods and priorities
        return [
            KernelEvents::EXCEPTION => [
                ['processException', 10],
            ],
        ];
    }
    
    public function processException(GetResponseForExceptionEvent $event)
    {
        // ...
    }

}

Service Locator ?

  • Introduit une refonte du container DI avec le support du PSR-11 (ContainerInterface)
  • La performance (on ne crée les services que lorsqu’ils sont utilisés)

  • La modularité (on injecte un sous-ensemble de services)

Service Locator ?

# app/config/services.yml

services:
    app.command_handler_locator:
        class: Symfony\Component\DependencyInjection\ServiceLocator
        tags: ['container.service_locator']
        arguments:
            AppBundle\FooCommand: '@app.command_handler.foo'
            AppBundle\BarCommand: '@app.command_handler.bar'

    AppBundle\CommandBus:
        arguments: ['@app.command_handler_locator']

L’utilisation des Attributs pour configurer les services

Symfony 6.1 : #[Autowire]

use Symfony\Component\DependencyInjection\Attribute\Autowire;

class MyService {
    public function __construct(
        #[Autowire(service: 'my_dependency')] private MyDependency $dependency
    ) {}
}

Symfony 6.1 : #[Autoconfigure]

use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;

#[Autoconfigure]
class MyService {}

 Symfony 6.3 : Améliorations de #[Autowire]

  • Nouveaux paramètres : env, param et service

class MyService {
    public function __construct(
        #[Autowire(env: 'APP_SECRET')] private string $appSecret
    ) {}
}

Symfony 6.3 : #[AsAlias]

  • AsAlias : Il permet de déclarer un alias pour un service directement dans le code, sans avoir besoin d’une définition YAML.

 

use Symfony\Component\DependencyInjection\Attribute\AsAlias;

#[AsAlias('my_service')]
class MyService {}

Symfony 6.3 : #[AutowireServiceClosure]

namespace App\Service;

use App\Service\Remote\MessageFormatter;
use Symfony\Component\DependencyInjection\Attribute\AutowireServiceClosure;

class MessageGenerator
{
    public function __construct(
        #[AutowireServiceClosure('third_party.remote_message_formatter')]
        private \Closure $messageFormatterResolver,
    ) {
    }

    public function generate(string $message): void
    {
        $formattedMessage = ($this->messageFormatterResolver)()->format($message);

        // ...
    }
}

Symfony 6.3 : #[AutowireCallable]

// src/Service/MessageGenerator.php
namespace App\Service;

use Symfony\Component\DependencyInjection\Attribute\AutowireCallable;

class MessageGenerator
{
    public function __construct(
        #[AutowireCallable(service: 'third_party.remote_message_formatter', method: 'format')]
        private \Closure $formatCallable,
    ) {
    }

    public function generate(string $message): void
    {
        $formattedMessage = ($this->formatCallable)($message);

        // ...
    }
}

 Symfony 7.1 : #[AutowireMethodOf]

// src/Service/MessageGenerator.php
namespace App\Service;

use Symfony\Component\DependencyInjection\Attribute\AutowireMethodOf;

class MessageGenerator
{
    public function __construct(
        #[AutowireMethodOf('third_party.remote_message_formatter')]
        private \Closure $format,
    ) {
    }

    public function generate(string $message): void
    {
        $formattedMessage = ($this->format)($message);

        // ...
    }
}

Symfony 7.1 : #[AutowireInline]

use Symfony\Component\DependencyInjection\Attribute\AutowireInline;

class MyService {
    public function __construct(
        #[AutowireInline] private MyDependency $dependency
    ) {}
}

Liste des tous les attributs 

  • AsDecorator
  • AsTaggedItem
  • AutoconfigureTag
  • AutowireDecorated
  • AutowireIterator
  • AutowireLocator
  • Exclude
  • Lazy
  • TaggedIterator
  • TaggedLocator
  • Target
  • When
  • WhenNot

 Impact des nouveautés

 

✅ Moins de configuration YAML/XML

✅ Plus de flexibilité grâce aux attributs PHP
✅ Meilleure lisibilité et maintenabilité du code

 

➡️ Depuis Symfony 6, on observe une nette simplification de l’injection de dépendances :

Conclusion

Le composant DependencyInjection continue d’évoluer vers plus de simplicité et d’efficacité.

 

Grâce aux nouvelles fonctionnalités, il est plus facile de structurer ses services et d’écrire du code maintenable et performant.

Merci !

Symfony et l'Injection de Dépendance : Une approche simplifiée

By Imen TROUDI

Symfony et l'Injection de Dépendance : Une approche simplifiée

  • 70