Meetup Tunis Talan 2025
Développeuse PHP Symfony
@imenezzine
@imenezzine1
➡️ 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
// apps/frontend/modules/job/actions/actions.class.php
class jobActions extends sfActions
{
private $userService;
public function __construct()
{
$this->userService = new UserService(); // ❌ Couplage fort
}
public function executeIndex(sfWebRequest $request)
{
$address = $this->userService->getAddress();
// ...
}
// ...
}
mailer:
class: sfMailer
param:
logging: %SF_LOGGING_ENABLED%
charset: %SF_CHARSET%
delivery_strategy: realtime
transport:
class: Swift_SmtpTransport
param:
host: localhost
port: 25
// apps/backend/modules/affiliate/actions/actions.class.php
class affiliateActions extends autoAffiliateActions
{
public function executeListActivate()
{
// faut passer par sfContext pour l'injection des services tiers
$mailer = sfContext::getInstance()->getMailer();
$message = $mailer->compose(.....);
$mailer->send($message);
$this->redirect('jobeet_affiliate');
}
// ...
}
# app/config/services.yml
services:
app.invoice_mailer:
class: AppBundle\Service\InvoiceMailer
app.job_affiliate_controller:
class: AppBundle\Controller\JobAffiliateController
arguments: ['@app.invoice_mailer']
app.exception_subscriber:
class: AppBundle\EventSubscriber\ExceptionSubscriber
arguments:
- '@app.invoice_mailer'
tags:
- { name: kernel.event_subscriber }
app.twig_extension:
class: AppBundle\Twig\AppExtension
public: false
tags:
- { name: twig.extension }
....
// src/AppBundle/EventSubscriber/ExceptionSubscriber.php
namespace AppBundle\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use AppBundle\Service\InvoiceMailer;
class ExceptionSubscriber implements EventSubscriberInterface
{
private $mailer;
public function __construct(InvoiceMailer $mailer)
{
$this->mailer =$mailer;
}
public static function getSubscribedEvents()
{
// return the subscribed events, their methods and priorities
return array(
KernelEvents::EXCEPTION => array(
array('processException', 10)
);
}
public function processException(GetResponseForExceptionEvent $event)
{
//.....
}
}
services:
# default configuration for services in *this* file
_defaults:
# automatically injects dependencies in your services
autowire: true
# automatically registers your services as commands, event subscribers, etc.
autoconfigure: true
# this means you cannot fetch services directly from the container via $container->get()
# if you need to do this, you can override this setting on individual services
public: false
# makes classes in src/AppBundle available to be used as services
# this creates a service per class whose id is the fully-qualified class name
AppBundle\:
resource: '../../src/AppBundle/*'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../../src/AppBundle/{Entity,Repository}'
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
# app/config/services.yml
parameters:
sender_email: manager@example.com
services:
# ... same as before
_defaults:
bind:
# pass this value to any $adminEmail argument for any service
# that's defined in this file (including controller arguments)
$adminEmail: 'manager@example.com'
# pass this service to any $requestLogger argument for any
# service that's defined in this file
$requestLogger: '@monolog.logger.request'
# pass this service for any LoggerInterface type-hint for any
# service that's defined in this file
Psr\Log\LoggerInterface: '@monolog.logger.request'
AppBundle\Updates\SiteUpdateManager:
arguments:
$senderEmail: '%sender_email%'
# explicitly configure the wanted service for monolog
AppBundle\Service\MessageGenerator:
arguments:
$logger: '@monolog.logger.request'
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();
}
}
namespace App\EventListener;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener(event: CustomEvent::class, method: 'onCustomEvent', priority: 42)]
final class RegisterUserListener
{
public function onRegister(CustomEvent $event): void
{
// ...
}
}
// src/Command/RegisterUserCommand.php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
// the "name" and "description" arguments of AsCommand replace the
// static $defaultName and $defaultDescription properties
#[AsCommand(
name: 'app:register-user',
description: 'Creates a new user.',
hidden: false,
aliases: ['app:add-user']
)]
class RegisterUserCommand extends Command
{
// ...
}
#[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 {}
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]
#[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 {}
#[
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);
// ...
}
}
AsDecorator
AsTaggedItem
AutoconfigureTag
AutowireDecorated
Exclude
Lazy
TaggedIterator AutowireIterator
TaggedLocator AutowireLocator
Target
When
WhenNot
✅ 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 :
🗂️ Ne pas mélanger attributs et fichiers de configuration YAML/XML
⚖️ Utilisation mesurée des attributs
🔄 Prudence avec les dépendances circulaires
🧩Testabilité
⚠️ Points d'attention à ne pas négliger :