Exposer une collection paginée et filtrable avec DTO
Ce cookbook présente un pattern complet pour :
- partir d’une entité Doctrine (
Customer), - exposer une collection paginée via API Platform 4,
- retourner un DTO avec champs calculés (ex:
fullName=firstName + lastName), - conserver la puissance des filtres Doctrine (WHERE, ORDER BY) et de la pagination,
- préparer des implémentations côté frontend (Vue / Flutter) qui consomment cette API proprement.
Les différentes implémentations techniques (Symfony, Vue, Flutter) partagent le même contrat d’API et la même philosophie.
Les exemples complets (structure de répertoires, tests, configuration) sont disponibles dans les dépôts Doing.
Les extraits ci-dessous se concentrent sur la compréhension du pattern et renvoient vers les cookbooks dédiés pour les détails d’implémentation.
TL;DR
-
Backend Symfony :
- une entité Doctrine
Customerreste la ressource principale API Platform, - un
CustomerDtodéfinit le contrat d’API (avec un champ calculéfullName), - un
CustomerDtoProviderremplace les entités par des DTO tout en conservant pagination + filtres, - des
QueryParametermodernes branchés sur des filtres Doctrine permettent de filtrer / trier surfullNameetage.
- une entité Doctrine
-
Frontend (Vue / Flutter, à venir) :
- consomme
/api/customerspaginé, - utilise des query params
fullName,age,order[fullName], - affiche une liste réactive de clients avec recherche et tri.
- consomme
Le cœur de la complexité est côté backend : comment rester sur une ressource Doctrine, tout en exposant un DTO riche et filtrable sur des champs calculés.
Cas d’usage couvert
Cas d’usage typique back-office :
- la base contient une entité
Customeravec les “vrais” champs métier :firstName,lastName,age,city; - l’API publique ne doit pas exposer directement cette entité (contrat d’API indépendant de la persistence) ;
- on veut un DTO avec un champ calculé
fullNameet éventuellement d’autres champs dérivés ; - on veut filtrer/ordonner sur
fullNamecomme si c’était un champ natif ; - on veut garder tout le confort d’API Platform : pagination, filtres Doctrine, documentation OpenAPI.
Adopte ce pattern lorsque :
- ton modèle Doctrine ne correspond pas exactement au contrat d’API souhaité,
- tu dois exposer des champs calculés (nom complet, âge, labels combinés…),
- tu veux préserver les extensions Doctrine (filtres globaux, sécurité, pagination) d’API Platform,
- tu anticipes du versionning d’API (DTO plus stable que l’entité).
Architecture globale
- API Platform pilote les opérations (Get / GetCollection), la pagination et les filtres.
- Doctrine exécute les requêtes SQL et applique les filtros (WHERE, ORDER BY).
- Un state provider custom fait le mapping entité → DTO.
- Le client (Vue / Flutter / autre) ne voit que le DTO, jamais l’entité.
Vue d’ensemble des paramètres de recherche
La collection d’exemple /api/customers accepte plusieurs paramètres de requête :
| Paramètre | Exemple d’appel | Effet côté backend |
|---|---|---|
fullName | /api/customers?fullName=dupont | Filtre sur CONCAT(firstName, ' ', lastName) (LIKE) |
age | /api/customers?age=30 | Filtre sur le champ age égal à un entier (cast auto) |
order[fullName] | /api/customers?order[fullName]=asc | Tri par nom complet (ASC/DESC) via ORDER BY calculé |
Côté API Platform 4.2+, ces paramètres sont déclarés avec QueryParameter sur l’opération GetCollection et associés à des filtres Doctrine (FilterInterface).
Pour le détail pas-à-pas (signature des filtres, gestion du QueryBuilder), voir la partie Symfony :
➡ Symfony - Exposer une collection paginée et filtrable avec DTO
Exemple rapide d’appels HTTP
# Liste paginée par défaut
curl "https://api.example.test/api/customers"
# Filtre sur le nom complet
curl "https://api.example.test/api/customers?fullName=dupont"
# Filtre combiné nom complet + âge
curl "https://api.example.test/api/customers?fullName=jean&age=30"
# Tri ascendant sur le nom complet
curl "https://api.example.test/api/customers?order[fullName]=asc"
# Filtre + tri
curl "https://api.example.test/api/customers?fullName=dupont&order[fullName]=asc"
Les réponses sont paginées selon la configuration API Platform (par défaut 20 éléments/page dans l’exemple Symfony) et exposent les métadonnées habituelles (liens next / previous, total, etc.).
➡ Pour entrer dans le détail de l’implémentation backend : symfony.mdx.
➡ Pour les intégrations front (Vue / Flutter), les cookbooks dédiés seront ajoutés prochainement.