From 95d38d8497e879ce45c3bd60794d570f4addc0bc Mon Sep 17 00:00:00 2001 From: Vincent Amstoutz Date: Fri, 12 Sep 2025 14:21:31 +0200 Subject: [PATCH 1/6] docs(doctrine/filters): document new custom filter command and syntax --- core/doctrine-filters.md | 490 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 489 insertions(+), 1 deletion(-) diff --git a/core/doctrine-filters.md b/core/doctrine-filters.md index a9e6e3778e6..f3178af5a3f 100644 --- a/core/doctrine-filters.md +++ b/core/doctrine-filters.md @@ -1143,6 +1143,251 @@ retrieve data from the database. They are only applied to collections. If you wa to retrieve items, [extensions](extensions.md) are the way to go. A Doctrine ORM filter is basically a class implementing the `ApiPlatform\Doctrine\Orm\Filter\FilterInterface`. + +For `MongoDB (ODM)` filters, please refer to [Creating Custom Doctrine ODM Filters documentation](#creating-custom-doctrine-mongodb-odm-filters). + +### Creating Custom Doctrine ORM Filters With The New Syntax (API Platform >= 4.2) + +Advantages of the new approach: + +- Simplicity: No more need to extend `AbstractFilter`. A simple implementation of `FilterInterface` is all it takes. +- Clarity and Code Quality: The logic is more direct and decoupled. +- Tooling: A make command is available to generate all the boilerplate code. + +#### Generating the Filter Skeleton + +To get started, API Platform includes a very handy make command to generate the basic structure of an ORM filter: + +```console +bin/console make:filter orm +``` + +Then, provide the name of your filter, for example `MonthFilter`, or pass it directly as an argument: + +```console +make:filter orm MyCustomFilter +``` + +You will get a file at `api/src/Filter/MonthFilter.php` with the following content: + +```php +getValue(); + + // Retrieve the property + // $property = $parameter->getProperty(); + + // Retrieve alias and parameter name + // $alias = $queryBuilder->getRootAliases()[0]; + // $parameterName = $queryNameGenerator->generateParameterName($property); + + // TODO: make your awesome query using the $queryBuilder + // $queryBuilder-> + } +} +``` +#### Implementing a Custom Filter + +Let's create a concrete filter that allows fetching entities based on the month of a date field (e.g., `createdAt`). + +The goal is to be able to call a URL like `GET /invoices?createdAtMonth=7` to get all invoices created in July. + +Here is the complete and corrected code for the filter: + +```php +getValue(); + + $parameterName = $queryNameGenerator->generateParameterName($property); + $alias = $queryBuilder->getRootAliases()[0]; + + $queryBuilder + ->andWhere(sprintf('MONTH(%s.%s) = :%s', $alias, $property, $parameterName)) + ->setParameter($parameterName, $monthValue); + } +} +``` + +Now that the filter is created, it must be associated with an API resource. We use the `#[QueryParameter]` attribute on +a `GetCollection` operation for this. For other synthax please refer to + +[//]: # (TODO: add link to synthax) + +```php + new QueryParameter( + filter: new MonthFilter(), + property: 'createdAt' + ), + ] +)] +class Invoice +{ + // ... +} +``` + +And that's it! ✅ Your filter is operational. A request like `GET /invoices?createdAtMonth=7` will now correctly return +the invoices from July! + +#### Adding Custom Filter Validation And A Better Typing + +Currently, our filter accepts any value, like `createdAtMonth=99` or `createdAtMonth=foo`, which could cause errors. +To validate inputs and ensure the correct type, we can implement the `JsonSchemaFilterInterface`. + +This allows delegating validation to API Platform, respecting the [SOLID Principles](https://en.wikipedia.org/wiki/SOLID). + +```php + 'integer', + + // <=> Symfony\Component\Validator\Constraints\Range + 'minimum' => 1, + 'maximum' => 12, + ]; + } +} +``` + +With this code, under the hood, API Platform has added a Symfony Range constraint (https://symfony.com/doc/current/reference/constraints/Range.html) +that only accepts values between `1` and `12` (inclusive), which is what we want. In addition, we map the value to an integer, +which allows us to reject other types and directly return an integer in our filter when we retrieve the value with +`$monthValue = $parameter->getValue();`. + +### Documenting the Filter (OpenAPI) + +#### The Simple Method (for scalar types) + +If your filter expects a simple type (`int`, `string`, `bool`, or arrays of these types), the quickest way is to use the +`OpenApiFilterTrait`. + +```php + + */ + public function getOpenApiParameters(Parameter $parameter): array + { + // Example for a filter that expects an array of values + // like ?myParam[key1]=value1&myParam[key2]=value2 + return [ + new OpenApiParameter( + name: $parameter->getKey(), + in: 'query', + description: 'A custom filter for complex objects.', + style: 'deepObject', + explode: true + ) + ]; + } +} +``` + +### Creating Custom Doctrine ORM Filters With The Old Syntax (API Platform < 4.2) + + API Platform includes a convenient abstract class implementing this interface and providing utility methods: `ApiPlatform\Doctrine\Orm\Filter\AbstractFilter`. In the following example, we create a class to filter a collection by applying a regular expression to a property. @@ -1345,9 +1590,252 @@ class Offer ## Creating Custom Doctrine MongoDB ODM Filters +For `Doctrine ORM` filters, please refer to [Creating Custom Doctrine ORM Filters documentation](#creating-custom-doctrine-orm-filters). + Doctrine MongoDB ODM filters have access to the context created from the HTTP request and to the [aggregation builder](https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/current/reference/aggregation-builder.html) instance used to retrieve data from the database and to execute [complex operations on data](https://docs.mongodb.com/manual/aggregation/). They are only applied to collections. If you want to deal with the aggregation pipeline generated to retrieve items, [extensions](extensions.md) are the way to go. A Doctrine MongoDB ODM filter is basically a class implementing the `ApiPlatform\Doctrine\Odm\Filter\FilterInterface`. -API Platform includes a convenient abstract class implementing this interface and providing utility methods: `ApiPlatform\Doctrine\Odm\Filter\AbstractFilter`. + +### Creating Custom Doctrine ODM Filters With The New Syntax (API Platform >= 4.2) + +Advantages of the new approach: + +- Simplicity: No more need to extend `AbstractFilter`. A simple implementation of `FilterInterface` is all it takes. +- Clarity and Code Quality: The logic is more direct and decoupled. +- Tooling: A make command is available to generate all the boilerplate code. + +#### Generating the Filter Skeleton + +To get started, API Platform includes a very handy make command to generate the basic structure of an ODM filter: + +```console +bin/console make:filter odm +``` + +Then, provide the name of your filter, for example `MonthFilter`, or pass it directly as an argument: + +```console +make:filter orm MyCustomFilter +``` + +You will get a file at `api/src/Filter/MonthFilter.php` with the following content: + +```php +getValue(); + + // Retrieve the property + // $property = $parameter->getProperty(); + + // TODO: make your awesome query using the $aggregationBuilder + // $aggregationBuilder-> + } +} +``` +#### Implementing a Custom Filter + +Let's create a concrete filter that allows fetching entities based on the month of a date field (e.g., `createdAt`). + +The goal is to be able to call a URL like `GET /invoices?createdAtMonth=7` to get all invoices created in July. + +Here is the complete and corrected code for the filter: + +```php +getValue(); + + $property = $parameter->getProperty(); + + $aggregationBuilder->match( + $aggregationBuilder->expr()->operator('$expr', [ + '$eq' => [ + ['$month' => '$' . $property], + $monthValue + ] + ]) + ); + } +} +``` + +Now that the filter is created, it must be associated with an API resource. We use the `#[QueryParameter]` attribute on +a `GetCollection` operation for this. For other synthax please refer to + +[//]: # (TODO: add link to synthax) + +```php + new QueryParameter( + filter: new MonthFilter(), + property: 'createdAt' + ), + ] +)] +class Invoice +{ + // ... +} +``` + +And that's it! ✅ Your filter is operational. A request like `GET /invoices?createdAtMonth=7` will now correctly return +the invoices from July! + +#### Adding Custom Filter Validation And A Better Typing + +Currently, our filter accepts any value, like `createdAtMonth=99` or `createdAtMonth=foo`, which could cause errors. +To validate inputs and ensure the correct type, we can implement the `JsonSchemaFilterInterface`. + +This allows delegating validation to API Platform, respecting the [SOLID Principles](https://en.wikipedia.org/wiki/SOLID). + +```php + 'integer', + + // <=> Symfony\Component\Validator\Constraints\Range + 'minimum' => 1, + 'maximum' => 12, + ]; + } +} +``` + +With this code, under the hood, API Platform has added a Symfony Range constraint (https://symfony.com/doc/current/reference/constraints/Range.html) +that only accepts values between `1` and `12` (inclusive), which is what we want. In addition, we map the value to an integer, +which allows us to reject other types and directly return an integer in our filter when we retrieve the value with +`$monthValue = $parameter->getValue();`. + +### Documenting the Filter (OpenAPI) + +#### The Simple Method (for scalar types) + +If your filter expects a simple type (`int`, `string`, `bool`, or arrays of these types), the quickest way is to use the +`OpenApiFilterTrait`. + +```php + + */ + public function getOpenApiParameters(Parameter $parameter): array + { + // Example for a filter that expects an array of values + // like ?myParam[key1]=value1&myParam[key2]=value2 + return [ + new OpenApiParameter( + name: $parameter->getKey(), + in: 'query', + description: 'A custom filter for complex objects.', + style: 'deepObject', + explode: true + ) + ]; + } +} +``` + +### Creating Custom Doctrine ODM Filters With The Old Syntax (API Platform < 4.2) + +API Platform includes a convenient abstract class implementing this interface and providing utility methods: +`ApiPlatform\Doctrine\Odm\Filter\AbstractFilter`. From b4f25bb2161e61ebd36decab8811ac0bcf9f7495 Mon Sep 17 00:00:00 2001 From: Vincent Amstoutz Date: Fri, 12 Sep 2025 14:21:50 +0200 Subject: [PATCH 2/6] docs(laravel/filters): document new custom filter command and syntax --- laravel/filters.md | 104 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/laravel/filters.md b/laravel/filters.md index 482f660d92c..5fa84227eaa 100644 --- a/laravel/filters.md +++ b/laravel/filters.md @@ -281,3 +281,107 @@ A few `filterContext` options are available to configure the filter: - `whitelist` properties whitelist to avoid uncontrolled data exposure (default `null` to allow all properties) Given that the collection endpoint is `/books`, you can filter the serialization properties with the following query: `/books?properties[]=title&properties[]=author`. + +### Creating Custom Filters (API Platform >= 4.2) + +#### Generating the Filter Skeleton + +To get started, API Platform includes a very handy make command to generate the basic structure of an Laravel Eloquent filter: + +```console +bin/console make:filter +``` + +Then, provide the name of your filter, for example `MonthFilter`, or pass it directly as an argument: + +```console +make:filter MyCustomFilter +``` + +You will get a file at `app/Filter/MonthFilter.php` with the following content: + +```php + $builder + * @param array $context + */ + public function apply(Builder $builder, mixed $values, Parameter $parameter, array $context = []): Builder + { + // TODO: make your awesome query using the $builder + // return $builder-> + } +} +``` + +#### Implementing a Custom Filter + +Let's create a concrete filter that allows fetching entities based on the month of a date field (e.g., `createdAt`). + +The goal is to be able to call a URL like `GET /invoices?createdAtMonth=7` to get all invoices created in July. + +Here is the complete and corrected code for the filter: + +```php + $builder + * @param array $context + */ + public function apply(Builder $builder, mixed $values, Parameter $parameter, array $context = []): Builder + { + return $builder->->whereMonth($parameter->getProperty(), $values); + } +} +``` + +We can now use it in our resources and model like other filters, for example, as follows: +```php + Date: Fri, 12 Sep 2025 14:56:06 +0200 Subject: [PATCH 3/6] docs(filters): promote QueryParameter usage over ApiFilter The `ApiFilter` attribute is deprecated and scheduled for removal in API Platform 5.0. This commit updates the documentation to strongly recommend the modern `QueryParameter` syntax. This new approach offers better flexibility and more explicit, per-operation filter configuration. The examples have been updated to reflect this best practice. --- core/doctrine-filters.md | 56 +++++++++++++++++++++++++++++++++-- core/elasticsearch-filters.md | 54 +++++++++++++++++++++++++++++++++ core/filters.md | 13 +++++--- 3 files changed, 117 insertions(+), 6 deletions(-) diff --git a/core/doctrine-filters.md b/core/doctrine-filters.md index f3178af5a3f..6020d6fd5dc 100644 --- a/core/doctrine-filters.md +++ b/core/doctrine-filters.md @@ -3,7 +3,59 @@ For further documentation on filters (including for Eloquent and Elasticsearch), please see the [Filters documentation](filters.md). > [!WARNING] -> Prefer using QueryParameter instead of ApiFilter for more flexibility, this is subject to change in the next major version. +> For maximum flexibility and to ensure future compatibility, it is strongly recommended to configure your filters via +> the parameters attribute using `QueryParameter`. The legacy method using the `ApiFilter` attribute is **deprecated** and +> will be **removed** in version **5.0**. + +The modern way to declare filters is to associate them directly with an operation's parameters. This allows for more +precise control over the exposed properties. + +Here is the recommended approach to apply a `PartialSearchFilter` only to the title and author properties of a Book resource. + +```php + new QueryParameter( + properties: ['title', 'author'], // Only these properties get parameters created + filter: new PartialSearchFilter() + ) + ] + ) +])] +class Book { + // ... +} +``` +> [!TIP] +> This filter can be also defined directly on a specific operation like `#[GetCollection(...)])` for finer +> control, like the following code: + +```php + new QueryParameter( + properties: ['title', 'author'], // Only these properties get parameters created + filter: new PartialSearchFilter() + ) + ] +)] +class Book { + // ... +} +``` + +**Further Reading** + +- Consult the documentation on [Per-Parameter Filters (Recommended Method)](../core/filters.md#2-per-parameter-filters-recommended). +- If you are working with a legacy codebase, you can refer to the [documentation for the old syntax (deprecated)](../core/filters.md#1-legacy-filters-searchfilter-etc---not-recommended). ## Basic Knowledge @@ -112,7 +164,7 @@ class Offer } ``` -Learn more on how the [ApiFilter attribute](filters.md#apifilter-attribute) works. +Learn more on how the [ApiFilter attribute](../core/filters.md#1-legacy-filters-searchfilter-etc---not-recommended) works. For the sake of consistency, we're using the attribute in the below documentation. diff --git a/core/elasticsearch-filters.md b/core/elasticsearch-filters.md index 63f75c3857f..efad12f70a1 100644 --- a/core/elasticsearch-filters.md +++ b/core/elasticsearch-filters.md @@ -2,6 +2,60 @@ For further documentation on filters (including for Eloquent and Doctrine), please see the [Filters documentation](filters.md). +> [!WARNING] +> For maximum flexibility and to ensure future compatibility, it is strongly recommended to configure your filters via +> the parameters attribute using `QueryParameter`. The legacy method using the `ApiFilter` attribute is **deprecated** and +> will be **removed** in version **5.0**. + +The modern way to declare filters is to associate them directly with an operation's parameters. This allows for more +precise control over the exposed properties. + +Here is the recommended approach to apply a `MatchFilter` only to the title and author properties of a Book resource. + +```php + new QueryParameter( + properties: ['title', 'author'], // Only these properties get parameters created + filter: new MatchFilter() + ) + ] + ) +])] +class Book { + // ... +} +``` +> [!TIP] +> This filter can be also defined directly on a specific operation like `#[GetCollection(...)])` for finer +> control, like the following code: + +```php + new QueryParameter( + properties: ['title', 'author'], // Only these properties get parameters created + filter: new matchFilter() + ) + ] +)] +class Book { + // ... +} +``` + +**Further Reading** + +- Consult the documentation on [Per-Parameter Filters (Recommended Method)](../core/filters.md#2-per-parameter-filters-recommended). +- If you are working with a legacy codebase, you can refer to the [documentation for the old syntax (deprecated)](../core/filters.md#1-legacy-filters-searchfilter-etc---not-recommended). + ## Ordering Filter (Sorting) The order filter allows to [sort](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html) diff --git a/core/filters.md b/core/filters.md index 62f1505c1b7..9a53f11bf4a 100644 --- a/core/filters.md +++ b/core/filters.md @@ -1,5 +1,10 @@ # Parameters and Filters +For documentation on the specific filter implementations available for your persistence layer, please refer to the following pages: + +- **[Doctrine Filters](../core/doctrine-filters.md)** +- **[Elasticsearch Filters](../core/elasticsearch-filters.md)** + API Platform provides a generic and powerful system to apply filters, sort criteria, and handle other request parameters. This system is primarily managed through **Parameter attributes** (`#[QueryParameter]` and `#[HeaderParameter]`), which allow for detailed and explicit configuration of how an API consumer can interact with a resource. These parameters can be linked to **Filters**, which are classes that contain the logic for applying criteria to your persistence backend (like Doctrine ORM or MongoDB ODM). @@ -8,10 +13,10 @@ You can declare parameters on a resource class to apply them to all operations,

Filtering and Searching screencast
Watch the Filtering & Searching screencast

-For documentation on the specific filter implementations available for your persistence layer, please refer to the following pages: - -* [Doctrine Filters](../core/doctrine-filters.md) -* [Elasticsearch Filters](../core/elasticsearch-filters.md) +> [!WARNING] +> For maximum flexibility and to ensure future compatibility, it is strongly recommended to configure your filters via +> the parameters attribute using `QueryParameter`. The legacy method using the `ApiFilter` attribute is **deprecated** and +> will be **removed** in version **5.0**. ## Declaring Parameters From cf24ec5481cedab7eb598ec6ff8334f6264cb372 Mon Sep 17 00:00:00 2001 From: Vincent Amstoutz Date: Fri, 12 Sep 2025 16:17:41 +0200 Subject: [PATCH 4/6] docs(filters): add documentation for new search filters Document the newly introduced search filters (`IriFilter`, `ExactFilter`, `PartialSearchFilter`, `FreeTextQueryFilter`, `OrFilter`) and clarify the deprecation of the `SearchFilter`. Encourage users to migrate to `QueryParameter` attributes for better configurability and explicitness. --- core/doctrine-filters.md | 198 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 197 insertions(+), 1 deletion(-) diff --git a/core/doctrine-filters.md b/core/doctrine-filters.md index 6020d6fd5dc..7039399cc84 100644 --- a/core/doctrine-filters.md +++ b/core/doctrine-filters.md @@ -171,7 +171,21 @@ For the sake of consistency, we're using the attribute in the below documentatio For MongoDB ODM, all the filters are in the namespace `ApiPlatform\Doctrine\Odm\Filter`. The filter services all begin with `api_platform.doctrine_mongodb.odm`. -## Search Filter +## Search Filter (not recommended) + +> [!WARNING] +> Instead of using the deprecated `SearchFilter` its recommended to use the new search filters with QueryParameter attributes + +### Built-in new Search Filters (API Platform >= 4.2) + +To add some search filters, choose over this new list: +- [IriFilter](#iri-filter) (filter on IRIs) +- [ExactFilter](#exact-filter) (filter with exact value) +- [PartialSearchFilter](#partial-search-filter) (filter using a `LIKE %value%``) +- [FreeTextQueryFilter](#free-text-query-filter) (allows you to apply multiple filters to multiple properties of a resource at the same time, using a single parameter in the URL) +- [OrFilter](#or-filter) (apply a filter using `orWhere` instead of `andWhere` ) + +### Legacy SearchFilter (API Platform < 4.2)) If Doctrine ORM or MongoDB ODM support is enabled, adding filters is as easy as registering a filter service in the `api/config/services.yaml` file and adding an attribute to your resource configuration. @@ -293,6 +307,188 @@ Using a numeric ID is also supported: `http://localhost:8000/api/offers?product= The above URLs will return all offers for the product having the following IRI as JSON-LD identifier (`@id`): `http://localhost:8000/api/products/12`. +## Iri Filter + +The iri filter allows filtering a resource using IRIs. + +Syntax: `?property=value` + +The value can take any [IRI(Internationalized Resource Identifier) ](https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier). + +Like other [new search filters](#built-in-new-search-filters-api-platform--42) it can be used on the ApiResource attribute +or in the operation attribute, for e.g., the `#GetCollection()` attribute: + +```php +// api/src/ApiResource/Chicken.php + +#[GetCollection( + parameters: [ + 'chickenCoop' => new QueryParameter(filter: new IriFilter()), + ], +)] +class Chicken +{ + //... +} +``` + +Given that the endpoint is `/chickens`, you can filter chickens by chicken coop with the following query: +`/chikens?chickenCoop=/chickenCoop/1`. + +It will return all the chickens that live the chicken coop number 1. + +## Exact Filter + +The exact filter allows filtering a resource using exact values. + +Syntax: `?property=value` + +The value can take any scalar value or array of values. + +Like other [new search filters](#built-in-new-search-filters-api-platform--42) it can be used on the ApiResource attribute +or in the operation attribute, for e.g., the `#GetCollection()` attribute: + +```php +// api/src/ApiResource/Chicken.php + +#[GetCollection( + parameters: [ + 'name' => new QueryParameter(filter: new ExactFilter()), + ], +)] +class Chicken +{ + //... +} +``` + +Given that the endpoint is `/chickens`, you can filter chickens by name with the following query: +`/chikens?name=Gertrude`. + +It will return all the chickens that are exactly named _Gertrude_. + +## Partial Search Filter + +The partial search filter allows filtering a resource using partial values. + +Syntax: `?property=value` + +The value can take any scalar value or array of values. + +Like other [new search filters](#built-in-new-search-filters-api-platform--42) it can be used on the ApiResource attribute +or in the operation attribute, for e.g., the `#GetCollection()` attribute: + +```php +// api/src/ApiResource/Chicken.php + +#[GetCollection( + parameters: [ + 'name' => new QueryParameter(filter: new PartialSearchFilter()), + ], +)] +class Chicken +{ + //... +} +``` + +Given that the endpoint is `/chickens`, you can filter chickens by name with the following query: +`/chikens?name=tom`. + +It will return all chickens where the name contains the substring _tom_. + +> [!NOTE] +> This filter performs a case-insensitive search. It automatically normalizes both the input value and the stored data +> (e.g., by converting them to lowercase) before making the comparison. + +## Free Text Query Filter + +The free text query filter allows filtering allows you to apply a single filter across a list of properties. Its primary +role is to repeat a filter's logic for each specified field. + +Syntax: `?property=value` + +The value can take any scalar value or array of values. + +Like other [new search filters](#built-in-new-search-filters-api-platform--42) it can be used on the ApiResource attribute +or in the operation attribute, for e.g. the `#GetCollection()` attribute: + +```php +// api/src/ApiResource/Chicken.php + +#[GetCollection( + parameters: [ + 'q' => new QueryParameter( + filter: new FreeTextQueryFilter(new PartialSearchFilter()), + properties: ['name', 'ean'] + ), + ], +)] +class Chicken +{ + //... +} +``` + +Given that the endpoint is `/chickens`, you can filter chickens by name with the following query: +`/chikens?q=tom`. + +**Result**: + +This request will return all chickens where: + +- the `name` is exactly "FR123456" +- **AND** +- the `ean` is exactly "FR123456". + +For the `OR` option refer to the [OrFilter](#or-filter). + +## Or Filter + +The or filter allows you to explicitly change the logical condition used by the filter it wraps. Its sole purpose is to +force a filter to combine its criteria with OR instead of the default AND. + +It's the ideal tool for creating a search parameter that should find a match in any of the specified fields, +but not necessarily all of them. + +Syntax: `?property=value` + +The value can take any scalar value or array of values. + +The `OrFilter` is a decorator: it is used by "wrapping" another, more specific filter (like for e.g. `PartialSearchFilter` +or `ExactFilter`). + +The real power emerges when you combine these decorators. For instance, to create an "autocomplete" feature that finds +exact matches in one of several fields. Example of usage: + +```php +// api/src/ApiResource/Chicken.php + +#[GetCollection( + parameters: [ + 'autocomplete' => new QueryParameter( + filter: new FreeTextQueryFilter(new OrFilter(new ExactFilter())), + properties: ['name', 'ean'] + ), + ], +)] +class Chicken +{ + //... +} +``` + +Given that the endpoint is `/chickens`, you can filter chickens by name with the following query: +`/chikens?autocomplete=tom`. + +**Result**: + +This request will return all chickens where: + +- the `name` is exactly "FR123456" +- OR +- the `ean` is exactly "FR123456". + ## Date Filter The date filter allows filtering a collection by date intervals. From 9e83ed0a36a5a1f1f961308756a81445dd041d0e Mon Sep 17 00:00:00 2001 From: Vincent Amstoutz Date: Fri, 12 Sep 2025 16:56:51 +0200 Subject: [PATCH 5/6] fix: lint --- core/doctrine-filters.md | 66 ++++++++++++++++++++-------------------- laravel/filters.md | 6 ++-- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/core/doctrine-filters.md b/core/doctrine-filters.md index 7039399cc84..2f23b60e3d4 100644 --- a/core/doctrine-filters.md +++ b/core/doctrine-filters.md @@ -1,10 +1,12 @@ # Doctrine ORM and MongoDB ODM Filters +## Introduction + For further documentation on filters (including for Eloquent and Elasticsearch), please see the [Filters documentation](filters.md). > [!WARNING] > For maximum flexibility and to ensure future compatibility, it is strongly recommended to configure your filters via -> the parameters attribute using `QueryParameter`. The legacy method using the `ApiFilter` attribute is **deprecated** and +> the parameters attribute using `QueryParameter`. The legacy method using the `ApiFilter` attribute is **deprecated** and > will be **removed** in version **5.0**. The modern way to declare filters is to associate them directly with an operation's parameters. This allows for more @@ -32,7 +34,7 @@ class Book { } ``` > [!TIP] -> This filter can be also defined directly on a specific operation like `#[GetCollection(...)])` for finer +> This filter can be also defined directly on a specific operation like `#[GetCollection(...)])` for finer > control, like the following code: ```php @@ -179,7 +181,7 @@ services all begin with `api_platform.doctrine_mongodb.odm`. ### Built-in new Search Filters (API Platform >= 4.2) To add some search filters, choose over this new list: -- [IriFilter](#iri-filter) (filter on IRIs) +- [IriFilter](#iri-filter) (filter on IRIs) - [ExactFilter](#exact-filter) (filter with exact value) - [PartialSearchFilter](#partial-search-filter) (filter using a `LIKE %value%``) - [FreeTextQueryFilter](#free-text-query-filter) (allows you to apply multiple filters to multiple properties of a resource at the same time, using a single parameter in the URL) @@ -313,7 +315,7 @@ The iri filter allows filtering a resource using IRIs. Syntax: `?property=value` -The value can take any [IRI(Internationalized Resource Identifier) ](https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier). +The value can take any [IRI(Internationalized Resource Identifier)](https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier). Like other [new search filters](#built-in-new-search-filters-api-platform--42) it can be used on the ApiResource attribute or in the operation attribute, for e.g., the `#GetCollection()` attribute: @@ -486,7 +488,7 @@ Given that the endpoint is `/chickens`, you can filter chickens by name with the This request will return all chickens where: - the `name` is exactly "FR123456" -- OR +- OR - the `ean` is exactly "FR123456". ## Date Filter @@ -1402,7 +1404,7 @@ Advantages of the new approach: - Clarity and Code Quality: The logic is more direct and decoupled. - Tooling: A make command is available to generate all the boilerplate code. -#### Generating the Filter Skeleton +#### Generating the Filter ORM Skeleton To get started, API Platform includes a very handy make command to generate the basic structure of an ORM filter: @@ -1495,10 +1497,8 @@ class MonthFilter implements FilterInterface } ``` -Now that the filter is created, it must be associated with an API resource. We use the `#[QueryParameter]` attribute on -a `GetCollection` operation for this. For other synthax please refer to - -[//]: # (TODO: add link to synthax) +Now that the filter is created, it must be associated with an API resource. We use the `#[QueryParameter]` attribute on +a `GetCollection` operation for this. For other syntax please refer to the [documentation](#introduction). ```php new QueryParameter( - filter: new MonthFilter(), - property: 'createdAt' + 'createdAtMonth' => new QueryParameter( + filter: new MonthFilter(), + property: 'createdAt' ), ] )] @@ -1527,9 +1527,9 @@ class Invoice And that's it! ✅ Your filter is operational. A request like `GET /invoices?createdAtMonth=7` will now correctly return the invoices from July! -#### Adding Custom Filter Validation And A Better Typing +#### Adding Custom Filter ORM Validation And A Better Typing -Currently, our filter accepts any value, like `createdAtMonth=99` or `createdAtMonth=foo`, which could cause errors. +Currently, our filter accepts any value, like `createdAtMonth=99` or `createdAtMonth=foo`, which could cause errors. To validate inputs and ensure the correct type, we can implement the `JsonSchemaFilterInterface`. This allows delegating validation to API Platform, respecting the [SOLID Principles](https://en.wikipedia.org/wiki/SOLID). @@ -1550,8 +1550,8 @@ final class MonthFilter implements FilterInterface, JsonSchemaFilterInterface public function getSchema(Parameter $parameter): array { return [ - 'type' => 'integer', - + 'type' => 'integer', + // <=> Symfony\Component\Validator\Constraints\Range 'minimum' => 1, 'maximum' => 12, @@ -1560,14 +1560,14 @@ final class MonthFilter implements FilterInterface, JsonSchemaFilterInterface } ``` -With this code, under the hood, API Platform has added a Symfony Range constraint (https://symfony.com/doc/current/reference/constraints/Range.html) +With this code, under the hood, API Platform has added a [Symfony Range constraint](https://symfony.com/doc/current/reference/constraints/Range.html) that only accepts values between `1` and `12` (inclusive), which is what we want. In addition, we map the value to an integer, which allows us to reject other types and directly return an integer in our filter when we retrieve the value with `$monthValue = $parameter->getValue();`. -### Documenting the Filter (OpenAPI) +### Documenting the ORM Filter (OpenAPI) -#### The Simple Method (for scalar types) +#### The Simple Method (for scalar types) On A Custom ORM Filter If your filter expects a simple type (`int`, `string`, `bool`, or arrays of these types), the quickest way is to use the `OpenApiFilterTrait`. @@ -1593,7 +1593,7 @@ final class MonthFilter implements FilterInterface, JsonSchemaFilterInterface, O That's all! The trait takes care of generating the corresponding OpenAPI documentation. 🚀 -#### The Custom Method to Documenting the Filter (OpenAPI) +#### The Custom Method to Documenting the ORM Filter (OpenAPI) If your filter expects more complex data (an object, a specific format), you must implement the `getOpenApiParameters` method manually. @@ -1854,7 +1854,7 @@ Advantages of the new approach: - Clarity and Code Quality: The logic is more direct and decoupled. - Tooling: A make command is available to generate all the boilerplate code. -#### Generating the Filter Skeleton +#### Generating the Filter ODM Skeleton To get started, API Platform includes a very handy make command to generate the basic structure of an ODM filter: @@ -1901,7 +1901,7 @@ class MonthFilter implements FilterInterface } } ``` -#### Implementing a Custom Filter +#### Implementing a Custom ODM Filter Let's create a concrete filter that allows fetching entities based on the month of a date field (e.g., `createdAt`). @@ -1962,9 +1962,9 @@ use App\Filters\MonthFilter; #[GetCollection( parameters: [ - 'createdAtMonth' => new QueryParameter( - filter: new MonthFilter(), - property: 'createdAt' + 'createdAtMonth' => new QueryParameter( + filter: new MonthFilter(), + property: 'createdAt' ), ] )] @@ -1977,7 +1977,7 @@ class Invoice And that's it! ✅ Your filter is operational. A request like `GET /invoices?createdAtMonth=7` will now correctly return the invoices from July! -#### Adding Custom Filter Validation And A Better Typing +#### Adding Custom Filter ODM Validation And A Better Typing Currently, our filter accepts any value, like `createdAtMonth=99` or `createdAtMonth=foo`, which could cause errors. To validate inputs and ensure the correct type, we can implement the `JsonSchemaFilterInterface`. @@ -2000,8 +2000,8 @@ final class MonthFilter implements FilterInterface, JsonSchemaFilterInterface public function getSchema(Parameter $parameter): array { return [ - 'type' => 'integer', - + 'type' => 'integer', + // <=> Symfony\Component\Validator\Constraints\Range 'minimum' => 1, 'maximum' => 12, @@ -2010,14 +2010,14 @@ final class MonthFilter implements FilterInterface, JsonSchemaFilterInterface } ``` -With this code, under the hood, API Platform has added a Symfony Range constraint (https://symfony.com/doc/current/reference/constraints/Range.html) +With this code, under the hood, API Platform has added a [Symfony Range constraint](https://symfony.com/doc/current/reference/constraints/Range.html) that only accepts values between `1` and `12` (inclusive), which is what we want. In addition, we map the value to an integer, which allows us to reject other types and directly return an integer in our filter when we retrieve the value with `$monthValue = $parameter->getValue();`. -### Documenting the Filter (OpenAPI) +### Documenting the ODM Filter (OpenAPI) -#### The Simple Method (for scalar types) +#### The Simple Method (for scalar types) On A Custom ODM Filter If your filter expects a simple type (`int`, `string`, `bool`, or arrays of these types), the quickest way is to use the `OpenApiFilterTrait`. @@ -2043,7 +2043,7 @@ final class MonthFilter implements FilterInterface, JsonSchemaFilterInterface, O That's all! The trait takes care of generating the corresponding OpenAPI documentation. 🚀 -#### The Custom Method to Documenting the Filter (OpenAPI) +#### The Custom Method to Documenting the Filter (OpenAPI) On A Custom ODM Filter If your filter expects more complex data (an object, a specific format), you must implement the `getOpenApiParameters` method manually. diff --git a/laravel/filters.md b/laravel/filters.md index 5fa84227eaa..433f99c4034 100644 --- a/laravel/filters.md +++ b/laravel/filters.md @@ -284,7 +284,7 @@ Given that the collection endpoint is `/books`, you can filter the serialization ### Creating Custom Filters (API Platform >= 4.2) -#### Generating the Filter Skeleton +#### Generating the Laravel Eloquent Filter Skeleton To get started, API Platform includes a very handy make command to generate the basic structure of an Laravel Eloquent filter: @@ -327,7 +327,7 @@ final class MonthFilter implements FilterInterface } ``` -#### Implementing a Custom Filter +#### Implementing a Custom Laravel Eloquent Filter Let's create a concrete filter that allows fetching entities based on the month of a date field (e.g., `createdAt`). @@ -360,7 +360,7 @@ final class MonthFilter implements FilterInterface } } ``` - + We can now use it in our resources and model like other filters, for example, as follows: ```php Date: Mon, 15 Sep 2025 09:32:56 +0200 Subject: [PATCH 6/6] docs(filters): enhance validation Add notes on automatic validation benefits and reference `ParameterValidatorProvider` for Symfony and Laravel. Clarify the usage of `ParameterExtension` for handling empty values in Doctrine ORM. --- core/doctrine-filters.md | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/core/doctrine-filters.md b/core/doctrine-filters.md index 2f23b60e3d4..67e560d58d4 100644 --- a/core/doctrine-filters.md +++ b/core/doctrine-filters.md @@ -181,7 +181,7 @@ services all begin with `api_platform.doctrine_mongodb.odm`. ### Built-in new Search Filters (API Platform >= 4.2) To add some search filters, choose over this new list: -- [IriFilter](#iri-filter) (filter on IRIs) +- [IriFilter](#iri-filter) (filter on IRIs) - [ExactFilter](#exact-filter) (filter with exact value) - [PartialSearchFilter](#partial-search-filter) (filter using a `LIKE %value%``) - [FreeTextQueryFilter](#free-text-query-filter) (allows you to apply multiple filters to multiple properties of a resource at the same time, using a single parameter in the URL) @@ -1534,6 +1534,18 @@ To validate inputs and ensure the correct type, we can implement the `JsonSchema This allows delegating validation to API Platform, respecting the [SOLID Principles](https://en.wikipedia.org/wiki/SOLID). +> [!NOTE] +> Even with our internal systems, some additional **manual validation** is needed to ensure greater accuracy. However, +> we already take care of a lot of these validations for you. +> +> You can see how this works directly in our code components: +> +> * The `ParameterValidatorProvider` for **Symfony** can be found [here](https://github.com/api-platform/core/blob/c9692b509d5b641104addbadb349b9bcab83e251/src/Symfony/Validator/State/ParameterValidatorProvider.php). +> * The `ParameterValidatorProvider` for **Laravel** is located [here](https://github.com/api-platform/core/blob/c9692b509d5b641104addbadb349b9bcab83e251/src/Laravel/State/ParameterValidatorProvider.php). +> +> Additionally, we filter out empty values within our `ParameterExtension` classes. For instance, the **Doctrine ORM** +> `ParameterExtension` [handles this filtering here](https://github.com/api-platform/core/blob/c9692b509d5b641104addbadb349b9bcab83e251/src/Doctrine/Orm/Extension/ParameterExtension.php#L51C13-L53C14). + ```php getValue();`. +With this code, under the hood, API Platform automatically adds a [Symfony Range constraint](https://symfony.com/doc/current/reference/constraints/Range.html). +This ensures the parameter only accepts values between `1` and `12` (inclusive), which is exactly what we need. + +This approach offers two key benefits: + +- Automatic Validation: It rejects other data types and invalid values, so you get an integer directly. +- Simplified Logic: You can retrieve the value with `$monthValue = $parameter->getValue();` knowing it's already a +- validated integer. + +This means you **don't have to add custom validation to your filter class, entity, or model**. The validation is handled +for you, making your code cleaner and more efficient. ### Documenting the ORM Filter (OpenAPI)