API Platform
Développeuse PHP Symfony
@imenezzine
@imenezzine1
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
➡️ 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.
➡️ 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.
Séparation des préoccupations
Testabilité
Maintenance
Compatible avec les principes SOLID
services:
app.my_service:
class: App\Service\MyService
arguments:
$dependency: '@app.dependency'
Arrivé du Symfony 1 en 2007
Class UserController extends sfActions
{
public function executeIndex(sfWebRequest $request)
{
$logger = $this->getContext()->getLogger();
$logger->info('Chargement de la page utilisateur.');
return sfView::SUCCESS;
}
}
Arrivée du Symfony 2 en 2011
services:
app.user_controller:
class: App\Controller\UserController
arguments: ['@logger', '%kernel.project_dir%']
Un vrai tournant dans l'histoire du framework !
namespace App\Controller;
use Psr\Log\LoggerInterface;
class UserController
{
private LoggerInterface $logger;
private string $projectDir;
public function __construct(LoggerInterface $logger, string $projectDir)
{
$this->logger = $logger;
$this->projectDir = $projectDir;
}
public function indexAction()
{
$this->logger->info('Chargement de la page utilisateur.');
return new Response('Page utilisateur chargée');
}
}
Arrivée du Symfony 3.3 en 2017
La performance (on ne crée les services que lorsqu’ils sont utilisés)
La modularité (on injecte un sous-ensemble de services)
# 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']
# app/config/services.yml
services:
_defaults:
autowire: true
autoconfigure: true
App\: '../src/'
namespace App\Controller;
use Psr\Log\LoggerInterface;
use Symfony\Component\Routing\Annotation\Route;
class UserController
{
private $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger; // Injection automatique
}
/**
* @Route("/user", name="user_index")
*/
public function index() {
$this->logger->info('Autowiring Symfony 3.3');
return new Response();
}
}
Introduction du l'autowiring et l'autoconfiguration
Arrivée du Symfony 5 en 2020
namespace App\Controller;
use Psr\Log\LoggerInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Response;
class UserController
{
public function __construct(
private LoggerInterface $logger, // Promoted property
#[Autowire('%kernel.project_dir%')] private string $projectDir
) {}
#[Route('/user', name: 'user_index')]
public function __invoke(): Response
{
$this->logger->info('Contrôleur invocable avec propriétés promues');
return new Response();
}
}
Arrivé de la version Symfony 6
namespace App\Controller;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Monolog\Attribute\WithMonologChannel;
#[Route('/user', name: 'user_index')]
#[WithMonologChannel('security')] // Configuration via attribut
class UserController
{
public function __construct(
#[Autowire(service: 'monolog.logger.custom')] // Ciblage précis
private LoggerInterface $customLogger
) {}
public function __invoke(): Response
{
$this->customLogger->info('Log dans un canal spécifique');
return new Response();
}
}
#[Autowire]
use Symfony\Component\DependencyInjection\Attribute\Autowire;
class MyService {
public function __construct(
#[Autowire(service: 'my_dependency')] private MyDependency $dependency
) {}
}
#[Autoconfigure]
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
#[Autoconfigure]
class MyService {}
Arrivé de la version 6.3
namespace App\Controller;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Monolog\Attribute\WithMonologChannel;
#[Route('/user', name: 'user_index')]
#[WithMonologChannel('security')] // Configuration via attribut
class UserController
{
public function __construct(
#[Autowire(service: 'monolog.logger.custom')] // Ciblage précis,
private LoggerInterface $customLogger,
#[Autowire(env: 'APP_SECRET')]
private string $appSecret,
) {}
public function __invoke(): Response
{
$this->customLogger->info('Log dans un canal spécifique');
return new Response();
}
}
#[Autowire]
Nouveaux paramètres : env
, param
et service
class MyService {
public function __construct(
#[Autowire(env: 'APP_SECRET')] private string $appSecret
) {}
}
#[AsAlias]
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
#[AsAlias('my_service')]
class MyService {}
# Symfony 3.3+
services:
_defaults:
autowire: true # Active l'inférence des dépendances
autoconfigure: true # Auto-tagging des services
public: false
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests,Kernel.php}'
# Symfony 6.1+
when@dev:
monolog:
channels: ['custom'] # Déclaration de canal
#[
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);
// ...
}
}
#[
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);
// ...
}
}
#[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);
// ...
}
}
✅ 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 :
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.