L'Injection de Dépendance dans Symfony : Une Force Cachée
@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.');
}
}
- 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.');
}
}
Text
Text
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
# 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
// 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));
}
}
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: ~
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
parameters:
database_url: '%env(DATABASE_URL)%'
services:
App\Service\DatabaseService:
arguments:
$url: '%database_url%'
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 :
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.
Nouvelle philosophie : Le concept de services et l'injection de dépendances ont introduit une nouvelle approche dans la conception des applications Symfony3.
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.
✅ 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.
class UserService {
private Database $db;
public function __construct() {
$this->db = new Database();
}
}
Avant et après le composant DependencyInjection
❌ Problèmes :
Database
🟢 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 :
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 :
#[Autoconfigure]
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
#[Autoconfigure]
class MyService {}
✅ Permet d’activer automatiquement l’autowiring
et les tags