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.

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.
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.
# 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 serveLa 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.
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.
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 :
#[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 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.
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);
}
}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 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.
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 :
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;
}
}Prêt à réussir tes entretiens Symfony ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
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.
Activation du streaming au niveau de la ressource ou de l'opération :
#[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.
#[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
}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
securitysur les opérations pour le contrôle d'accès basé sur les rôles securityPostDenormalizepour 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
#[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 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é.
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 # SortingTests 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.
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 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é
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tu saurais repérer le bug en Symfony ?
Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Écrit par
Anthony Fillion-MailletFondateur de SharpSkill
Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.
Mis à jour le 8 septembre 2026
Tags
Partager
Articles similaires

Questions d'entretien Symfony : Top 25 en 2026
Les 25 questions d'entretien Symfony les plus posées. Architecture, Doctrine ORM, services, sécurité, formulaires et tests avec réponses détaillées et exemples de code.

Sécurité Symfony en 2026 : Voters, Firewalls et Questions d'Entretien Technique
Plongée technique dans la sécurité Symfony couvrant les voters, firewalls, access tokens et authentification, avec les questions d'entretien les plus fréquentes pour les développeurs Symfony.

Doctrine ORM : Maîtriser les relations en Symfony
Guide complet des relations Doctrine ORM dans Symfony. OneToMany, ManyToMany, stratégies de chargement et optimisation des performances avec exemples pratiques.