L'Injection de Dépendance dans Symfony : Une Force Cachée

Imen EZZINE

@imenezzine

  @imenezzine1

 

Développeuse PHP Symfony
  • Symfony 1.x : une approche rigide

    •  

Text

class ReportGenerator
{
    public function generateAndStore(array $data, string $fileName): void
    {
        $pdfGenerator = new PdfGenerator('/usr/local/bin/wkhtmltopdf');
        $pdfContent = $pdfGenerator->create($data);

        $storage = new FileStorage('/var/reports');
        $storage->save($fileName, $pdfContent);

        $logger = new Logger('/var/log/app.log');
        $logger->log('Report ' . $fileName . ' successfully generated and stored.');
    }
}
  • Les dépendances...
  •       - ne sont pas modifiables.

  •       - ne sont pas configurables.

  •       - seront toujours testées avec votre code.

  •       - ne sont pas explicites.

    •  

      Symfony 1.x : une approche rigide

      •  

Symfony 2 est sorti le 28 juillet 2011. 🎉

  • Le composant DependencyInjection,

  • L’architecture bundle,

  • L’intégration poussée avec Composer (arrivé un peu plus tard),

  • L’approche RESTful, l’autoloader PSR-0, etc.

 

Un vrai tournant dans l'histoire du framework !

class ReportGenerator
{
    private $pdfGenerator;
    private $storage;
    private $logger;

    public function __construct(PdfGenerator $pdfGenerator, FileStorage $storage, LoggerInterface $logger)
    {
        $this->pdfGenerator = $pdfGenerator;
        $this->storage = $storage;
        $this->logger = $logger;
    }

    public function generateAndStore(array $data, string $fileName): void
    {
        $pdfContent = $this->pdfGenerator->create($data);
        $this->storage->save($fileName, $pdfContent);
        $this->logger->info('Report ' . $fileName . ' successfully generated and stored.');
    }
}

🤔 Le composant DependencyInjection, c'est quoi ?

 

 

🤔 Le composant DependencyInjection, c'est quoi ?

 

  •  - Le composant DependencyInjection de Symfony permet de gérer et injecter automatiquement les dépendances entre les classes de ton application.
    - Il repose sur un conteneur de services qui centralise la création, la configuration et la mise à disposition des objets.
    - Cela permet un code plus modulaire, testable et maintenable, en suivant les principes de l’injection de dépendances et de l’inversion de contrôle.

Text

Text

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)

Text

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']

Text

Service Locator

// src/AppBundle/CommandBus.php

namespace AppBundle;

use Psr\Container\ContainerInterface;

class CommandBus
{
    private $locator;

    public function __construct(ContainerInterface $locator)
    {
        $this->locator = $locator;
    }

    public function handle($command)
    {
        $commandClass = get_class($command);

        if ($this->locator->has($commandClass)) {
            $handler = $this->locator->get($commandClass);
            return $handler->handle($command);
        }

        throw new \LogicException(sprintf('No handler found for command "%s".', $commandClass));
    }
}

Symfony 3 : Simplification avec l'autowiring

 

services:
    App\Service\UserService: ~ # Autowiring activé automatiquement
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]);
    }
}
services:
    _defaults:
        autoconfigure: true # Active l'autoconfiguration pour tous les services

    App\EventListener\MyEventListener: ~

Symfony 4 et au-delà : Évolutions modernes

 

Autoconfiguration

namespace App\EventListener;

use Symfony\Component\HttpKernel\Event\RequestEvent;

class MyEventListener
{
    public function onKernelRequest(RequestEvent $event)
    {
        // Logique à exécuter lors de l'événement
    }
}
DATABASE_URL=mysql://user:password@127.0.0.1:3306/mydb

Symfony 4 et au-delà : Évolutions modernes

 

Gestion des variables d'environnement

parameters:
    database_url: '%env(DATABASE_URL)%'

services:
    App\Service\DatabaseService:
        arguments:
            $url: '%database_url%'

Symfony 5

Utilisation des attributs

namespace App\Service;

use Symfony\Component\DependencyInjection\Attribute\Autowire;

class MyService
{
    public function __construct(
        #[Autowire(service: 'logger')] private LoggerInterface $logger,
    ) {}
}

 

L'ajout de ce composant a eu plusieurs effets importants :

  1. Révolution de l'écosystème PHP : Symfony 2.0 a bouleversé la façon dont les applications PHP étaient structurées et développées3.

  2. Nouvelle philosophie : Le concept de services et l'injection de dépendances ont introduit une nouvelle approche dans la conception des applications Symfony3.

  3. Apprentissage nécessaire : Les développeurs ont dû réapprendre le framework, car la version 2.0 était radicalement différente de la précédente3.

Impacts de l'introduction

Découplage 
Facilité de maintenance
Testabilité améliorée 
Performance optimisée 

 

Depuis son introduction, le composant d'injection de dépendances a continué d'évoluer :

  • Il est devenu plus puissant et central au framework au fil des versions1.

  • Les versions récentes de Symfony (comme Symfony 6) ont simplifié son utilisation avec des fonctionnalités comme l'autowiring et l'autoconfiguration4.

L'injection de dépendances reste un concept fondamental dans Symfony, permettant une meilleure organisation du code, une plus grande flexibilité et une maintenance plus aisée des applications.

Évolution du composant

class UserService {
    private Database $db;

    public function __construct() {
        $this->db = new Database();
    }
}

 Avant et après le composant DependencyInjection

🔴 Avant Symfony et son conteneur de services

 

Problèmes :

  • Dépendance forte avec Database
  • Code rigide et difficilement testable
  • Pas de gestion centralisée des services

🟢 Après : Symfony et son conteneur de services

use Symfony\Component\DependencyInjection\Attribute\Autowire;

class UserService {
    public function __construct(
        #[Autowire(service: 'database_connection')]
        private Database $db
    ) {}
}

Avantages :

  • Code plus flexible et testable
  • Configuration centralisée
  • Meilleure maintenabilité

 Symfony 6.1 : Introduction de #[Autowire]

use Symfony\Component\DependencyInjection\Attribute\Autowire;

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

Avantages :

  • Plus besoin de config YAML/XML
  • Injection visible directement dans le code

 Symfony 6.1 : #[Autoconfigure]

 

use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;

#[Autoconfigure]
class MyService {}

Permet d’activer automatiquement l’autowiring et les tags

Merci!

Des questions ?

Made with Slides.com