Le problème
Le service order expose ses commandes par la
ressource API Platform OrderResource, reliée à
l'entité Order. Nous ajoutons à
Order et à OrderResource une
priorité de préparation, un entier. Les applications
clientes veulent filtrer par seuil :
/orders?priority[gte]=3.
ComparisonFilter, apparu avec la 4.3, répond à
ce besoin. Il enveloppe un filtre d'égalité et lui ajoute
les opérateurs gt, gte,
lt, lte et ne. La 5.0
y ajoute between.
// src/ApiResource/OrderResource.php (opération GetCollection)
use ApiPlatform\Doctrine\Orm\Filter\ComparisonFilter;
use ApiPlatform\Doctrine\Orm\Filter\ExactFilter;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\QueryParameter;
new GetCollection(
parameters: [
'priority' => new QueryParameter(
filter: new ComparisonFilter(new ExactFilter()),
property: 'priority',
),
],
),
Le filtrage fonctionne, la documentation non. Swagger UI et
Scalar listent priority[gt],
priority[gte], etc., chacun typé
object avec les cinq opérateurs en propriétés.
Le bouton « Execute » de Swagger UI envoie alors
gt=...>e=..., sans le préfixe
priority. Le filtre ne reçoit rien et la
réponse contient toutes les commandes.
La cause : le filtre se décrit deux fois.
getSchema() renvoie un objet avec une propriété
par opérateur, posé sur priority.
getOpenApiParameters() renvoie un paramètre
OpenAPI par opérateur, sans schéma.
En construisant le document,
OpenApiFactory fusionne chaque opérateur avec
ce paramètre parent. Faute de schéma propre, chacun hérite
de l'objet, avec style: form et
explode: true. La spécification OpenAPI
sérialise un tel objet en paires clé=valeur,
sans le nom du paramètre.
Nous l'avons reproduit sur la 4.4.2, et sur la 5.0.1 avec
six opérateurs (between compris). Le code en
cause date de la 4.3.0.
La solution
Mettre à jour. La 4.4.3 et la 5.0.2,
publiées le 2 octobre 2026, corrigent le filtre. Chaque
opérateur y reçoit un schéma scalaire et
explode: false : Swagger UI affiche de simples
champs texte.
Le type reste string, car
ExactFilter ne décrit pas le type de la
propriété. Pour documenter un entier, ou sur une version
antérieure (la 4.3 n'a pas reçu le correctif), nous
surchargeons la documentation.
Surcharger openApi. Cette
propriété du QueryParameter accepte une liste
de Parameter OpenAPI : un par opérateur, avec
un schéma simple.
// src/ApiResource/OrderResource.php (opération GetCollection)
use ApiPlatform\OpenApi\Model\Parameter;
'priority' => new QueryParameter(
filter: new ComparisonFilter(new ExactFilter()),
property: 'priority',
openApi: [
new Parameter('priority[gt]', 'query', 'Priorité strictement supérieure', schema: ['type' => 'integer']),
new Parameter('priority[gte]', 'query', 'Priorité supérieure ou égale', schema: ['type' => 'integer']),
new Parameter('priority[lt]', 'query', 'Priorité strictement inférieure', schema: ['type' => 'integer']),
new Parameter('priority[lte]', 'query', 'Priorité inférieure ou égale', schema: ['type' => 'integer']),
new Parameter('priority[ne]', 'query', 'Priorité différente', schema: ['type' => 'integer']),
// 5.0 uniquement : deux bornes incluses, séparées par deux points.
new Parameter('priority[between]', 'query', 'Priorité entre deux bornes', schema: ['type' => 'string'], example: '2..4'),
],
),
Chaque nom est complet : priority[gte], pas
gte. Swagger UI et Scalar affichent alors des
champs entiers, et la requête part en
priority[gte]=3. Un opérateur absent de la
liste reste actif, mais disparaît de la documentation.
Sur la 5.0, priority[between]=2..4 renvoie les
priorités 2 à 4, bornes comprises. Une valeur mal formée
(2-4) est ignorée sans erreur : toutes les
commandes reviennent.
Le piège
Ne pas passer par la clé schema du
QueryParameter.
Elle ressemble à de la documentation, mais elle agit à
l'exécution. API Platform en tire des contraintes de
validation et, avec castToNativeType: true, une
conversion de type.
Or la valeur du paramètre n'est pas un entier : c'est le
tableau des opérateurs, par exemple
['gte' => '3']. Sur la 4.4 comme sur la 5.0
:
-
schema: ['type' => 'integer', 'minimum' => 1, 'maximum' => 5]: toute requête qui l'utilise reçoit une 422, « This value should be a valid number. » ; -
schema: ['type' => 'integer']aveccastToNativeType: true: 422 aussi, « This value should be of type integer. » ; -
minimumseul ne rejette rien,maximumseul rejette tout : PHP juge un tableau supérieur à tout nombre.
Seul, schema: ['type' => 'integer'] passe et
corrige même l'affichage jusqu'à la 4.4.2 et la 5.0.1.
Depuis les correctifs, le schéma du filtre l'emporte : la
clé ne change plus la documentation.
Pour valider les valeurs, nous passons par
constraints, appliquées au tableau entier.
constraints: [new Assert\All([new
Assert\Regex('/^\d+$/')])]
rejette priority[gte]=abc avec une 422 (motif à
élargir pour between). La règle à retenir :
openApi décrit,
constraints valide.
Sources
-
API Platform,
Filters, OpenAPI et JSON Schema
et
validation des paramètres
: propriétés
openApi,schema,castToNativeTypeetconstraints. -
API Platform,
Comparison Filter
(5.0) : opérateurs et
between. -
API Platform, code de la 4.4.2 :
ComparisonFilter,
ParameterResourceMetadataCollectionFactory
(
addFilterMetadata), OpenApiFactory (mergeParameter) et ParameterValidationConstraints. - API Platform, ticket #8589 et correctif #8605, livré dans la 4.4.3 et la 5.0.2.
-
API Platform,
CHANGELOG de la 5.0.2
:
ComparisonFilterdans la 4.3.0 (#7760),betweendans la 5.0.0 (#8351). -
OpenAPI 3.2,
exemples de style
: sérialisation
formd'un objet. - PHP, comparaison entre types : un tableau est toujours supérieur.