Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rethinking API Platform Filters

Rethinking API Platform Filters

Talk given at the Dutch PHP Conference 2026 - Amsterdam

API Platform 4.1 and 4.2 unveils a revamped filter system featuring a brand-new syntax, inspired by the advancements introduced with Laravel support in version 4.0. These enhancements not only make filters more intuitive, flexible, and powerful for developers but also significantly evolve the codebase.

In this talk, we’ll walk through my contribution to this transformation — from identifying the limitations of the previous approach to designing and implementing a simplified, consistent solution.

Using practical examples, we’ll explore how these updates can streamline your API projects and reduce development complexity. To wrap up, we’ll discuss the next steps and potential enhancements planned for the filter system.

Whether you’re new to API Platform or an experienced user, this session will provide valuable insights, helping you leverage these improvements for faster and cleaner API development.

Avatar for Vincent Amstoutz

Vincent Amstoutz

March 22, 2026
Tweet

More Decks by Vincent Amstoutz

Other Decks in Programming

Transcript

  1. Vincent Amstoutz 💻 Lead Developer @ Les-Tilleuls.coop 🌍 OSS Contributions

    to FrankenPHP, Symfony, API Platform, Sulu and more! 🤾‍♂️⛷️🤿 Active Adventurer 🪩 EDM vinceAmstoutz
  2. PHP, JS, GO, Rust, C Dev DevOps, SRE Maintenance of

    your applications Agile Management, UX design, UI design @vinceAmstoutz API, Web & Cloud experts
  3. What Is It For? 🤔 Filter resources { "@context": "/contexts/Book",

    "@id": "/books", "@type": "Collection", "totalItems": 2, "member": [ { "id": 1, "title": "Living Documentation", "author": "Cyrille Martraire", "publicationDate": "2019-05-16T00:00:00+00:00", "genre": "Software Engineering" }, { "id": 2, "title": "Clean Code in PHP", "author": "Carsten Windler and Alexandre Daubois", "publicationDate": "2022-10-31T00:00:00+00:00", "genre": "Programming / PHP" } ] } GET /books?publicationDate[after]=2022-01-01 { "@context": "/contexts/Book", "@id": "/books", "@type": "Collection", "totalItems": 1, "member": [ { "id": 2, "title": "Clean Code in PHP", "author": "Carsten Windler and Alexandre Daubois", "publicationDate": "2022-10-31T00:00:00+00:00", "genre": "Programming / PHP" } ], } SELECT * FROM book WHERE publication_date > '2022-01-01'; GET /books @vinceAmstoutz
  4. How Does It Work? 🤔 <?php // src/Entity/Book.php namespace App\Entity;

    use Doctrine\ORM\Mapping as ORM; #[ORM\Entity] class Book { #[ORM\Id, ORM\Column, ORM\GeneratedValue] private ?int $id = null; #[ORM\Column(type: 'datetime_immutable')] private ?\DateTimeImmutable $publicationDate = null; // Other properties, Getters and Setters, etc. } 1 2 3 4 5 use ApiPlatform\Metadata\ApiFilter; 6 use ApiPlatform\Metadata\ApiResource; 7 use ApiPlatform\Doctrine\Orm\Filter\DateFilter; 8 9 10 11 #[ApiResource] 12 #[ApiFilter(DateFilter::class, properties: ['publicationDate'])] 13 14 15 16 17 18 19 20 21 22 23 use ApiPlatform\Metadata\ApiResource; #[ApiResource] <?php 1 // src/Entity/Book.php 2 3 namespace App\Entity; 4 5 use ApiPlatform\Metadata\ApiFilter; 6 7 use ApiPlatform\Doctrine\Orm\Filter\DateFilter; 8 use Doctrine\ORM\Mapping as ORM; 9 10 #[ORM\Entity] 11 12 #[ApiFilter(DateFilter::class, properties: ['publicationDate'])] 13 class Book 14 { 15 #[ORM\Id, ORM\Column, ORM\GeneratedValue] 16 private ?int $id = null; 17 18 #[ORM\Column(type: 'datetime_immutable')] 19 private ?\DateTimeImmutable $publicationDate = null; 20 21 // Other properties, Getters and Setters, etc. 22 } 23 use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; #[ApiFilter(DateFilter::class, properties: ['publicationDate'])] <?php 1 // src/Entity/Book.php 2 3 namespace App\Entity; 4 5 6 use ApiPlatform\Metadata\ApiResource; 7 8 use Doctrine\ORM\Mapping as ORM; 9 10 #[ORM\Entity] 11 #[ApiResource] 12 13 class Book 14 { 15 #[ORM\Id, ORM\Column, ORM\GeneratedValue] 16 private ?int $id = null; 17 18 #[ORM\Column(type: 'datetime_immutable')] 19 private ?\DateTimeImmutable $publicationDate = null; 20 21 // Other properties, Getters and Setters, etc. 22 } 23 @vinceAmstoutz
  5. Example: BooleanFilter #[ApiFilter(BooleanFilter::class, properties: ['isAvailable'])] <?php 1 // api/src/ApiResource/Product.php 2

    namespace App\ApiResource; 3 4 use ApiPlatform\Metadata\ApiFilter; 5 use ApiPlatform\Metadata\ApiResource; 6 use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; 7 8 #[ApiResource] 9 10 class Product 11 { 12 // ... 13 } 14 /products?isAvailable=true Syntax: ?property=<true|false|1|0> @vinceAmstoutz
  6. Example: StartSearchFilter #[QueryParameter(filter: new StartSearchFilter(), property: 'isbn')] <?php 1 //

    api/src/ApiResource/Book.php 2 namespace App\ApiResource; 3 4 use ApiPlatform\Laravel\Eloquent\Filter\StartSearchFilter; 5 // ... 6 7 #[ApiResource] 8 9 class Book 10 { 11 // ... 12 } 13 /books?isbn=978 Syntax: ?property=value @vinceAmstoutz
  7. DX Improvement And Simplification And also PRs #6749, #7079, #6865

    and #6775 Thanks to Antoine Bluchet (@soyuka) 🙏 @vinceAmstoutz
  8. ApiFilter (old method) <?php /* * This file is part

    of the API Platform project. */ namespace ApiPlatform\Metadata; // ... #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] final class ApiFilter { /* ... */ public function __construct( public string $filterClass, public ?string $id = null, public ?string $strategy = null, public array $properties = [], public array $arguments = [], public ?string $alias = null, ) {...} } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @vinceAmstoutz
  9. Parameter Metadata <?php /* This file is part of the

    API Platform project.*/ namespace ApiPlatform\Metadata; // ... abstract class Parameter { public function __construct( protected ?string $key = null, protected ?array $schema = null, protected OpenApiParameter|array|false|null $openApi = null, protected mixed $provider = null, protected mixed $filter = null, protected ?string $property = null, protected ?string $description = null, protected ?array $properties = null, protected ?bool $required = null, protected ?int $priority = null, protected ?false $hydra = null, protected mixed $constraints = null, protected string|\Stringable|null $security = null, protected ?string $securityMessage = null, protected ?array $extraProperties = [], protected array|string|null $filterContext = null, protected ?Type $nativeType = null, protected ?bool $castToArray = null, protected ?bool $castToNativeType = null, protected mixed $castFn = null, ) {...} } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @vinceAmstoutz
  10. QueryParameter <?php /* * This file is part of the

    API Platform project. * * (c) Kévin Dunglas <[email protected]> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace ApiPlatform\Metadata; /** * @experimental */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] class QueryParameter extends Parameter implements QueryParameterInterface { } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @vinceAmstoutz
  11. Arrays vs Objects <?php // api/src/Entity/Offer.php namespace App\Entity; use ApiPlatform\Metadata\ApiResource;

    #[ApiResource] class Offer { // ... } 1 2 3 4 5 use ApiPlatform\Metadata\ApiFilter; 6 use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; 7 8 9 #[ApiFilter(SearchFilter::class, properties: [ 10 'id' => 'exact', 11 'price' => 'exact', 12 'description' => 'partial' 13 ])] 14 15 16 17 18 use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; #[ApiFilter(SearchFilter::class, properties: [ 'id' => 'exact', 'price' => 'exact', 'description' => 'partial' ])] <?php 1 // api/src/Entity/Offer.php 2 namespace App\Entity; 3 4 use ApiPlatform\Metadata\ApiResource; 5 6 7 8 #[ApiResource] 9 10 11 12 13 14 class Offer 15 { 16 // ... 17 } 18 Syntax: ?property[]=foo&property[]=bar <?php // api/src/Entity/Offer.php namespace App\Entity; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\QueryParameter; use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; use ApiPlatform\Doctrine\Orm\Filter\PartialSearchFilter; #[ApiResource] #[QueryParameter(filter: new ExactSearchFilter(), property: 'id')] #[QueryParameter(filter: new ExactSearchFilter(), property: 'price')] #[QueryParameter(filter: new PartialSearchFilter(), property: 'description')] class Offer {...} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 GET /offers?id=9b1febd5-48ff-4a30-a63d-92d5d96020d8 GET /offers?price=10&description=cheap @vinceAmstoutz
  12. New Dedicated Search Filters FreeTextQueryFilter GET books?autocomplete=orange OrFilter GET /books?q[]=97815&q[]=978036940

    PartialSearchFilter GET /books?namePartial=de ExactFilter GET /books?name=CleanCode IriFilter GET /products?factory=/factory/1 ✨ Support for ORM and ODM @vinceAmstoutz
  13. Example Of Usage GET /products?name=brush Syntax: ?property=value <?php // src/ApiResource/Product.php

    namespace App\ApiResource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\QueryParameter; use ApiPlatform\Doctrine\Orm\Filter\ExactFilter; #[ApiResource] #[QueryParameter(filter: new ExactSearchFilter(), property: 'name')] class Product { // ... } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @vinceAmstoutz
  14. Another Example Of Usage <?php // src/ApiResource/GetCollectionProduct.php namespace App\ApiResource; )]

    1 2 3 4 use ApiPlatform\Metadata\GetCollection; 5 use ApiPlatform\Metadata\QueryParameter; 6 use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; 7 8 #[GetCollection( 9 uriTemplate: '/products', 10 parameters: ['name' => new QueryParameter(filter: new ExactSearchFilter())] 11 12 class GetCollectionProduct {...} 13 use ApiPlatform\Metadata\GetCollection; #[GetCollection( <?php 1 // src/ApiResource/GetCollectionProduct.php 2 namespace App\ApiResource; 3 4 5 use ApiPlatform\Metadata\QueryParameter; 6 use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; 7 8 9 uriTemplate: '/products', 10 parameters: ['name' => new QueryParameter(filter: new ExactSearchFilter())] 11 )] 12 class GetCollectionProduct {...} 13 use ApiPlatform\Metadata\QueryParameter; use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; parameters: ['name' => new QueryParameter(filter: new ExactSearchFilter())] <?php 1 // src/ApiResource/GetCollectionProduct.php 2 namespace App\ApiResource; 3 4 use ApiPlatform\Metadata\GetCollection; 5 6 7 8 #[GetCollection( 9 uriTemplate: '/products', 10 11 )] 12 class GetCollectionProduct {...} 13 <?php // src/ApiResource/GetCollectionProduct.php namespace App\ApiResource; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\QueryParameter; use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; #[GetCollection( uriTemplate: '/products', parameters: ['name' => new QueryParameter(filter: new ExactSearchFilter())] )] class GetCollectionProduct {...} 1 2 3 4 5 6 7 8 9 10 11 12 13 GET /products?name=brush Syntax: ?property=value @vinceAmstoutz
  15. Another Example Of Usage <?php // src/ApiResource/GetCollectionProduct.php namespace App\ApiResource; )]

    1 2 3 4 use ApiPlatform\Metadata\GetCollection; 5 use ApiPlatform\Metadata\QueryParameter; 6 use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; 7 8 #[GetCollection( 9 uriTemplate: '/products', 10 parameters: [ 11 'short' => new QueryParameter( 12 filter: new ExactSearchFilter(), 13 property: 'shortName' 14 ) 15 ] 16 17 class GetCollectionProduct {...} 18 use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\QueryParameter; #[GetCollection( uriTemplate: '/products', parameters: [ <?php 1 // src/ApiResource/GetCollectionProduct.php 2 namespace App\ApiResource; 3 4 5 6 use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; 7 8 9 10 11 'short' => new QueryParameter( 12 filter: new ExactSearchFilter(), 13 property: 'shortName' 14 ) 15 ] 16 )] 17 class GetCollectionProduct {...} 18 use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; 'short' => new QueryParameter( <?php 1 // src/ApiResource/GetCollectionProduct.php 2 namespace App\ApiResource; 3 4 use ApiPlatform\Metadata\GetCollection; 5 use ApiPlatform\Metadata\QueryParameter; 6 7 8 #[GetCollection( 9 uriTemplate: '/products', 10 parameters: [ 11 12 filter: new ExactSearchFilter(), 13 property: 'shortName' 14 ) 15 ] 16 )] 17 class GetCollectionProduct {...} 18 property: 'shortName' <?php 1 // src/ApiResource/GetCollectionProduct.php 2 namespace App\ApiResource; 3 4 use ApiPlatform\Metadata\GetCollection; 5 use ApiPlatform\Metadata\QueryParameter; 6 use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; 7 8 #[GetCollection( 9 uriTemplate: '/products', 10 parameters: [ 11 'short' => new QueryParameter( 12 filter: new ExactSearchFilter(), 13 14 ) 15 ] 16 )] 17 class GetCollectionProduct {...} 18 filter: new ExactSearchFilter(), <?php 1 // src/ApiResource/GetCollectionProduct.php 2 namespace App\ApiResource; 3 4 use ApiPlatform\Metadata\GetCollection; 5 use ApiPlatform\Metadata\QueryParameter; 6 use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; 7 8 #[GetCollection( 9 uriTemplate: '/products', 10 parameters: [ 11 'short' => new QueryParameter( 12 13 property: 'shortName' 14 ) 15 ] 16 )] 17 class GetCollectionProduct {...} 18 <?php // src/ApiResource/GetCollectionProduct.php namespace App\ApiResource; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\QueryParameter; use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; #[GetCollection( uriTemplate: '/products', parameters: [ 'short' => new QueryParameter( filter: new ExactSearchFilter(), property: 'shortName' ) ] )] class GetCollectionProduct {...} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 GET /products?short=brush Syntax: ?property=value @vinceAmstoutz
  16. Another Example Of Usage <?php // src/ApiResource/GetCollectionEvent.php namespace App\ApiResource; )]

    1 2 3 4 use ApiPlatform\Metadata\GetCollection; 5 use ApiPlatform\Metadata\QueryParameter; 6 use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; 7 8 #[GetCollection( 9 uriTemplate: '/events', 10 parameters: [ 11 'date[:property]' => new QueryParameter( 12 filter: DateFilter::class, 13 properties: ['startDate', 'endDate'] 14 ) 15 ] 16 17 class GetCollectionEvent {...} 18 use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\QueryParameter; #[GetCollection( uriTemplate: '/events', parameters: [ <?php 1 // src/ApiResource/GetCollectionEvent.php 2 namespace App\ApiResource; 3 4 5 6 use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; 7 8 9 10 11 'date[:property]' => new QueryParameter( 12 filter: DateFilter::class, 13 properties: ['startDate', 'endDate'] 14 ) 15 ] 16 )] 17 class GetCollectionEvent {...} 18 use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; 'date[:property]' => new QueryParameter( <?php 1 // src/ApiResource/GetCollectionEvent.php 2 namespace App\ApiResource; 3 4 use ApiPlatform\Metadata\GetCollection; 5 use ApiPlatform\Metadata\QueryParameter; 6 7 8 #[GetCollection( 9 uriTemplate: '/events', 10 parameters: [ 11 12 filter: DateFilter::class, 13 properties: ['startDate', 'endDate'] 14 ) 15 ] 16 )] 17 class GetCollectionEvent {...} 18 filter: DateFilter::class, <?php 1 // src/ApiResource/GetCollectionEvent.php 2 namespace App\ApiResource; 3 4 use ApiPlatform\Metadata\GetCollection; 5 use ApiPlatform\Metadata\QueryParameter; 6 use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; 7 8 #[GetCollection( 9 uriTemplate: '/events', 10 parameters: [ 11 'date[:property]' => new QueryParameter( 12 13 properties: ['startDate', 'endDate'] 14 ) 15 ] 16 )] 17 class GetCollectionEvent {...} 18 properties: ['startDate', 'endDate'] <?php 1 // src/ApiResource/GetCollectionEvent.php 2 namespace App\ApiResource; 3 4 use ApiPlatform\Metadata\GetCollection; 5 use ApiPlatform\Metadata\QueryParameter; 6 use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; 7 8 #[GetCollection( 9 uriTemplate: '/events', 10 parameters: [ 11 'date[:property]' => new QueryParameter( 12 filter: DateFilter::class, 13 14 ) 15 ] 16 )] 17 class GetCollectionEvent {...} 18 <?php // src/ApiResource/GetCollectionEvent.php namespace App\ApiResource; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\QueryParameter; use ApiPlatform\Doctrine\Orm\Filter\ExactSearchFilter; #[GetCollection( uriTemplate: '/events', parameters: [ 'date[:property]' => new QueryParameter( filter: DateFilter::class, properties: ['startDate', 'endDate'] ) ] )] class GetCollectionEvent {...} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Syntax: ?key[property][strategy]=value @vinceAmstoutz GET /events?date[endDate][before]=2023-12-31 GET /events?date[startDate][after]=2023-01-01
  17. Create Your Custom Filters 🚀 ✅ FilterInterface quickly implementable ✅

    Complexity handled under the hood ❌ No longer need to extend AbstractFilter ✅ Extension ✅ Optional additional interfaces @vinceAmstoutz
  18. use ApiPlatform\Doctrine\Orm\Filter\FilterInterface; final class MonthFilter implements FilterInterface public function apply(...):

    void <?php 1 2 // api/src/Filter/MonthFilter.php 3 namespace App\Filter; 4 5 6 // ... 7 8 9 { 10 11 { 12 /** Parameter $parameter */ 13 $parameter = $context['parameter']; 14 $monthValue = $parameter->getValue(); 15 16 $parameterName = $queryNameGenerator->generateParameterName($property); 17 $alias = $queryBuilder->getRootAliases()[0]; 18 19 $queryBuilder 20 ->andWhere(sprintf('MONTH(%s.%s) = :%s', $alias, $property, $parameterName)) 21 ->setParameter($parameterName, $monthValue); 22 } 23 } 24 /** Parameter $parameter */ $parameter = $context['parameter']; $monthValue = $parameter->getValue(); <?php 1 2 // api/src/Filter/MonthFilter.php 3 namespace App\Filter; 4 5 use ApiPlatform\Doctrine\Orm\Filter\FilterInterface; 6 // ... 7 8 final class MonthFilter implements FilterInterface 9 { 10 public function apply(...): void 11 { 12 13 14 15 16 $parameterName = $queryNameGenerator->generateParameterName($property); 17 $alias = $queryBuilder->getRootAliases()[0]; 18 19 $queryBuilder 20 ->andWhere(sprintf('MONTH(%s.%s) = :%s', $alias, $property, $parameterName)) 21 ->setParameter($parameterName, $monthValue); 22 } 23 } 24 $parameterName = $queryNameGenerator->generateParameterName($property); $alias = $queryBuilder->getRootAliases()[0]; <?php 1 2 // api/src/Filter/MonthFilter.php 3 namespace App\Filter; 4 5 use ApiPlatform\Doctrine\Orm\Filter\FilterInterface; 6 // ... 7 8 final class MonthFilter implements FilterInterface 9 { 10 public function apply(...): void 11 { 12 /** Parameter $parameter */ 13 $parameter = $context['parameter']; 14 $monthValue = $parameter->getValue(); 15 16 17 18 19 $queryBuilder 20 ->andWhere(sprintf('MONTH(%s.%s) = :%s', $alias, $property, $parameterName)) 21 ->setParameter($parameterName, $monthValue); 22 } 23 } 24 $queryBuilder ->andWhere(sprintf('MONTH(%s.%s) = :%s', $alias, $property, $parameterName)) ->setParameter($parameterName, $monthValue); } <?php 1 2 // api/src/Filter/MonthFilter.php 3 namespace App\Filter; 4 5 use ApiPlatform\Doctrine\Orm\Filter\FilterInterface; 6 // ... 7 8 final class MonthFilter implements FilterInterface 9 { 10 public function apply(...): void 11 { 12 /** Parameter $parameter */ 13 $parameter = $context['parameter']; 14 $monthValue = $parameter->getValue(); 15 16 $parameterName = $queryNameGenerator->generateParameterName($property); 17 $alias = $queryBuilder->getRootAliases()[0]; 18 19 20 21 22 23 } 24 Create Your Custom Filters 🚀 @vinceAmstoutz
  19. Add Filter Validation use ApiPlatform\Metadata\JsonSchemaFilterInterface; final class MonthFilter implements FilterInterface,

    JsonSchemaFilterInterface { <?php 1 2 // api/src/Filter/MonthFilter.php 3 namespace App\Filter; 4 5 6 // ... 7 8 9 { 10 public function apply(...): void {...} 11 12 public function getSchema(Parameter $parameter): array 13 14 return [ 15 'type' => 'integer', 16 17 // <=> Symfony\Component\Validator\Constraints\Range 18 'minimum' => 1, 19 'maximum' => 12, 20 ]; 21 } 22 } 23 'type' => 'integer', // <=> Symfony\Component\Validator\Constraints\Range 'minimum' => 1, 'maximum' => 12, ]; } <?php 1 2 // api/src/Filter/MonthFilter.php 3 namespace App\Filter; 4 5 use ApiPlatform\Metadata\JsonSchemaFilterInterface; 6 // ... 7 8 final class MonthFilter implements FilterInterface, JsonSchemaFilterInterface 9 { 10 public function apply(...): void {...} 11 12 public function getSchema(Parameter $parameter): array 13 { 14 return [ 15 16 17 18 19 20 21 22 } 23 @vinceAmstoutz
  20. Filter Usage Example #[GetCollection( parameters: [ 'createdAt' => new QueryParameter(

    filter: new MonthFilter(), ), <?php 1 2 // src/ApiResource/Invoice.php 3 4 namespace App\ApiResource; 5 6 use ApiPlatform\Metadata\QueryParameter; 7 use App\Filter\MonthFilter; 8 9 10 11 12 13 castToNativeType => true 14 15 'createdAtCustomName' => new QueryParameter( 16 filter: new MonthFilter(), 17 property: 'createdAt', 18 castToNativeType => true 19 ) 20 ] 21 )] 22 class Invoice {...} 23 #[GetCollection( parameters: [ 'createdAtCustomName' => new QueryParameter( filter: new MonthFilter(), property: 'createdAt', ) <?php 1 2 // src/ApiResource/Invoice.php 3 4 namespace App\ApiResource; 5 6 use ApiPlatform\Metadata\QueryParameter; 7 use App\Filter\MonthFilter; 8 9 10 11 'createdAt' => new QueryParameter( 12 filter: new MonthFilter(), 13 castToNativeType => true 14 ), 15 16 17 18 castToNativeType => true 19 20 ] 21 )] 22 class Invoice {...} 23 castToNativeType => true castToNativeType => true <?php 1 2 // src/ApiResource/Invoice.php 3 4 namespace App\ApiResource; 5 6 use ApiPlatform\Metadata\QueryParameter; 7 use App\Filter\MonthFilter; 8 9 #[GetCollection( 10 parameters: [ 11 'createdAt' => new QueryParameter( 12 filter: new MonthFilter(), 13 14 ), 15 'createdAtCustomName' => new QueryParameter( 16 filter: new MonthFilter(), 17 property: 'createdAt', 18 19 ) 20 ] 21 )] 22 class Invoice {...} 23 GET /invoices?createdAt=7 @vinceAmstoutz // Here we retrieve 12 instead of "12" $monthValue = $parameter->getValue(); <?php 1 2 // api/src/Filter/MonthFilter.php 3 namespace App\Filter; 4 5 // ... 6 7 final class MonthFilter implements FilterInterface 8 { 9 public function apply(...): void 10 { 11 /** Parameter $parameter */ 12 $parameter = $context['parameter']; 13 14 15 16 17 // ... 18 19 } 20 } 21
  21. Document a Custom Filter use ApiPlatform\Doctrine\Common\Filter\OpenApiFilterTrait; use ApiPlatform\Metadata\OpenApiParameterFilterInterface; final class

    MonthFilter implements FilterInterface, JsonSchemaFilterInterface, OpenApiParameterFilterInterface { use OpenApiFilterTrait; <?php 1 2 // api/src/Filter/MonthFilter.php 3 namespace App\Filter; 4 5 use ApiPlatform\Metadata\JsonSchemaFilterInterface; 6 7 8 // ... 9 10 11 12 13 14 // ... 15 } 16 If the parameter value is a scalar OR an array of 1 scalar @vinceAmstoutz
  22. use ApiPlatform\Metadata\OpenApiParameterFilterInterface; final class MonthFilter implements FilterInterface, JsonSchemaFilterInterface, OpenApiParameterFilterInterface public

    function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array|null { // TODO: change default implementation return new OpenApiParameter(name: $parameter->getKey().'[]', in: 'query', style: 'deepObject', explode: true); } } <?php 1 2 // api/src/Filter/MonthFilter.php 3 namespace App\Filter; 4 5 use ApiPlatform\Metadata\JsonSchemaFilterInterface; 6 7 // ... 8 9 10 { 11 public function apply(...): void {} 12 13 14 15 16 17 18 19 // TODO: change default implementation return new OpenApiParameter(name: $parameter->getKey().'[]', in: 'query', style: 'deepObject', explode: true); <?php 1 2 // api/src/Filter/MonthFilter.php 3 namespace App\Filter; 4 5 use ApiPlatform\Metadata\JsonSchemaFilterInterface; 6 use ApiPlatform\Metadata\OpenApiParameterFilterInterface; 7 // ... 8 9 final class MonthFilter implements FilterInterface, JsonSchemaFilterInterface, OpenApiParameterFilterInterface 10 { 11 public function apply(...): void {} 12 13 public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array|null 14 { 15 16 17 } 18 } 19 Document a Custom Filter If the parameter value is not a scalar or an array of 1 scalar https://api- platform.com/docs/core/filters/#customizing-the- openapi-parameter @vinceAmstoutz
  23. Conclusion Easier to create Filters More flexible Maintainability Use of

    objects instead of arrays Compatible with the legacy filters @vinceAmstoutz
  24. use Rector\ApiPlatform\ApiPlatform50\Rector\Function\ApiFilterToQueryParameters; ->withRules([ ApiFilterToQueryParameters::class, ]); <?php 1 2 //rector.php 3

    4 use Rector\Config\RectorConfig; 5 6 7 return RectorConfig::configure() 8 9 10 11 Migrate Our Api Filters With Rector vendor/bin/rector @vinceAmstoutz
  25. Removal Of ApiFilter And SearchFilter ⚠️ Not before version 5.0

    php bin/console make:api:refactor-filters-to-parameters @vinceAmstoutz vendor/bin/rector
  26. IriConverterParameterProvider <?php // api/src/Resource/Foo.php // ... #[ApiResource(operations: [ new Get(

    parameters: [ 'related' => new QueryParameter( provider: IriConverterParameterProvider::class, filter: MyCustomFilter::class, extraProperties: ['fetch_data' => true] // Forces fetching the entity data ), ], ) ])] class Foo { // ... } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @vinceAmstoutz
  27. IriConverterParameterProvider <?php use ApiPlatform\State\ParameterProviderInterface; use ApiPlatform\Metadata\ParameterProviderFilterInterface; // ... final class

    CustomFilter implements FilterInterface, ParameterProviderFilterInterface { use BackwardCompatibleFilterDescriptionTrait; public function apply(...): void {} public static function getParameterProvider(): string { return IriConverterParameterProvider::class; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @vinceAmstoutz
  28. ReadLinkParameterProvider <?php // api/src/Resource/Foo.php // ... #[ApiResource(operations: [ new Get(

    parameters: [ 'dummy' => new QueryParameter( provider: ReadLinkParameterProvider::class, extraProperties: [ 'resource_class' => Dummy::class, 'uri_template' => '/dummies/{id}' // Optional ], // NB: This time without a preset filter (optional) ) ], ) ])] class Foo {...} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // NB: This time without a preset filter (optional) <?php 1 2 // api/src/Resource/Foo.php 3 4 // ... 5 6 #[ApiResource(operations: [ 7 new Get( 8 parameters: [ 9 'dummy' => new QueryParameter( 10 provider: ReadLinkParameterProvider::class, 11 extraProperties: [ 12 'resource_class' => Dummy::class, 13 'uri_template' => '/dummies/{id}' // Optional 14 ], 15 16 ) 17 ], 18 ) 19 ])] 20 class Foo {...} 21 @vinceAmstoutz
  29. <?php // ... use ApiPlatform\State\ParameterProvider\ReadLinkParameterProvider; use ApiPlatform\State\ParameterProviderInterface; final class CustomFilter

    implements FilterInterface, ParameterProviderFilterInterface { use BackwardCompatibleFilterDescriptionTrait; public function apply(...): void {} public static function getParameterProvider(): string { return ReadLinkParameterProvider::class; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ReadLinkParameterProvider @vinceAmstoutz