Schéma architecture modulaire Symfony + API Platform


API Platform

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


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'


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;
}
}


💥 Avant l’injection de dépendance — quels problèmes ?
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 !

✅ Après l’injection de dépendance
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
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']
# 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
Promoted properties + attributs PHP 8
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();
}
}
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 {}


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();
}
}
Symfony 6.3 : Améliorations de #[Autowire]
-
Nouveaux paramètres :
env
,param
etservice
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 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
Évolution des fichiers de configuration
L’utilisation des Attributs pour configurer les services


Liste des tous les attributs
- AsDecorator
- AsTaggedItem
- AutoconfigureTag
- AutowireDecorated
- Exclude
- Lazy
-
TaggedIteratorAutowireIterator -
TaggedLocatorAutowireLocator - Target
- When
- WhenNot


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);
// ...
}
}


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 !


Copy of Symfony et l'Injection de Dépendance : Une approche simplifiée
By Imen TROUDI
Copy of Symfony et l'Injection de Dépendance : Une approche simplifiée
- 96