# API Platform avec Symfony en 2026 : Architecture, State Providers et Questions d'Entretien > Maîtriser API Platform 4.2 avec Symfony : State Providers, Processors, Object Mapper, JSON Streamer pour des performances optimales, et questions d'entretien technique pour développeurs confirmés. - Published: 2026-09-08 - Updated: 2026-09-08 - Author: Anthony Fillion-Maillet - Tags: api-platform, symfony, rest-api, state-providers, interview - Reading time: 9 min --- API Platform 4.2 révolutionne la manière dont les applications Symfony exposent des API REST et GraphQL. Cette version introduit le Symfony Object Mapper pour une séparation nette des ressources, le JSON Streamer pour des gains de performance significatifs, et un système de filtres repensé. Pour les développeurs qui préparent des entretiens techniques, la compréhension de ces patterns architecturaux distingue les profils seniors des profils juniors. > **Prérequis API Platform 4.2** > > API Platform 4.2 nécessite Symfony 7.4 ou 8.0. Le support de Symfony 6.4 et 7.0-7.3 a été abandonné. Le JSON Streamer offre jusqu'à 32% de requêtes supplémentaires par seconde sur les endpoints de collection. ## Installation d'API Platform 4.2 avec Symfony API Platform s'installe via Symfony Flex avec une configuration automatique. La configuration par défaut couvre la plupart des cas d'usage tout en restant entièrement personnalisable pour les exigences métier complexes. ```bash # Install API Platform composer require api-platform/symfony # The API documentation is available at /api/ # Open http://localhost:8000/api/ after starting the server symfony serve ``` La recette Flex configure les groupes de sérialisation, l'intégration Doctrine et la génération de documentation OpenAPI. Les ressources API exposent des opérations CRUD en ajoutant un simple attribut aux classes d'entités. ```php // src/Entity/Book.php namespace App\Entity; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; use ApiPlatform\Metadata\Delete; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; #[ORM\Entity] #[ApiResource( operations: [ new GetCollection(), new Get(), new Post(security: "is_granted('ROLE_ADMIN')"), new Put(security: "is_granted('ROLE_ADMIN')"), new Delete(security: "is_granted('ROLE_ADMIN')") ], normalizationContext: ['groups' => ['book:read']], denormalizationContext: ['groups' => ['book:write']] )] class Book { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; #[ORM\Column(length: 255)] #[Assert\NotBlank] #[Groups(['book:read', 'book:write'])] private string $title; #[ORM\Column(type: 'text')] #[Groups(['book:read', 'book:write'])] private string $description; #[ORM\Column] #[Groups(['book:read'])] private \DateTimeImmutable $createdAt; // Getters and setters... } ``` Cette configuration génère cinq endpoints avec validation automatique, sérialisation et documentation OpenAPI. ## State Providers : Récupération de Données depuis Toutes Sources Les State Providers contrôlent la manière dont API Platform récupère les données pour les opérations GET. Le provider Doctrine par défaut gère la récupération des entités, mais les providers personnalisés permettent l'intégration avec des API externes, Elasticsearch ou des données en cache. ```php // src/State/BookStateProvider.php namespace App\State; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use App\Repository\BookRepository; use Psr\Cache\CacheItemPoolInterface; final class BookStateProvider implements ProviderInterface { public function __construct( private BookRepository $repository, private CacheItemPoolInterface $cache ) {} public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null { // Single item retrieval if (isset($uriVariables['id'])) { $cacheKey = sprintf('book_%d', $uriVariables['id']); $item = $this->cache->getItem($cacheKey); if ($item->isHit()) { return $item->get(); } $book = $this->repository->find($uriVariables['id']); $item->set($book)->expiresAfter(3600); $this->cache->save($item); return $book; } // Collection retrieval with custom filtering return $this->repository->findActiveBooks(); } } ``` L'enregistrement du provider sur des opérations spécifiques s'effectue ainsi : ```php #[ApiResource( operations: [ new GetCollection(provider: BookStateProvider::class), new Get(provider: BookStateProvider::class), // Other operations use default Doctrine provider new Post(), new Put(), ] )] class Book { /* ... */ } ``` ## State Processors : Gestion des Mutations avec Logique Métier Les [State Processors](https://api-platform.com/docs/core/state-processors/) gèrent les opérations POST, PUT, PATCH et DELETE. Ils reçoivent les données désérialisées et appliquent la logique métier avant la persistance. ```php // src/State/BookStateProcessor.php namespace App\State; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProcessorInterface; use App\Entity\Book; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mime\Email; final class BookStateProcessor implements ProcessorInterface { public function __construct( private EntityManagerInterface $em, private MailerInterface $mailer, private ProcessorInterface $persistProcessor // Decorated Doctrine processor ) {} public function process( mixed $data, Operation $operation, array $uriVariables = [], array $context = [] ): mixed { // Pre-persist business logic if ($data instanceof Book && $operation instanceof Post) { $data->setCreatedAt(new \DateTimeImmutable()); $data->setSlug($this->generateSlug($data->getTitle())); } // Delegate to Doctrine processor $result = $this->persistProcessor->process($data, $operation, $uriVariables, $context); // Post-persist notification if ($operation instanceof Post) { $this->notifyNewBook($data); } return $result; } private function generateSlug(string $title): string { return strtolower(preg_replace('/[^a-zA-Z0-9]+/', '-', $title)); } private function notifyNewBook(Book $book): void { $email = (new Email()) ->to('catalog@example.com') ->subject('New book added: ' . $book->getTitle()) ->text('A new book has been added to the catalog.'); $this->mailer->send($email); } } ``` > **Décoration des Processors** > > La décoration du processor Doctrine par défaut avec `#[AsDecorator]` préserve le comportement de persistance tout en ajoutant une logique personnalisée. Ce pattern évite de dupliquer les opérations ORM. ## Object Mapper : Séparation des Ressources API et des Entités API Platform 4.2 intègre le [composant Symfony Object Mapper](https://symfony.com/doc/current/components/object_mapper.html) pour découpler les représentations API des entités du domaine. Cette séparation permet différents modèles de lecture/écriture et protège les structures internes des entités. ```php // src/ApiResource/BookResource.php namespace App\ApiResource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use App\Entity\Book; use Symfony\Component\ObjectMapper\Attribute\Map; #[ApiResource( shortName: 'Book', operations: [ new GetCollection(), new Get() ] )] #[Map(target: Book::class)] class BookResource { public ?int $id = null; public string $title; public string $description; // Computed field not in entity public int $wordCount; // Formatted date for API consumers public string $publishedDate; } ``` Le provider mapper transforme automatiquement les entités en ressources : ```php // src/State/BookResourceProvider.php namespace App\State; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use App\ApiResource\BookResource; use App\Repository\BookRepository; use Symfony\Component\ObjectMapper\ObjectMapperInterface; final class BookResourceProvider implements ProviderInterface { public function __construct( private BookRepository $repository, private ObjectMapperInterface $mapper ) {} public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null { if (isset($uriVariables['id'])) { $book = $this->repository->find($uriVariables['id']); return $book ? $this->toResource($book) : null; } return array_map( fn(Book $book) => $this->toResource($book), $this->repository->findAll() ); } private function toResource(Book $book): BookResource { $resource = $this->mapper->map($book, BookResource::class); $resource->wordCount = str_word_count($book->getDescription()); $resource->publishedDate = $book->getCreatedAt()->format('F j, Y'); return $resource; } } ``` ## JSON Streamer : Amélioration des Performances de 32% Le composant JSON Streamer sérialise les grandes collections sans charger l'ensemble des données en mémoire. Les benchmarks sur l'API Sylius montrent une [augmentation de 32,4% des requêtes par seconde](https://soyuka.me/api-platform-4-2-redefining-api-development/). Activation du streaming au niveau de la ressource ou de l'opération : ```php #[ApiResource( operations: [ new GetCollection( jsonStream: true, // Enable JSON streaming paginationItemsPerPage: 100 ), new Get() ] )] class Book { /* ... */ } ``` Le streaming bénéficie particulièrement aux : - Endpoints de collection avec plus de 50 éléments - Ressources avec des relations imbriquées - API servant des clients mobiles avec bande passante limitée La spécification OpenAPI a également été optimisée. La mutualisation des JSON Schema réduit la taille des fichiers de 30%, améliorant les temps de chargement de la documentation. ## Questions d'Entretien : Architecture API Platform Les entretiens techniques pour les postes Symfony couvrent fréquemment les patterns API Platform. Ces questions évaluent la compréhension de l'architecture du framework au-delà des opérations CRUD basiques. ### "Expliquez la différence entre State Providers et Processors" **Réponse attendue** : Les State Providers récupèrent les données pour les opérations de lecture (GET). Ils retournent des entités, des DTOs ou des tableaux. Les State Processors gèrent les opérations d'écriture (POST, PUT, PATCH, DELETE). Ils reçoivent les données désérialisées et exécutent la logique métier avant la persistance. Cette séparation suit les principes CQRS : les requêtes passent par les Providers, les commandes par les Processors. ### "Quand utiliser une ressource API personnalisée plutôt qu'exposer directement une entité ?" **Réponse attendue** : Les ressources personnalisées s'appliquent lorsque : - La représentation API diffère du schéma de base de données - Des champs calculés nécessitent l'agrégation de plusieurs entités - Les modèles de lecture et d'écriture requièrent des structures différentes - Des champs internes des entités doivent rester cachés des consommateurs de l'API - La compatibilité de version exige des contrats stables pendant que les entités évoluent ### "Comment API Platform gère-t-il la validation ?" **Réponse attendue** : API Platform utilise les contraintes Symfony Validator sur les propriétés des entités. La validation s'exécute automatiquement pendant la désérialisation avant l'exécution du State Processor. Les groupes de validation contrôlent quelles contraintes s'appliquent par opération. Les validateurs personnalisés s'intègrent via les mécanismes Symfony standards. ```php #[ApiResource( operations: [ new Post(validationContext: ['groups' => ['create']]), new Put(validationContext: ['groups' => ['update']]) ] )] class Book { #[Assert\NotBlank(groups: ['create', 'update'])] private string $title; #[Assert\Isbn(groups: ['create'])] private string $isbn; // Required only on creation } ``` > **Erreur Courante en Entretien** > > Les candidats décrivent souvent la validation comme "automatique" sans mentionner les groupes de validation ou les contraintes personnalisées. Les recruteurs recherchent une compréhension de la personnalisation de la validation par opération. ### "Quels mécanismes de sécurité API Platform fournit-il ?" **Réponse attendue** : API Platform s'intègre avec Symfony Security via : - L'attribut `security` sur les opérations pour le contrôle d'accès basé sur les rôles - `securityPostDenormalize` pour les vérifications au niveau objet après la liaison des données - Les Voters pour une logique d'autorisation complexe - La limitation de débit via l'intégration du Symfony Rate Limiter ```php #[ApiResource( operations: [ new Get( security: "is_granted('ROLE_USER')" ), new Put( security: "is_granted('ROLE_ADMIN') or object.getOwner() == user", securityPostDenormalize: "is_granted('BOOK_EDIT', object)" ) ] )] ``` ## Filtres et Pagination : Patterns de Requêtes Avancés Les [filtres API Platform](https://api-platform.com/docs/core/filters/) permettent aux clients d'interroger les collections avec des paramètres URL. Le système de filtres dans la version 4.2 a été repensé pour une meilleure extensibilité. ```php use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Metadata\ApiFilter; #[ApiResource] #[ApiFilter(SearchFilter::class, properties: [ 'title' => 'partial', // LIKE %value% 'author.name' => 'exact' // Nested property ])] #[ApiFilter(DateFilter::class, properties: ['createdAt'])] #[ApiFilter(OrderFilter::class, properties: ['title', 'createdAt'])] class Book { /* ... */ } ``` Endpoints générés : ``` GET /api/books?title=symfony # Search by title GET /api/books?createdAt[after]=2026-01-01 # Date range GET /api/books?order[createdAt]=desc # Sorting ``` ## Tests des Ressources API Platform API Platform fournit un client de test qui simplifie les tests fonctionnels. La classe `ApiTestCase` offre des assertions spécifiques aux réponses API. ```php // tests/Api/BookTest.php namespace App\Tests\Api; use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; use App\Entity\Book; class BookTest extends ApiTestCase { public function testGetCollection(): void { $response = static::createClient()->request('GET', '/api/books'); $this->assertResponseIsSuccessful(); $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); $this->assertJsonContains([ '@context' => '/api/contexts/Book', '@type' => 'Collection' ]); } public function testCreateBook(): void { $response = static::createClient()->request('POST', '/api/books', [ 'json' => [ 'title' => 'Symfony Best Practices', 'description' => 'A guide to modern Symfony development' ], 'headers' => ['Authorization' => 'Bearer ' . $this->getToken()] ]); $this->assertResponseStatusCodeSame(201); $this->assertJsonContains(['title' => 'Symfony Best Practices']); } public function testCreateBookValidationFails(): void { $response = static::createClient()->request('POST', '/api/books', [ 'json' => ['description' => 'Missing title'], 'headers' => ['Authorization' => 'Bearer ' . $this->getToken()] ]); $this->assertResponseStatusCodeSame(422); $this->assertJsonContains([ 'violations' => [ ['propertyPath' => 'title', 'message' => 'This value should not be blank.'] ] ]); } } ``` ## Points Clés pour API Platform avec Symfony - Les State Providers gèrent les opérations GET, les State Processors gèrent les mutations. Cette séparation permet une architecture propre avec des sources de données personnalisées et une logique métier - Le composant Object Mapper découple les ressources API des entités Doctrine, permettant différents modèles de lecture/écriture et protégeant les structures internes - Le JSON Streamer offre une amélioration des performances de 32% sur les endpoints de collection en sérialisant sans allocation mémoire complète - La sécurité s'intègre via les mécanismes standards de Symfony : expressions `security`, Voters et composant Rate Limiter - Les [groupes de validation](/technologies/symfony/interview-questions/testing) personnalisent l'application des contraintes par opération - Les filtres exposent automatiquement des paramètres de requête, les filtres de recherche, date et tri couvrant la plupart des cas d'usage - Les questions d'entretien se concentrent sur les décisions architecturales : quand utiliser des providers personnalisés, comment séparer les modèles de lecture/écriture, et les patterns d'implémentation de la sécurité --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/symfony/api-platform-symfony-architecture-interview-questions