API Platform Core provides a generic system to apply filters on collections. Useful filters for the Doctrine ORM are provided with the library. You can also create custom filters that would fit your specific needs. You can also add filtering support to your custom data providers by implementing interfaces provided by the library.
By default, all filters are disabled. They must be enabled explicitly.
When a filter is enabled, it is automatically documented as a hydra:search
property in the collection response. It also
automatically appears in the NelmioApiDoc documentation if it is available.
If Doctrine ORM support is enabled, adding filters is as easy as registering a filter service in the app/config/api_filters.yml
file and adding an attribute to your resource configuration.
The search filter supports exact
, partial
, start
, end
, and word_start
matching strategies:
partial
strategy uses LIKE %text%
to search for fields that containing the text.start
strategy uses LIKE text%
to search for fields that starts with text.end
strategy uses LIKE %text
to search for fields that ends with text.word_start
strategy uses LIKE text% OR LIKE % text%
to search for fields that contains the word starting with text
.Prepend the letter i
to the filter if you want it to be case insensitive. For example ipartial
or iexact
. Note that
this will use the LOWER
function and will impact performance if there is no proper index.
Case insensitivity may already be enforced at the database level depending on the collation
used. If you are using MySQL, note that the commonly used utf8_unicode_ci
collation (and its sibling utf8mb4_unicode_ci
)
are already case insensitive, as indicated by the _ci
part in their names.
In the following example, we will see how to allow the filtering of a list of e-commerce offers:
# app/config/api_filters.yml
services:
offer.search_filter:
parent: 'api_platform.doctrine.orm.search_filter'
arguments: [ { id: 'exact', price: 'exact', name: 'partial' } ]
tags: [ 'api_platform.filter' ]
<?php
// src/AppBundle/Entity/Offer.php
namespace AppBundle\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"filters"={"offer.search_filter"}})
*/
class Offer
{
// ...
}
http://localhost:8000/api/offers?price=10
will return all offers with a price being exactly 10
.
http://localhost:8000/api/offers?name=shirt
will return all offers with a description containing the word “shirt”.
Filters can be combined together: http://localhost:8000/api/offers?price=10&name=shirt
It is possible to filter on relations too:
# app/config/api_filters.yml
services:
offer.search_filter:
parent: 'api_platform.doctrine.orm.search_filter'
arguments: [ { product: 'exact' } ]
tags: [ 'api_platform.filter' ]
With this service definition, it is possible to find all offers belonging to the product identified by a given IRI.
Try the following: http://localhost:8000/api/offers?product=/api/products/12
Using a numeric ID is also supported: http://localhost:8000/api/offers?product=12
Previous URLs will return all offers for the product having the following IRI as JSON-LD identifier (@id
): http://localhost:8000/api/products/12
.
The date filter allows for filtering a collection by date intervals.
Syntax: ?property[<after|before|strictly_after|strictly_before>]=value
The value can take any date format supported by the \DateTime
constructor.
The after
and before
filters will filter including the value whereas strictly_after
and strictly_before
will filter excluding the value.
As others filters, the date filter must be explicitly enabled:
# app/config/api_filters.yml
services:
# Enable date filter for for the property "dateProperty" of the resource "offer"
offer.date_filter:
parent: 'api_platform.doctrine.orm.date_filter'
arguments: [ { dateProperty: ~ } ]
tags: [ 'api_platform.filter' ]
<?php
// src/AppBundle/Entity/Offer.php
namespace AppBundle\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"filters"={"offer.date_filter"}})
*/
class Offer
{
// ...
}
null
ValuesThe date filter is able to deal with date properties having null
values.
Four behaviors are available at the property level of the filter:
Description | Strategy to set |
---|---|
Use the default behavior of the DBMS | null |
Exclude items | ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter::EXCLUDE_NULL (exclude_null ) |
Consider items as oldest | ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter::INCLUDE_NULL_BEFORE (include_null_before ) |
Consider items as youngest | ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter::INCLUDE_NULL_AFTER (include_null_after ) |
For instance, exclude entries with a property value of null
, with the following service definition:
# app/config/api_filters.yml
services:
offer.date_filter:
parent: 'api_platform.doctrine.orm.date_filter'
arguments: [ { dateProperty: 'exclude_null' } ]
tags: [ 'api_platform.filter' ]
If you use a service definition format other than YAML, you can use the ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter::EXCLUDE_NULL
constant directly.
The boolean filter allows you to search on boolean fields and values.
Syntax: ?property=[true|false|1|0]
You can either use TRUE or true, the parameters are case insensitive.
Enable the filter:
# app/config/api_filters.yml
services:
offer.boolean_filter:
parent: 'api_platform.doctrine.orm.boolean_filter'
arguments: [ { isAvailableGenericallyInMyCountry: ~ } ]
tags: [ 'api_platform.filter' ]
<?php
// src/AppBundle/Entity/Offer.php
namespace AppBundle\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"filters"={"offer.boolean_filter"}})
*/
class Offer
{
// ...
}
Given that the collection endpoint is /offers
, you can filter offers by boolean with the following query: /offers?isAvailableGenericallyInMyCountry=true
.
It will return all offers where isAvailableGenericallyInMyCountry
equals true
.
The numeric filter allows you to search on numeric fields and values.
Syntax: ?property=int|bigint|decimal...
Enable the filter:
# app/config/api_filters.yml
services:
offer.numeric_filter:
parent: 'api_platform.doctrine.orm.numeric_filter'
arguments: [ { sold: ~ } ]
tags: [ 'api_platform.filter' ]
<?php
// src/AppBundle/Entity/Offer.php
namespace AppBundle\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"filters"={"offer.numeric_filter"}})
*/
class Offer
{
// ...
}
Given that the collection endpoint is /offers
, you can filter offers by boolean with the following query: /offers?sold=1
.
It will return all offers with sold
equals 1
.
The range filter allows you to filter by a value Lower than, Greater than, Lower than or equal, Greater than or equal and between two values.
Syntax: ?property[lt]|[gt]|[lte]|[gte]|[between]=value
Enable the filter:
# app/config/api_filters.yml
services:
offer.range_filter:
parent: 'api_platform.doctrine.orm.range_filter'
arguments: [ { price: ~ } ]
tags: [ 'api_platform.filter' ]
<?php
// src/AppBundle/Entity/Offer.php
namespace AppBundle\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"filters"={"offer.range_filter"}})
*/
class Offer
{
// ...
}
Given that the collection endpoint is /offers
, you can filters the price with the following query: /offers?price[between]=12.99..15.99
.
It will return all offers with price
between 12.99 and 15.99.
You can filter offers by joining two values, for example: /offers?price[gt]=12.99&price[lt]=19.99
.
The order filter allows to order a collection against the given properties.
Syntax: ?order[property]=<asc|desc>
Enable the filter:
# app/config/api_filters.yml
services:
offer.order_filter:
parent: 'api_platform.doctrine.orm.order_filter'
arguments: [ { id: ~, name: ~ } ]
tags: [ 'api_platform.filter' ]
<?php
// src/AppBundle/Entity/Offer.php
namespace AppBundle\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"filters"={"offer.order_filter"}})
*/
class Offer
{
// ...
}
Given that the collection endpoint is /offers
, you can filter offers by name in ascending order and then by ID in descending
order with the following query: /offers?order[name]=desc&order[id]=asc
.
By default, whenever the query does not specify the direction explicitly (e.g: /offers?order[name]&order[id]
), filters
will not be applied unless you configure a default order direction to use:
# app/config/api_filters.yml
services:
offer.order_filter:
parent: 'api_platform.doctrine.orm.order_filter'
arguments: [ { id: { default_direction: 'ASC' }, name: { default_direction: 'DESC' } } ]
tags: [ 'api_platform.filter' ]
When the property used for ordering can contain null
values, you may want to specify how null
values are treated in
the comparison:
Description | Strategy to set |
---|---|
Use the default behavior of the DBMS | null |
Consider items as smallest | ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter::NULLS_SMALLEST (nulls_smallest ) |
Consider items as largest | ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter::NULLS_LARGEST (nulls_largest ) |
For instance, treat entries with a property value of null
as the smallest, with the following service definition:
# app/config/api_filters.yml
services:
offer.order_filter:
parent: 'api_platform.doctrine.orm.order_filter'
arguments: [ { validFrom: { nulls_comparison: 'nulls_smallest' } } ]
tags: [ 'api_platform.filter' ]
If you use a service definition format other than YAML, you can use the ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter::NULLS_SMALLEST
constant directly.
A conflict will occur if order
is also the name of a property with the search filter enabled.
Luckily, the query parameter name to use is configurable:
# app/config/config.yml
api_platform:
collection:
order_parameter_name: '_order' # the URL query parameter to use is now "_order"
Sometimes, you need to be able to perform filtering based on some linked resources (on the other side of a relation). All
built-in filters support nested properties using the dot (.
) syntax, e.g.:
# app/config/api_filters.yml
services:
offer.search_filter:
parent: 'api_platform.doctrine.orm.search_filter'
arguments: [ { product.color: 'exact' } ]
tags: [ 'api_platform.filter' ]
offer.order_filter:
parent: 'api_platform.doctrine.orm.order_filter'
arguments: [ { product.releaseDate: ~ } ]
tags: [ 'api_platform.filter' ]
The above allows you to find offers by their respective product’s color: http://localhost:8000/api/offers?product.color=red
,
or order offers by the product’s release date: http://localhost:8000/api/offers?order[product.releaseDate]=desc
As we have seen in previous examples, properties where filters can be applied must be explicitly declared. If you don’t care about security and performance (e.g. an API with restricted access), it is also possible to enable built-in filters for all properties:
# app/config/api_filters.yml
services:
# Filter enabled for all properties
offer.order_filter:
parent: 'api_platform.doctrine.orm.order_filter'
tags: [ 'api_platform.filter' ]
Note: Filters on nested properties must still be enabled explicitly, in order to keep things sane
Regardless of this option, filters can by applied on a property only if:
asc
or desc
for the order filters).It means that the filter will be silently ignored if the property:
The group filter allows you to filter by serialization groups.
Syntax: ?groups[]=<group>
You can add as many groups as you need.
Enable the filter:
# app/config/api_filters.yml
services:
book.group_filter:
parent: 'api_platform.serializer.group_filter'
arguments: # Default arguments values
- 'groups' # The query parameter name
- false # Allow to override the default serialization groups
tags: [ 'api_platform.filter' ]
<?php
// src/AppBundle/Entity/Book.php
namespace AppBundle\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"filters"={"book.group_filter"}})
*/
class Book
{
// ...
}
Given that the collection endpoint is /books
, you can filter by serialization groups with the following query: /books?groups[]=read&groups[]=write
.
By default the query parameter name is groups
but you can easily customize it to another by changing the first argument.
For example, with serialization_groups
as name:
# app/config/api_filters.yml
services:
book.group_filter:
parent: 'api_platform.serializer.group_filter'
arguments: [ 'serialization_groups' ]
tags: [ 'api_platform.filter' ]
You can also override the default serialization groups when you use the filter by changing the second argument to
true
:
# app/config/api_filters.yml
services:
book.group_filter:
parent: 'api_platform.serializer.group_filter'
arguments: [ 'groups', true ]
tags: [ 'api_platform.filter' ]
To avoid uncontrolled data exposure, you can also specify a whitelist of serialization groups as a third argument:
# app/config/api_filters.yml
services:
book.group_filter:
parent: 'api_platform.serializer.group_filter'
arguments: [ 'groups', false, ['allowed_group', 'safe_group'] ]
tags: [ 'api_platform.filter' ]
The property filter adds the possibility to filter serialization properties.
Syntax: ?properties[]=<property>
You can add as many properties as you need.
Enable the filter:
# app/config/api_filters.yml
services:
book.property_filter:
parent: 'api_platform.serializer.property_filter'
arguments: # Default arguments values
- 'properties' # The query parameter name
- false # Allow to override the default serialization properties
tags: [ 'api_platform.filter' ]
<?php
// src/AppBundle/Entity/Book.php
namespace AppBundle\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"filters"={"book.property_filter"}})
*/
class Book
{
// ...
}
Given that the collection endpoint is /books
, you can filter the serialization properties with the following query: /books?properties[]=title&properties[]=author
.
By default the query parameter name is properties
but you can easily customize it to another by changing the first argument.
For example, with serialization_properties
as name:
# app/config/api_filters.yml
services:
book.property_filter:
parent: 'api_platform.serializer.property_filter'
arguments: [ 'serialization_properties' ]
tags: [ 'api_platform.filter' ]
You can also override the default serialization properties when you use the filter by changing the second argument to
true
:
# app/config/api_filters.yml
services:
book.property_filter:
parent: 'api_platform.serializer.property_filter'
arguments: [ 'properties', true ]
tags: [ 'api_platform.filter' ]
To avoid uncontrolled data exposure, you can also specify a whitelist of properties as a third argument:
# app/config/api_filters.yml
services:
book.group_filter:
parent: 'api_platform.serializer.group_filter'
arguments: [ 'groups', false, ['allowed_property', {'nested': ['safe_property']}] ]
tags: [ 'api_platform.filter' ]
Custom filters can be written by implementing the ApiPlatform\Core\Api\FilterInterface
interface.
API Platform provides a convenient way to create Doctrine ORM filters. If you use custom data providers, you can still create filters by implementing the previously mentioned interface, but - as API Platform isn’t aware of your persistence system’s internals - you have to create the filtering logic by yourself.
Doctrine filters have access to the HTTP request (Symfony’s Request
object) and to the QueryBuilder
instance used to
retrieve data from the database. They are only applied to collections. If you want to deal with the DQL query generated
to retrieve items, or don’t need to access the HTTP request, extensions are the way to go.
A Doctrine ORM filter is basically a class implementing the ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\FilterInterface
.
API Platform includes a convenient abstract class implementing this interface and providing utility methods: ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter
In the following example, we create a class to filter a collection by applying a regexp to a property. The REGEXP
DQL
function used in this example can be found in the DoctrineExtensions
library. This library must be properly installed and registered to use this example (works only with MySQL).
<?php
// src/AppBundle/Filter/RegexpFilter.php
namespace AppBundle\Filter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use Doctrine\ORM\QueryBuilder;
final class RegexpFilter extends AbstractFilter
{
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
{
$parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters
$queryBuilder
->andWhere(sprintf('REGEXP(o.%s, :%s) = 1', $property, $parameterName))
->setParameter($parameterName, $value);
}
// This function is only used to hook in documentation generators (supported by Swagger and Hydra)
public function getDescription(string $resourceClass): array
{
$description = [];
foreach ($this->properties as $property => $strategy) {
$description['regexp_'.$property] = [
'property' => $property,
'type' => 'string',
'required' => false,
'swagger' => [
'description' => 'Filter using a regex. This will appear in the Swagger documentation!',
'name' => 'Custom name to use in the Swagger documentation',
'type' => 'Will appear below the name in the Swagger documentation',
],
];
}
return $description;
}
}
Then, register this filter as a service:
# app/config/api_filters.yml
services:
'AppBundle\Filter\RegexpFilter':
tags: [ 'api_platform.filter' ]
In the previous example, the filter can be applied on any property. However, thanks to the AbstractFilter
class,
it can also be enabled for some properties:
# app/config/api_filters.yml
services:
'AppBundle\Filter\RegexpFilter':
arguments: [ '@doctrine', '@request_stack', '@?logger', { email: ~, anOtherProperty: ~ } ]
tags: [ 'api_platform.filter' ]
Finally, add this filter to resources you want to be filtered:
<?php
// src/AppBundle/Entity/Offer.php
namespace AppBundle\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use AppBundle\Filter\RegexpFilter;
/**
* @ApiResource(attributes={"filters"={RegexpFilter::class}})
*/
class Offer
{
// ...
}
You can now enable this filter using URLs like http://example.com/offers?regexp_email=^[FOO]
. This new filter will also
appear in Swagger and Hydra documentations.
You can change the way the filter parameters are extracted from the request. This can be done by overriding the extractProperties(\Symfony\Component\HttpFoundation\Request $request)
method.
In the following example, we will completely change the syntax of the order filter to be the following: ?filter[order][property]
<?php
// src/AppBundle/Filter/CustomOrderFilter.php
namespace AppBundle\Filter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
use Symfony\Component\HttpFoundation\Request;
final class CustomOrderFilter extends OrderFilter
{
protected function extractProperties(Request $request): array
{
return $request->query->get('filter[order]', []);
}
}
Finally, register the custom filter:
# app/config/api_filters.yml
services:
'AppBundle\Filter\CustomOrderFilter':
tags: [ 'api_platform.filter' ]
Made with love by
Les-Tilleuls.coop can help you design and develop your APIs and web projects, and train your teams in API Platform, Symfony, Next.js, Kubernetes and a wide range of other technologies.
Learn more