Symfony 8 CheatSheet

24 catégories · 126 features · 40 termes du glossaire

📦

AssetMapper

Gestion des assets front-end sans bundler, importmap, composants JS.

3
beginner component 6.3
Bonne pratique Nouveauté 7.x

AssetMapper

Gestion des assets front-end sans webpack/npm. Les fichiers JS/CSS dans assets/ sont servis directement avec versioning par hash. Supporte les ES modules natifs et l'importmap.

twig AssetMapper — configuration et usage Twig

Intégrer AssetMapper dans le template de base.

{# templates/base.html.twig #}
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %}</title>

    {# Injecte l'importmap et charge app.js #}
    {{ importmap('app') }}

    {# CSS géré par AssetMapper (avec hash de version) #}
    <link rel="stylesheet" href="{{ asset('styles/app.css') }}">
</head>
<body>
    {% block body %}{% endblock %}
</body>
</html>
beginner concept 6.3
Nouveauté 7.x

Importmap

importmap.php liste les packages JS et leurs CDN URLs (jsDelivr). importmap:require ajoute un package. {{ importmap('app') }} dans Twig injecte le script approprié. Compatible navigateurs modernes.

php importmap.php — configuration des dépendances JS

Fichier de mapping des modules ES importés depuis CDN.

<?php
// importmap.php — géré par "symfony importmap:require"
return [
    'app' => [
        'path' => './assets/app.js',
        'entrypoint' => true,
    ],
    '@hotwired/stimulus' => [
        'version' => '3.2.2',
    ],
    '@symfony/stimulus-bundle' => [
        'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js',
    ],
    'bootstrap' => [
        'version' => '5.3.3',
    ],
    'alpinejs' => [
        'version' => '3.14.0',
    ],
];
beginner command 6.3
Console Nouveauté 7.x

importmap:require

Ajoute un package npm à l'importmap via jsDelivr ou unpkg. Ex: symfony importmap:require bootstrap. Met à jour importmap.php automatiquement. Pas de node_modules nécessaire.

Cache

Pools de cache, adaptateurs, tags, invalidation, cache HTTP.

4
intermediate attribute 7.1
HTTP Performance Attribut PHP Nouveauté 7.x

#[Cache] (HTTP caching)

Attribut sur un contrôleur configurant les headers HTTP Cache-Control, Expires, ETag, Last-Modified. Intégration avec le composant HttpCache (reverse proxy PHP) ou Varnish via ESI.

intermediate concept
Performance

Cache Pools

Pools nommés configurés dans cache.yaml. Adaptateurs : filesystem, redis, memcached, apcu, array, pdo. Chaque pool a son namespace et TTL par défaut. CacheInterface ou PSR-16 CacheInterface.

php Utiliser le Cache

Mettre en cache le résultat d'un calcul coûteux.

class StatsService
{
    public function __construct(
        private readonly CacheInterface $cache,
    ) {}

    public function getMonthlyStats(int $year, int $month): array
    {
        $key = "stats_{$year}_{$month}";

        return $this->cache->get($key, function (ItemInterface $item) use ($year, $month): array {
            $item->expiresAfter(3600); // 1 heure
            $item->tag(["stats", "stats-{$year}"]);

            return $this->computeExpensiveStats($year, $month);
        });
    }

    public function invalidateYear(int $year): void
    {
        // Invalide tous les items du cache taggés "stats-2024"
        $this->cache->invalidateTags(["stats-{$year}"]);
    }
}
advanced concept
Performance

Cache Tags (invalidation)

TagAwareCacheInterface permet de tagger des items et de les invalider par tag. Exemple : invalider tous les items tagués "user-42" quand un utilisateur est modifié. Adaptateurs supportés : Redis, Memcached, filesystem.

advanced concept
Performance

Cache Warmer

Implémente CacheWarmerInterface pour pré-remplir le cache lors de cache:warmup. Appelé par symfony cache:warmup. isOptional() indique si le warmup peut être ignoré.

🕐

Clock

Abstraction du temps, NativeClock, MockClock pour les tests.

2
intermediate component 6.2
Test Bonne pratique Nouveauté 7.x

ClockInterface

Abstraction du temps système. now() retourne un DateTimeImmutable. Implémentations : NativeClock (temps réel), MockClock (temps figé pour tests), MonotonicClock (temps monotone). Injecter ClockInterface plutôt que new DateTime().

php ClockInterface — service et test

Injecter ClockInterface pour des tests déterministes.

// Service — injecte ClockInterface
class SubscriptionService
{
    public function __construct(
        private readonly ClockInterface $clock,
    ) {}

    public function isActive(Subscription $sub): bool
    {
        return $sub->getExpiresAt() > $this->clock->now();
    }

    public function createTrial(): Subscription
    {
        return new Subscription(
            createdAt: $this->clock->now(),
            expiresAt: $this->clock->now()->modify('+30 days'),
        );
    }
}

// Test — MockClock pour figer le temps
class SubscriptionServiceTest extends TestCase
{
    public function testIsActiveWithFrozenTime(): void
    {
        $clock = new MockClock('2024-06-01 12:00:00');
        $service = new SubscriptionService($clock);

        $sub = new Subscription(expiresAt: new \DateTimeImmutable('2024-12-31'));
        $this->assertTrue($service->isActive($sub));

        // Avancer le temps dans le test
        $clock->modify('+1 year');
        $this->assertFalse($service->isActive($sub));
    }
}
intermediate component 6.2
Test Nouveauté 7.x

MockClock (tests)

MockClock permet de figer ou manipuler le temps dans les tests. sleep() ou modify('+1 day') avance le temps. Élimine les tests flaky liés au temps réel.

🛠️

Configuration

Fichiers de config YAML/PHP, variables d'env, secrets, paramètres.

4
advanced component
Configuration

Config Component (TreeBuilder)

Définit la structure de configuration d'un bundle avec validation et normalisation automatiques. TreeBuilder avec scalarNode, arrayNode, booleanNode, integerNode, enumNode. Utilisé dans Configuration::getConfigTreeBuilder().

intermediate configuration 5.3
Bonne pratique Configuration DX (Developer Experience)

PHP config (kernel.php)

Les fichiers de configuration peuvent être écrits en PHP (config/packages/*.php). Syntaxe fluente avec $configurator->extension(). Autocomplétion IDE meilleure que YAML. Recommandé pour les nouveaux projets Symfony 8.

advanced component 4.4
Sécurité Configuration

Secrets Symfony

secrets:set chiffre une valeur et la stocke dans config/secrets/. En production, seule la clé publique est commitée. %env(secret:APP_SECRET)% déchiffre à l'exécution. Vault externe possible.

beginner configuration
Configuration Sécurité Bonne pratique

Variables d'environnement

Déclarées dans .env, surchargées dans .env.local (non commité). Utilisées via %env(VAR)% dans les configs. Processeurs : env(int:PORT), env(bool:FLAG), env(json:DATA), env(base64:...), env(file:...).

yaml Variables d'environnement — types et processeurs

Utilisation de processeurs de variables d'environnement dans la configuration.

# .env
APP_ENV=dev
DATABASE_URL=mysql://root:@127.0.0.1:3306/myapp
MAILER_DSN=smtp://localhost
APP_DEBUG=true
MAX_UPLOAD_SIZE=10485760
ALLOWED_ORIGINS=["https://app.example.com","https://www.example.com"]

# config/packages/framework.yaml — utilisation des processeurs
framework:
    # env(bool:...) → cast en booléen
    debug: '%env(bool:APP_DEBUG)%'

    # env(int:...) → cast en entier
    http_method_override: false

# config/packages/monolog.yaml
monolog:
    handlers:
        main:
            # env(resolve:...) → résout les vars dans la valeur
            path: '%env(resolve:LOG_PATH)%'
💻

Console

Commandes CLI, helpers, styles, progression, tableaux.

8
beginner attribute 6.0
Attribut PHP Console Bonne pratique

#[AsCommand]

Déclare une classe comme commande console. name définit la commande, description la description courte, aliases les alias, hidden masque de la liste. Remplace la méthode configure() pour ces métadonnées.

php Commande Console complète

Structure complète d'une commande Symfony avec argument, option et SymfonyStyle.

#[AsCommand(
    name: 'app:import:users',
    description: 'Importe des utilisateurs depuis un CSV',
    aliases: ['app:users:import'],
)]
class ImportUsersCommand extends Command
{
    public function __construct(
        private readonly UserImporter $importer,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addArgument('file', InputArgument::REQUIRED, 'Chemin vers le fichier CSV')
            ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Simuler sans persister')
            ->addOption('batch-size', 'b', InputOption::VALUE_REQUIRED, 'Taille des lots', 100);
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $file = $input->getArgument('file');
        $dryRun = $input->getOption('dry-run');

        $io->title('Import des utilisateurs');
        $io->note("Fichier : {$file}" . ($dryRun ? ' [DRY-RUN]' : ''));

        $count = $this->importer->import($file, $dryRun);

        $io->success("{$count} utilisateurs importés avec succès.");

        return Command::SUCCESS;
    }
}
beginner concept
Console

Arguments et Options

addArgument() définit des arguments positionnels (obligatoires ou optionnels). addOption() définit des options (--name=value ou --flag). InputArgument et InputOption avec modes : REQUIRED, OPTIONAL, IS_ARRAY.

intermediate concept
Performance Console

Commandes lazys

Avec #[AsCommand], les commandes sont enregistrées en mode lazy : l'objet n'est instancié que lorsqu'elle est effectivement appelée. Améliore significativement le temps de démarrage des grosses applications.

advanced concept
Console

Événements de commande

ConsoleEvents::COMMAND (avant exécution), ConsoleEvents::TERMINATE (après succès), ConsoleEvents::ERROR (sur exception). Permettent de logger, auditer ou modifier le comportement des commandes.

beginner command
Console DX (Developer Experience)

make:command

Génère le squelette d'une nouvelle commande console avec #[AsCommand], configure() et execute() pré-remplis. Fait partie du MakerBundle.

beginner component
Console

Progress Bar

ProgressBar($output, $max) affiche une barre de progression. start() → advance() → finish(). Personnalisable via setFormat() et setBarCharacter(). SymfonyStyle::progressStart() pour usage simplifié.

beginner component
Console DX (Developer Experience)

SymfonyStyle (io)

SymfonyStyle($input, $output) offre title(), section(), text(), note(), caution(), success(), error(), table(), listing(), ask(), confirm(), progressStart(). Sortie stylisée et cohérente.

beginner component
Console

Table Helper

Affiche des données tabulaires dans le terminal. new Table($output) → setHeaders() → addRows() → render(). Styles : default, compact, borderless, box, box-double.

🔧

Dependency Injection

Conteneur de services, autowiring, autoconfiguration, attributs DI.

13
intermediate attribute 6.1
Attribut PHP Injection de dépendances

#[AutoconfigureTag]

Applique automatiquement un tag à tous les services d'une interface ou classe abstraite. Évite de configurer le tag dans services.yaml pour chaque implémentation.

intermediate attribute 6.1
DX (Developer Experience) Injection de dépendances Attribut PHP

#[Autowire]

Précise explicitement ce qu'injecter pour un paramètre : service, paramètre de conteneur, expression ou valeur d'env. Ex: #[Autowire('%app.secret%')] ou #[Autowire(service: 'monservice')].

php #[Autowire] — injection explicite

Injecter un paramètre de conteneur, une variable d'env ou un service nommé.

class PaymentService
{
    public function __construct(
        #[Autowire('%app.stripe_key%')]
        private readonly string $stripeKey,

        #[Autowire(env: 'PAYMENT_TIMEOUT')]
        private readonly int $timeout,

        #[Autowire(service: 'monolog.logger.payment')]
        private readonly LoggerInterface $logger,
    ) {}
}
advanced attribute 6.1
Injection de dépendances Attribut PHP

#[AutowireDecorated]

Injecte automatiquement le service décoré dans un décorateur. Évite de déclarer manuellement le service inner. Utilisé avec le pattern Decorator.

intermediate attribute 6.2
Injection de dépendances Attribut PHP

#[Target]

Désambiguïse l'autowiring lorsque plusieurs services implémentent la même interface. #[Target('mailerLogger')] indique lequel des LoggerInterface injecter.

php #[Target] — désambiguïsation d'interface

Choisir lequel des services LoggerInterface injecter par son nom.

class OrderService
{
    public function __construct(
        // Injecte monolog.logger.order (nommé "orderLogger")
        #[Target('orderLogger')]
        private readonly LoggerInterface $logger,

        // Injecte monolog.logger.payment (nommé "paymentLogger")
        #[Target('paymentLogger')]
        private readonly LoggerInterface $paymentLogger,
    ) {}
}
intermediate attribute 6.2
Injection de dépendances Configuration Attribut PHP

#[When] (environnement)

Enregistre un service uniquement dans certains environnements. #[When(env: 'dev')] ou #[When(env: 'test')] évite les conditions dans services.yaml.

php #[When] — service conditionnel par environnement

Ce service n'existe que dans l'environnement dev.

#[When(env: 'dev')]
class DebugQueryLogger implements QueryLoggerInterface
{
    public function logQuery(string $sql, array $params): void
    {
        dump($sql, $params);
    }
}

// En production, ce service n'est pas enregistré du tout
// L'interface QueryLoggerInterface pointe vers une autre implémentation
beginner concept
Injection de dépendances Bonne pratique

Autoconfiguration

Le conteneur détecte automatiquement les interfaces connues (EventSubscriberInterface, CommandInterface...) et applique les tags correspondants sans configuration manuelle.

beginner concept
Injection de dépendances Bonne pratique

Autowiring

Le conteneur injecte automatiquement les dépendances en analysant les type-hints du constructeur. Actif par défaut (autowire: true dans services.yaml). Fonctionne avec interfaces si un seul service les implémente.

beginner configuration
Injection de dépendances Configuration

Binding de paramètres

La clé bind dans services.yaml mappe une valeur (paramètre, service) à un nom d'argument ou type. Pratique pour injecter la même valeur dans tous les services sans répétition.

advanced concept
Injection de dépendances

Compiler Pass

Classe ModifiantContainerBuilder pendant la compilation. Utilisation typique : collecter des services par tag et les injecter dans un service central (ex: collecte des handlers, extensions Twig).

beginner command
DX (Developer Experience) Injection de dépendances Console

debug:container

Liste tous les services du conteneur. Options utiles : --show-private, --tag=twig.extension, --type=LoggerInterface. Indispensable pour debugger l'autowiring.

advanced concept
Injection de dépendances Bonne pratique

Décorateur de service

Pattern permettant d'encapsuler un service existant sans modifier son code. Configurer avec decorates: dans services.yaml ou l'attribut #[AsDecorator]. Le service décoré est injecté via #[AutowireDecorated].

php Décorateur de service

Décorer un service existant sans modifier son code source.

#[AsDecorator(decorates: UserRepository::class)]
class CachedUserRepository implements UserRepositoryInterface
{
    public function __construct(
        #[AutowireDecorated]
        private readonly UserRepositoryInterface $inner,
        private readonly CacheInterface $cache,
    ) {}

    public function findById(int $id): ?User
    {
        return $this->cache->get(
            "user_{$id}",
            fn() => $this->inner->findById($id)
        );
    }
}
advanced concept
Performance Injection de dépendances

Service Locator

Conteneur léger injectant un sous-ensemble de services de façon lazy. Utile dans les factories ou handlers qui n'ont besoin que d'un service à la fois parmi plusieurs.

advanced concept
Injection de dépendances Performance

Services lazys

Un service marqué lazy: true est instancié via un proxy uniquement au premier appel effectif. Réduit le temps de bootstrap pour les services coûteux non toujours utilisés.

🗄️

Doctrine ORM

Entités, repositories, relations, migrations, lifecycle callbacks.

8
intermediate concept

DQL

Doctrine Query Language : langage de requête orienté entités. SELECT u FROM App\Entity\User u WHERE u.email = :email. Supporte JOIN, GROUP BY, HAVING, sous-requêtes. Compilé en SQL selon le driver.

intermediate concept

Embeddables

#[ORM\Embeddable] et #[ORM\Embedded] permettent d'intégrer un objet value-object dans une entité. Les colonnes de l'embeddable sont ajoutées à la table de l'entité parente avec un préfixe.

advanced concept

Héritage de mapping

Trois stratégies : SINGLE_TABLE (discriminator column, une table), JOINED (une table par classe, JOINs), TABLE_PER_CLASS (une table complète par classe). Configuré via #[ORM\InheritanceType] et #[ORM\DiscriminatorMap].

intermediate attribute
Attribut PHP

Lifecycle Callbacks

#[ORM\PrePersist], #[ORM\PostPersist], #[ORM\PreUpdate], #[ORM\PostUpdate], #[ORM\PreRemove], #[ORM\PostRemove], #[ORM\PostLoad]. Méthodes de l'entité appelées automatiquement par Doctrine.

beginner attribute
Attribut PHP Bonne pratique

Mapping d'entité par attributs

#[ORM\Entity], #[ORM\Column], #[ORM\Id], #[ORM\GeneratedValue], #[ORM\ManyToOne], #[ORM\OneToMany], #[ORM\ManyToMany], #[ORM\JoinColumn]. Remplace le XML/YAML depuis Doctrine 2.9.

php Entité Doctrine complète

Exemple d'entité avec toutes les annotations courantes.

#[ORM\Entity(repositoryClass: ArticleRepository::class)]
#[ORM\Table(name: 'articles')]
#[ORM\HasLifecycleCallbacks]
class Article
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private string $title;

    #[ORM\Column(type: Types::TEXT)]
    private string $content;

    #[ORM\Column]
    private \DateTimeImmutable $createdAt;

    #[ORM\ManyToOne(targetEntity: User::class)]
    #[ORM\JoinColumn(nullable: false)]
    private User $author;

    #[ORM\ManyToMany(targetEntity: Tag::class)]
    #[ORM\JoinTable(name: 'article_tags')]
    private Collection $tags;

    #[ORM\PrePersist]
    public function onPrePersist(): void
    {
        $this->createdAt = new \DateTimeImmutable();
    }
}
beginner command
Bonne pratique

Migrations Doctrine

doctrine:migrations:diff génère la migration depuis le diff schéma/entités. doctrine:migrations:migrate l'applique. Chaque migration a up() et down(). Versionnée en base dans la table doctrine_migration_versions.

intermediate concept

Relations Doctrine

OneToOne, OneToMany/ManyToOne, ManyToMany. Chaque relation a un côté owning (JoinColumn/JoinTable) et un côté inverse (mappedBy/inversedBy). Le côté owning est celui persisté.

intermediate component

Repository et QueryBuilder

ServiceEntityRepository fournit find(), findBy(), findOneBy(). createQueryBuilder() retourne un QueryBuilder fluent pour des requêtes complexes avec jointures, conditions, tri, pagination.

php QueryBuilder — requête complexe

Requête avec JOIN, WHERE, pagination dans un Repository.

class ArticleRepository extends ServiceEntityRepository
{
    public function findPublishedByTag(string $tagSlug, int $page = 1, int $limit = 20): array
    {
        return $this->createQueryBuilder('a')
            ->join('a.tags', 't')
            ->join('a.author', 'u')
            ->addSelect('u', 't')
            ->where('t.slug = :tag')
            ->andWhere('a.publishedAt IS NOT NULL')
            ->andWhere('a.publishedAt <= :now')
            ->setParameter('tag', $tagSlug)
            ->setParameter('now', new \DateTimeImmutable())
            ->orderBy('a.publishedAt', 'DESC')
            ->setFirstResult(($page - 1) * $limit)
            ->setMaxResults($limit)
            ->getQuery()
            ->getResult();
    }
}
📡

Event Dispatcher

Événements, listeners, subscribers, stoppable events, attribut #[AsEventListener].

3
beginner attribute 6.0
Attribut PHP Bonne pratique

#[AsEventListener]

Enregistre une méthode comme listener d'événement sans implémenter d'interface. #[AsEventListener(event: KernelEvents::REQUEST, method: 'onRequest', priority: 10)].

php #[AsEventListener] — listener de requête

Intercepter chaque requête pour logger ou modifier son comportement.

class RequestLocaleListener
{
    #[AsEventListener(event: KernelEvents::REQUEST, priority: 20)]
    public function onRequest(RequestEvent $event): void
    {
        $request = $event->getRequest();
        $locale = $request->query->get('_locale', 'fr');
        $request->setLocale($locale);
    }

    #[AsEventListener(event: KernelEvents::EXCEPTION)]
    public function onException(ExceptionEvent $event): void
    {
        $exception = $event->getThrowable();
        if ($exception instanceof NotFoundHttpException) {
            // Journalisation personnalisée des 404
        }
    }
}
intermediate concept

Événements stoppables

Un événement implémentant StoppableEventInterface peut être stoppé via stopPropagation(). Les listeners suivants ne sont pas appelés. isPropagationStopped() permet de vérifier l'état.

beginner concept

Event Subscriber

Implémenter EventSubscriberInterface avec getSubscribedEvents() retournant un tableau events → méthodes. Auto-configuré par le conteneur. Préférer #[AsEventListener] pour les nouveaux projets.

📝

Forms

Formulaires, types, validation, transformateurs de données, events.

6
advanced component

CollectionType

Gère une collection de sous-formulaires. allow_add / allow_delete permettent d'ajouter/supprimer dynamiquement des éléments en JS. Utilisé avec prototype pour le template JS.

advanced concept

Data Transformer

Convertit entre la vue (ex: string "Paris") et le modèle (ex: objet City). Deux types : Model Transformers (modèle ↔ normalisé) et View Transformers (normalisé ↔ vue HTML).

advanced concept

Form Events

FormEvents::PRE_SET_DATA, POST_SET_DATA, PRE_SUBMIT, SUBMIT, POST_SUBMIT permettent de modifier dynamiquement la structure du formulaire selon les données (ex: champs conditionnels).

intermediate component

Form Type

Classe héritant de AbstractType définissant un formulaire. buildForm() ajoute les champs, configureOptions() définit data_class, validation_groups, etc. Réutilisable et composable.

php Form Type complet

Type de formulaire Symfony avec validation et data_class.

class ArticleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('title', TextType::class, [
                'label' => 'Titre',
                'constraints' => [
                    new NotBlank(),
                    new Length(min: 5, max: 255),
                ],
            ])
            ->add('content', TextareaType::class, ['label' => 'Contenu'])
            ->add('category', EntityType::class, [
                'class' => Category::class,
                'choice_label' => 'name',
            ])
            ->add('publishedAt', DateType::class, [
                'widget' => 'single_text',
                'required' => false,
            ])
            ->add('save', SubmitType::class, ['label' => 'Publier']);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Article::class,
        ]);
    }
}
beginner concept

Types de champs built-in

TextType, EmailType, PasswordType, IntegerType, NumberType, CheckboxType, ChoiceType, EntityType, DateType, DateTimeType, FileType, HiddenType, CollectionType, RepeatedType, SubmitType...

intermediate concept
Bonne pratique

Validation de formulaire

Les contraintes Validator appliquées sur le data_class sont vérifiées lors de isValid(). Groupes de validation via validation_groups. Contraintes directement sur les champs via constraints: option.

🌐

HTTP Foundation

Request, Response, Session, Cookies, UploadedFile.

7
beginner attribute 7.1
DX (Developer Experience) Attribut PHP Nouveauté 7.x

#[MapQueryString]

Mappe toute la query string vers un objet DTO typé en un seul paramètre de contrôleur. Inclut validation automatique. Variante de #[MapRequestPayload] pour les GET.

intermediate component
Performance HTTP

BinaryFileResponse

Envoie un fichier au client avec support des headers Range (téléchargement partiel), Content-Disposition, Cache-Control. sendfile() kernel optionnel pour X-Accel-Redirect (Nginx).

intermediate component
HTTP

File Upload

Les fichiers uploadés sont accessibles via $request->files, retournant des instances de UploadedFile. Méthodes move(), guessExtension(), getMimeType(), isValid().

beginner component
HTTP Bonne pratique

JsonResponse

Réponse HTTP qui sérialise automatiquement un tableau ou objet PHP en JSON et positionne le header Content-Type: application/json. Méthode raccourci $this->json() dans AbstractController.

beginner component
HTTP

Objet Request

Encapsule la requête HTTP. Donne accès à query ($request->query), POST ($request->request), fichiers ($request->files), headers ($request->headers), cookies, session, attributs, méthode, URL, format attendu.

beginner component
HTTP

Objet Response

Représente la réponse HTTP renvoyée au client. Contient le statut HTTP, les headers, le body. Sous-classes : JsonResponse, RedirectResponse, BinaryFileResponse, StreamedResponse.

beginner component
HTTP

Session

Gestion des sessions HTTP. RequestStack::getSession() retourne la session courante. Supports : PHP natif, PDO, Redis, Memcached. Flash messages via addFlash() / getFlashBag().

⚙️

HTTP Kernel

Cycle de vie du kernel, events, sous-requêtes, résolution des contrôleurs.

4
advanced concept
DX (Developer Experience) Injection de dépendances

Argument Resolver

Résout les arguments d'un contrôleur avant son appel. Des resolvers built-in gèrent Request, services, entités (#[MapEntity]), payload (#[MapRequestPayload])... Extensible via ValueResolverInterface.

intermediate concept
HTTP

Cycle de vie du Kernel

handle(Request) déclenche la séquence : kernel.request → kernel.controller → kernel.controller_arguments → contrôleur → kernel.view → kernel.response → kernel.terminate. Chaque étape est un événement interceptable.

intermediate concept
HTTP

Événements Kernel

KernelEvents::REQUEST, CONTROLLER, CONTROLLER_ARGUMENTS, VIEW, RESPONSE, FINISH_REQUEST, EXCEPTION, TERMINATE. Permettent d'intercepter et modifier la requête ou la réponse à chaque étape.

advanced concept
HTTP

Sous-requêtes

HttpKernelInterface::handle() peut être appelé récursivement avec SUB_REQUEST pour inclure le résultat d'un autre contrôleur dans la réponse. Utilisé par l'ESI et render() Twig.

🔑

Lock

Verrous distribués, stores Redis/Semaphore/Flock, locks partagés.

1
intermediate component 3.4
Asynchrone

Lock Factory

LockFactory::createLock('resource-name', ttl) crée un verrou. acquire() → release(). Stores : Redis, Semaphore (Unix), Flock (fichier), PDO, ZooKeeper, MongoDB. Verrous partagés (read) et exclusifs (write).

✉️

Mailer

Envoi d'emails, MIME, pièces jointes, transports, tests.

3
beginner component
Bonne pratique

Email basé sur Twig

TemplatedEmail() → htmlTemplate() / textTemplate() utilise Twig pour le corps de l'email. Inlining CSS automatique avec Twig Inky. Pièces jointes via attachFromPath() ou embed().

beginner component

Envoyer un email

MailerInterface::send(Email $email). Email() → from() → to() → subject() → text() → html(). Transport configuré via MAILER_DSN : smtp://, gmail+smtp://, sendgrid+api://, null://, in-memory://

intermediate concept
Performance Asynchrone

Mailer asynchrone

En configurant Messenger, les emails sont envoyés de façon asynchrone. MAILER_DSN reste synchrone mais le transport Messenger délègue l'envoi à un worker. Routing automatique via symfony/mailer.

📨

Messenger

Bus de messages, handlers, transports, retry, messages échoués.

7
beginner attribute 6.0
Attribut PHP Bonne pratique Asynchrone

#[AsMessageHandler]

Enregistre une classe ou méthode __invoke() comme handler d'un message. Le type du paramètre de __invoke() détermine le message géré. Un handler peut gérer plusieurs types via plusieurs méthodes.

php Message + Handler Messenger

Message immuable et son handler asynchrone.

// Message (DTO immuable)
final class SendWelcomeEmailMessage
{
    public function __construct(
        public readonly int $userId,
        public readonly string $email,
    ) {}
}

// Handler
#[AsMessageHandler]
class SendWelcomeEmailHandler
{
    public function __construct(
        private readonly MailerInterface $mailer,
        private readonly UserRepository $users,
    ) {}

    public function __invoke(SendWelcomeEmailMessage $message): void
    {
        $user = $this->users->find($message->userId);

        $email = (new TemplatedEmail())
            ->to($message->email)
            ->subject('Bienvenue !')
            ->htmlTemplate('emails/welcome.html.twig')
            ->context(['user' => $user]);

        $this->mailer->send($email);
    }
}

// Dispatch depuis un contrôleur (envoi asynchrone)
$this->bus->dispatch(new SendWelcomeEmailMessage($user->getId(), $user->getEmail()));
beginner command
Console Asynchrone

messenger:consume

Lance un worker consommant les messages d'un ou plusieurs transports. Options : --limit (nombre de messages), --time-limit (durée), --memory-limit, --sleep (attente entre messages). Supervisord recommandé en production.

advanced concept
Asynchrone

Middleware Messenger

Couche interceptant chaque message avant/après dispatch. Built-in : SendMessageMiddleware, HandleMessageMiddleware, ValidationMiddleware, DoctrineTransactionMiddleware. Extensible via MiddlewareInterface.

intermediate configuration
Asynchrone

Retry Strategy

En cas d'échec de traitement, Messenger réessaie le message selon la stratégie configurée (max_retries, delay, multiplier). Après épuisement, le message va dans le transport failure (failed queue).

intermediate configuration
Asynchrone

Routing de messages

routing dans messenger.yaml mappe des classes de messages à des transports. Plusieurs transports possibles par message. Un message non routé est traité de façon synchrone.

yaml Configuration routing Messenger

Routage des messages vers les transports dans messenger.yaml.

# config/packages/messenger.yaml
framework:
    messenger:
        transports:
            async:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                retry_strategy:
                    max_retries: 3
                    delay: 1000
                    multiplier: 2
            async_priority_high:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                options:
                    queue_name: high_priority
            failed:
                dsn: 'doctrine://default?queue_name=failed'

        routing:
            App\Message\SendWelcomeEmailMessage: async
            App\Message\ProcessPaymentMessage: async_priority_high
            App\Message\GenerateReportMessage: async
advanced concept
Asynchrone

Stamps et Enveloppes

bus->dispatch(new MyMessage(), [new DelayStamp(5000)]). Stamps disponibles : DelayStamp, TransportNamesStamp, ReceivedStamp, HandledStamp, SentStamp, RedeliveryStamp, AmqpStamp...

intermediate concept
Asynchrone

Transports Messenger

AMQP (RabbitMQ), Redis Streams, Doctrine (table BDD), SQS (Amazon), Beanstalkd, In-Memory (sync). Configurés via DSN dans messenger.yaml. Chaque transport peut avoir son propre routing.

🚦

Rate Limiter

Limitation de débit, politiques token bucket, sliding window.

2
intermediate attribute 7.2
Sécurité Attribut PHP Nouveauté 7.x Nouveauté 8.x

#[RateLimit]

Attribut sur un contrôleur limitant le débit directement sans code dans l'action. #[RateLimit(policy: 'api', limit: 60, interval: '1 minute')] → 429 automatique si dépassé.

php #[RateLimit] sur un endpoint API

Limiter les appels à 10 par minute sur un endpoint sensible.

// config/packages/rate_limiter.yaml
// framework:
//   rate_limiter:
//     api_login:
//       policy: sliding_window
//       limit: 10
//       interval: '1 minute'

#[RateLimit(policy: 'api_login')]
#[Route('/api/auth/login', methods: ['POST'])]
public function login(#[MapRequestPayload] LoginInput $input): JsonResponse
{
    // Si la limite est dépassée → 429 Too Many Requests automatique
    // Header Retry-After ajouté à la réponse
}
intermediate concept 5.2
Sécurité Performance

Politiques de Rate Limiting

Quatre stratégies : token_bucket (tokens rechargés progressivement), fixed_window (N req/période fixe), sliding_window (fenêtre glissante), no_limit. Configuré dans rate_limiter.yaml.

🗺️

Routing

Système de routage HTTP, attributs de routes, génération d'URL.

13
beginner attribute 6.2
Attribut PHP DX (Developer Experience)

#[MapEntity]

Remplace @ParamConverter. Résout automatiquement une entité Doctrine depuis les paramètres de route. Supporte les expressions et le mapping de champs personnalisés.

beginner attribute 6.3
DX (Developer Experience) Attribut PHP Nouveauté 7.x

#[MapQueryParameter]

Injecte et type-cast automatiquement un paramètre de query string dans un argument de contrôleur. Gère la validation de type et les tableaux. Ex: #[MapQueryParameter] int $page.

php #[MapQueryParameter] — paramètre typé depuis la query string

Injection automatique et castée d'un paramètre de query string.

#[Route('/articles', name: 'article_list')]
public function list(
    #[MapQueryParameter] int $page = 1,
    #[MapQueryParameter] int $perPage = 20,
    #[MapQueryParameter] string $sort = 'createdAt',
    #[MapQueryParameter] array $tags = [],
): Response {
    // GET /articles?page=2&perPage=10&tags[]=php&tags[]=symfony
}
beginner attribute 6.3
DX (Developer Experience) Attribut PHP Nouveauté 7.x

#[MapRequestPayload]

Désérialise et valide automatiquement le corps d'une requête JSON/form dans un DTO typé. Si la validation échoue, une 422 est retournée automatiquement.

php #[MapRequestPayload] — DTO depuis le JSON body

Désérialise et valide automatiquement le corps JSON dans un DTO.

// DTO avec contraintes de validation
class CreateArticleInput
{
    #[Assert\NotBlank]
    #[Assert\Length(min: 5, max: 255)]
    public string $title;

    #[Assert\NotBlank]
    public string $content;
}

// Contrôleur
#[Route('/articles', methods: ['POST'])]
public function create(
    #[MapRequestPayload] CreateArticleInput $input
): Response {
    // Si validation échoue → 422 Unprocessable Entity automatiquement
    // $input->title et $input->content sont déjà validés
}
beginner attribute 6.0
Bonne pratique Attribut PHP

#[Route] sur la classe (préfixe)

Placer #[Route('/prefix')] sur la classe du contrôleur préfixe automatiquement toutes les routes des méthodes de cette classe. Pratique pour les ressources CRUD.

php Préfixe de route sur la classe

Toutes les routes du contrôleur seront préfixées par /api/articles.

#[Route('/api/articles')]
class ArticleController extends AbstractController
{
    #[Route('', name: 'article_list', methods: ['GET'])]
    public function list(): Response { /* GET /api/articles */ }

    #[Route('/{id}', name: 'article_show', methods: ['GET'])]
    public function show(int $id): Response { /* GET /api/articles/42 */ }

    #[Route('', name: 'article_create', methods: ['POST'])]
    public function create(): Response { /* POST /api/articles */ }
}
intermediate concept 6.1

Alias de routes

Les alias de routes permettent de référencer une route sous plusieurs noms. Utile lors de renommages progressifs pour maintenir la compatibilité des URLs générées.

beginner attribute 6.0
Bonne pratique Attribut PHP

Attribut #[Route]

Déclare une route directement sur une méthode de contrôleur via l'attribut PHP #[Route]. Remplace les annotations Doctrine. Supporte les paramètres name, path, methods, requirements, defaults, host, schemes, condition, priority.

php Route simple avec attribut

Déclaration d'une route GET sur une méthode de contrôleur.

#[Route('/articles/{id}', name: 'article_show', methods: ['GET'])]
public function show(int $id): Response
{
    // ...
}
advanced concept

Condition de route (expression)

Le paramètre condition accepte une expression ExpressionLanguage évaluée à l'exécution. Accès à request, env et context. Ex: condition: "request.headers.get('Accept') matches '/json/'".

beginner command
Console DX (Developer Experience)

debug:router

Commande console qui liste toutes les routes de l'application avec leurs méthodes, chemins, contrôleurs. Supporte la recherche par nom et l'affichage détaillé d'une route.

beginner component
Bonne pratique

Génération d'URL (UrlGenerator)

Le service router (UrlGeneratorInterface) génère des URLs depuis un nom de route et ses paramètres. generateUrl() en contrôleur, path()/url() en Twig. Supporte les URLs absolues et relatives.

intermediate concept

Priorité de routes

Le paramètre priority de #[Route] permet de définir l'ordre d'évaluation des routes. Plus la valeur est haute, plus la route est évaluée en premier. Utile quand deux routes peuvent correspondre à un même URL.

beginner concept 2.0

Requirements de routes (regex)

Les requirements contraignent les valeurs des paramètres de route via des expressions régulières. Ex: requirements: ['id' => '\d+']. Symfony propose des constantes prêtes à l'emploi : Requirement::DIGITS, ASCII_SLUG, etc.

advanced concept

Routage par sous-domaine

Le paramètre host de #[Route] permet de restreindre une route à un sous-domaine spécifique. Les variables du host peuvent être capturées comme paramètres de route.

beginner concept

Valeurs par défaut de routes

Les defaults permettent de définir des valeurs par défaut pour les paramètres optionnels d'une route ou d'injecter des variables dans le contrôleur (ex: _format, _locale).

Scheduler

Tâches planifiées avec #[AsPeriodicTask] et #[AsCronTask].

2
beginner attribute 6.3
Asynchrone Nouveauté 7.x Attribut PHP

#[AsCronTask]

Planifie une tâche via une expression cron. #[AsCronTask('0 * * * *')] pour toutes les heures. Supporte les timezones. Plus expressif que #[AsPeriodicTask] pour des planifications complexes.

php #[AsCronTask] — tâche planifiée

Exécution automatique d'une tâche selon une expression cron.

#[AsCronTask('0 2 * * *', timezone: 'Europe/Paris')]
class DailyReportHandler
{
    public function __construct(
        private readonly ReportGenerator $generator,
        private readonly MailerInterface $mailer,
    ) {}

    public function __invoke(): void
    {
        $report = $this->generator->generateDaily();
        $this->mailer->send(/* email du rapport */);
    }
}

// Lancer le scheduler worker :
// symfony console messenger:consume scheduler_default
beginner attribute 6.3
Attribut PHP Asynchrone Nouveauté 7.x

#[AsPeriodicTask]

Planifie l'exécution périodique d'un message Messenger. #[AsPeriodicTask(frequency: '1 hour', from: 'now')] sur un handler. S'exécute via messenger:consume scheduler.

🔒

Security

Authentification, autorisation, firewalls, voters, CSRF.

11
beginner attribute 6.0
Attribut PHP Sécurité

#[IsGranted]

Attribut sur un contrôleur ou méthode déclarant l'autorisation requise. Lève une AccessDeniedException si la condition n'est pas remplie. Ex: #[IsGranted('ROLE_ADMIN')] ou #[IsGranted('edit', 'post')].

php #[IsGranted] sur un contrôleur

Protéger une action de contrôleur avec un rôle ou un voter.

class ArticleController extends AbstractController
{
    // Exige le rôle ROLE_ADMIN
    #[IsGranted('ROLE_ADMIN')]
    #[Route('/admin/articles', name: 'admin_articles')]
    public function adminList(): Response { /* ... */ }

    // Utilise le ArticleVoter avec l'objet courant
    #[IsGranted('edit', subject: 'article')]
    #[Route('/articles/{id}/edit', name: 'article_edit')]
    public function edit(Article $article): Response { /* ... */ }

    // Message d'erreur personnalisé
    #[IsGranted('ROLE_EDITOR', message: 'Accès réservé aux éditeurs.')]
    #[Route('/articles/create', name: 'article_create')]
    public function create(): Response { /* ... */ }
}
beginner configuration
Configuration Sécurité

Access Control (security.yaml)

Liste de règles dans security.yaml matchant des patterns d'URL. Chaque règle peut exiger un rôle, une IP, un canal (HTTPS) ou une expression. Évaluées dans l'ordre, la première qui match est appliquée.

advanced component 5.3
Sécurité

Authenticator

Remplace les Guard Authenticators. Implémente AuthenticatorInterface : supports(), authenticate() → Passport, onAuthenticationSuccess(), onAuthenticationFailure(). Plus modulaire grâce aux Badges.

php Authenticator JWT personnalisé

Authentification sans état par token Bearer JWT.

class JwtAuthenticator extends AbstractAuthenticator
{
    public function supports(Request $request): ?bool
    {
        return $request->headers->has('Authorization')
            && str_starts_with($request->headers->get('Authorization'), 'Bearer ');
    }

    public function authenticate(Request $request): Passport
    {
        $token = substr($request->headers->get('Authorization'), 7);
        $payload = $this->jwtDecoder->decode($token); // votre logique

        return new SelfValidatingPassport(
            new UserBadge($payload['email'])
        );
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        return null; // continue la requête
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        return new JsonResponse(['error' => 'Invalid token'], 401);
    }
}
intermediate component
Sécurité

CSRF Protection

CsrfTokenManagerInterface génère et valide des tokens anti-CSRF. Intégré automatiquement dans les Form Symfony. Pour les routes custom, utiliser isCsrfTokenValid() dans le contrôleur.

advanced concept
Sécurité

Expression Language dans la sécurité

Les expressions permettent des règles d'autorisation complexes. Dans #[IsGranted] ou access_control: is_granted('ROLE') and request.isSecure(). Accès à user, object, token, trust_resolver.

intermediate configuration
Sécurité Configuration

Firewall

Zone de sécurité définie dans security.yaml. Chaque firewall a son pattern d'URL, ses authenticateurs, son provider d'utilisateurs et sa configuration de session. Ex: firewalls.api (stateless JWT) + firewalls.main.

beginner configuration
Sécurité

Hiérarchie de rôles

role_hierarchy dans security.yaml définit quels rôles héritent d'autres rôles. Ex: ROLE_ADMIN: [ROLE_USER]. Un utilisateur ROLE_ADMIN possède automatiquement ROLE_USER.

beginner component
Bonne pratique Sécurité

Password Hasher

UserPasswordHasherInterface hash les mots de passe selon l'algorithme configuré (bcrypt, argon2id, sodium). hashPassword() + isPasswordValid(). Supporte le rehashage automatique (needsRehash()).

intermediate component
Sécurité

Remember Me

Fonctionnalité "se souvenir de moi" via un cookie persistant. Activé par RememberMeBadge dans le Passport. Configurable dans le firewall : lifetime, secure, samesite.

advanced concept 5.3
Sécurité

Security Passport & Badges

Un Passport contient les credentials (UserBadge + PasswordCredentials ou SelfValidatingPassport) et des badges optionnels : CsrfTokenBadge, RememberMeBadge, PasswordUpgradeBadge.

intermediate component
Sécurité Bonne pratique

Voter

Centralise la logique d'autorisation pour une ressource. Hérite de Voter<TAttribute, TSubject>. voteOnAttribute() retourne true/false. Déclenché via $this->denyAccessUnlessGranted() ou #[IsGranted].

php Voter — autorisation sur une ressource

Voter centralisant les droits d'accès à un Article.

class ArticleVoter extends Voter
{
    const EDIT = 'edit';
    const DELETE = 'delete';

    protected function supports(string $attribute, mixed $subject): bool
    {
        return in_array($attribute, [self::EDIT, self::DELETE])
            && $subject instanceof Article;
    }

    protected function voteOnAttribute(
        string $attribute,
        mixed $subject,
        TokenInterface $token
    ): bool {
        $user = $token->getUser();
        if (!$user instanceof User) {
            return false;
        }

        return match($attribute) {
            self::EDIT   => $subject->getAuthor() === $user,
            self::DELETE => $subject->getAuthor() === $user
                         || $this->security->isGranted('ROLE_ADMIN'),
            default      => false,
        };
    }
}
🔄

Serializer

Normalisation, dénormalisation, encodage JSON/XML, groupes de sérialisation.

5
intermediate attribute
Attribut PHP Performance

#[MaxDepth]

Limite la profondeur de sérialisation des relations circulaires ou profondes. Nécessite l'activation de ENABLE_MAX_DEPTH dans le contexte. Évite les boucles infinies sans configuration lourde.

beginner attribute
Attribut PHP

#[SerializedName]

Définit le nom utilisé dans la représentation sérialisée indépendamment du nom de la propriété PHP. Ex: #[SerializedName('firstName')] sur une propriété $first_name.

advanced attribute 6.3
Nouveauté 7.x Attribut PHP

#[SerializedPath]

Mappe une propriété PHP à un chemin imbriqué dans la structure JSON/XML. Ex: #[SerializedPath('[data][user][name]')] pour accéder à data.user.name depuis une propriété $name.

intermediate concept

Groupes de sérialisation

#[Groups(['read', 'write'])] sur les propriétés. Passer les groupes dans le contexte du serializer ou de l'ApiResource. Permet d'exposer différentes propriétés selon le contexte (lecture, écriture, sous-ressource).

php Groupes de sérialisation

Exposer des champs différents selon le contexte de sérialisation.

class User
{
    #[Groups(['user:read', 'user:list'])]
    public int $id;

    #[Groups(['user:read', 'user:list', 'user:write'])]
    public string $name;

    #[Groups(['user:read', 'user:write'])]
    #[SerializedName('emailAddress')]
    public string $email;

    // Non exposé en lecture (mot de passe hashé)
    #[Groups(['user:write'])]
    public string $password;

    // Uniquement dans la vue détail, pas dans la liste
    #[Groups(['user:read'])]
    public array $roles;
}

// Utilisation dans un contrôleur
$json = $this->serializer->serialize($user, 'json', [
    'groups' => ['user:read'],
]);
advanced component

Normalizer personnalisé

Implémenter NormalizerInterface / DenormalizerInterface pour gérer la sérialisation d'un type spécifique. supportsNormalization() décide si le normalizer prend en charge un objet donné.

🧪

Testing

Tests fonctionnels, unitaires, WebTestCase, KernelTestCase, mocks.

5
intermediate component
Test

Crawler (DomCrawler)

API de traversal HTML/XML dans les tests fonctionnels. filter('h1'), filterXPath(), text(), attr(), link(), form(). Permet de vérifier le contenu HTML des réponses et de naviguer dans les pages.

beginner component
Test

KernelTestCase

Charge le kernel Symfony sans serveur HTTP. self::getContainer() donne accès aux services. Utilisé pour tester des services, repositories, commandes sans couche HTTP.

intermediate component
HTTP Test

MockHttpClient

MockHttpClient + MockResponse simule des réponses HTTP sans appel réseau. Pratique pour tester les services qui appellent des APIs externes. Vérification des requêtes envoyées via MockResponse::getRequestOptions().

intermediate concept
Test

Mocking de services en test

self::getContainer()->set(MyService::class, $mock) remplace un service dans le conteneur de test. Combiné avec createMock() de PHPUnit. Pratique pour mocker les appels externes (API, Mailer, HttpClient).

beginner component
Test Bonne pratique

WebTestCase

Classe de base pour les tests fonctionnels HTTP. createClient() retourne un Client simulant un navigateur. $client->request('GET', '/'), $client->submitForm(), $crawler->filter(). Vérifie les réponses HTTP.

php Test fonctionnel HTTP

Test d'une API REST avec WebTestCase.

class ArticleApiTest extends WebTestCase
{
    private KernelBrowser $client;

    protected function setUp(): void
    {
        $this->client = static::createClient();
    }

    public function testGetArticleList(): void
    {
        $this->client->request('GET', '/api/articles', [], [], [
            'HTTP_ACCEPT' => 'application/json',
        ]);

        $this->assertResponseIsSuccessful();
        $this->assertResponseHeaderSame('Content-Type', 'application/json');

        $data = json_decode($this->client->getResponse()->getContent(), true);
        $this->assertIsArray($data);
    }

    public function testCreateArticleRequiresAuth(): void
    {
        $this->client->request('POST', '/api/articles', [], [], [
            'CONTENT_TYPE' => 'application/json',
        ], json_encode(['title' => 'Test']));

        $this->assertResponseStatusCodeSame(401);
    }
}
🌍

Translation

Catalogues de traduction, pluralisation, format ICU, extraction automatique.

3
beginner concept

Catalogues de traduction

Fichiers YAML/XLIFF dans translations/. Nommés [domaine].[locale].yaml (ex: messages.fr.yaml). $translator->trans('key', [], 'domain', 'fr'). Hiérarchie de fallback locale.

beginner command
Console DX (Developer Experience)

Extraction automatique (translation:extract)

translation:extract --force fr analyse le code source et les templates Twig pour extraire les clés de traduction et les ajouter aux fichiers de catalogue manquants.

intermediate concept

Format ICU

Format de message internationalisé standard. Support de la pluralisation {count, plural, one{# item} other{# items}}, des genres, des sélections. Actif via le format +intl-icu dans le nom de fichier.

🌿

Twig

Moteur de templates, extensions, filtres, fonctions, héritage.

4
intermediate component 7.0
Nouveauté 7.x DX (Developer Experience)

Composants Twig (UX)

#[AsTwigComponent] marque une classe PHP comme composant Twig réutilisable. Props automatiquement mappées, template dans templates/components/. Live Components ajoutent l'interactivité sans JS custom.

intermediate component
Bonne pratique

Extension Twig

Hériter de AbstractExtension pour ajouter des filtres (getFilters()), fonctions (getFunctions()), tests, opérateurs, globals et node visitors. Taggé automatiquement twig.extension via autoconfiguration.

php Extension Twig personnalisée

Filtre et fonction Twig custom avec autoconfiguration.

// Taggé automatiquement "twig.extension" par autoconfiguration
class AppExtension extends AbstractExtension
{
    public function getFilters(): array
    {
        return [
            new TwigFilter('price', [$this, 'formatPrice']),
            new TwigFilter('truncate', [$this, 'truncate'], ['is_safe' => ['html']]),
        ];
    }

    public function getFunctions(): array
    {
        return [
            new TwigFunction('initials', [$this, 'getInitials']),
        ];
    }

    public function formatPrice(float $amount, string $currency = 'EUR'): string
    {
        return number_format($amount, 2, ',', ' ') . ' ' . $currency;
    }

    public function truncate(string $text, int $length = 100): string
    {
        return mb_strlen($text) > $length
            ? mb_substr($text, 0, $length) . '…'
            : $text;
    }

    public function getInitials(string $name): string
    {
        return implode('', array_map(
            fn($word) => mb_strtoupper(mb_substr($word, 0, 1)),
            explode(' ', $name)
        ));
    }
}
beginner concept

Fonctions et filtres Twig utiles

Fonctions : path(), url(), asset(), is_granted(), csrf_token(), render(), include(), dump(). Filtres : |date, |trans, |format_currency, |format_number, |escape, |raw, |slice, |json_encode, |yaml_encode.

beginner concept
Bonne pratique

Héritage de templates

{% extends 'base.html.twig' %} hérite d'un template parent. {% block content %}{% endblock %} définit des zones remplaçables. {{ parent() }} inclut le contenu du bloc parent.

Validator

Contraintes de validation, groupes, contraintes personnalisées.

5
intermediate component 6.1
Nouveauté 7.x

Contrainte Compound

Regroupe plusieurs contraintes en une seule contrainte réutilisable. Hériter de Compound et implémenter getConstraints(). Ex: #[PasswordStrength] = NotBlank + Length(min:8) + Regex.

advanced concept

Contrainte personnalisée

Créer une classe Constraint (avec message) et un ConstraintValidator (avec validate()). Utiliser $this->context->buildViolation()->addViolation() pour signaler une erreur.

php Contrainte de validation personnalisée

Créer une contrainte #[IsSlug] vérifiant le format d'un slug.

// La contrainte (métadonnée)
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class IsSlug extends Constraint
{
    public string $message = 'La valeur "{{ value }}" n\'est pas un slug valide.';
}

// Le validateur (logique)
class IsSlugValidator extends ConstraintValidator
{
    public function validate(mixed $value, Constraint $constraint): void
    {
        if (null === $value || '' === $value) {
            return;
        }

        if (!preg_match('/^[a-z0-9]+(?:-[a-z0-9]+)*$/', $value)) {
            $this->context->buildViolation($constraint->message)
                ->setParameter('{{ value }}', $value)
                ->addViolation();
        }
    }
}

// Utilisation
class Article
{
    #[IsSlug]
    #[Assert\Length(max: 255)]
    private string $slug;
}
intermediate component 6.1

Contrainte Sequentially

Exécute des contraintes l'une après l'autre et s'arrête à la première violation. Évite des messages d'erreur multiples et redondants (ex: vérifier NotBlank avant Email).

beginner concept

Contraintes built-in

NotBlank, NotNull, Length, Email, Url, Regex, Range, Choice, Count, UniqueEntity, Valid, All, Collection, Type, IsTrue, IsFalse, Positive, Negative, Uuid, Iban, Bic, Isbn, Issn, Cidr, Hostname...

intermediate concept

Groupes de validation

Les contraintes peuvent être assignées à des groupes. validator->validate($object, null, ['creation']) n'exécute que les contraintes du groupe "creation". GroupSequence permet un ordre strict.

🔀

Workflow

Machine à états, transitions, guards, events de workflow.

3
intermediate concept

Événements Workflow

workflow.[name].guard.[transition], workflow.[name].leave.[place], workflow.[name].transition.[transition], workflow.[name].enter.[place], workflow.[name].announce.[transition].

intermediate concept

Guards de Workflow

Listeners sur WorkflowEvents::GUARD_[TRANSITION] permettant de bloquer une transition dynamiquement. $event->setBlocked(true, 'raison'). Vérification des droits, données, état externe.

php Guard de workflow

Bloquer une transition selon des conditions métier.

#[AsEventListener(event: 'workflow.article_publishing.guard.publish')]
class ArticlePublishGuard
{
    public function __invoke(GuardEvent $event): void
    {
        /** @var Article $article */
        $article = $event->getSubject();

        if (empty($article->getTitle())) {
            $event->setBlocked(true, 'Le titre est obligatoire pour publier.');
        }

        if ($article->getWordCount() < 300) {
            $event->setBlocked(true, 'L\'article doit faire au moins 300 mots.');
        }
    }
}

// Usage dans un contrôleur
public function publish(Article $article, WorkflowInterface $workflow): Response
{
    if ($workflow->can($article, 'publish')) {
        $workflow->apply($article, 'publish');
    }
}
intermediate concept

Workflow vs StateMachine

Workflow : un objet peut être dans plusieurs places simultanément (Petri net). StateMachine : une seule place à la fois. Configurés dans workflow.yaml. StateMachine est un cas particulier de Workflow.

yaml Configuration State Machine (article)

State machine pour le cycle de vie d'un article.

# config/packages/workflow.yaml
framework:
    workflows:
        article_publishing:
            type: state_machine
            marking_store:
                type: method
                property: status
            supports:
                - App\Entity\Article
            initial_marking: draft
            places:
                - draft
                - review
                - published
                - rejected
                - archived
            transitions:
                submit_for_review:
                    from: draft
                    to: review
                publish:
                    from: review
                    to: published
                reject:
                    from: review
                    to: rejected
                archive:
                    from: published
                    to: archived
                revise:
                    from: [review, rejected]
                    to: draft