diff --git a/docs/adr/0006-filters.md b/docs/adr/0006-filters.md index cacc67682d..f3699c024b 100644 --- a/docs/adr/0006-filters.md +++ b/docs/adr/0006-filters.md @@ -99,30 +99,37 @@ We need a way to instruct the program to parse query parameters and produce a li Let's define a new Attribute `Parameter` that holds informations (filters, context, schema) tight to a parameter `key`. ```php +namespace ApiPlatform\Metadata; + +use ApiPlatform\OpenApi; + final class Parameter { public string $key; public \ArrayObject schema; public array $context; public OpenApi\Parameter $openApi; public string|callable provider(): Operation; - public string|callable filter(); - - /** - * The filters should be called within the API Platform state providers as they alter the Doctrine/Elasticsearch Query, - * therefore we will need one interface per persistence layer supported. - * As usual this is either a callable either a symfony service. - * - * @param iterable|mixed $value - */ - public function filter(Parameter $parameter, $value, array $context); + // filter service id + public string $filter; } ``` +By default applied to a class, the `Parameter` would apply on every operations, or it could be specified on a single operation: + +```php +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Doctrine\Common\AndParameter; + +#[GetCollection(parameters: ['and' => new AndParameter])] +#[AndParameter('and')] +class Book {} +``` + API Platform will continue to provide parsed query parameters and set an `_api_query_parameters` Request attribute, in the end the filter may or may not use it: ```php $queryString = RequestParser::getQueryString($request); -$request->attributes->set($queryString ? RequestParser::parseRequestParams($queryString) : []); +$request->attributes->set('_api_query_parameters', $queryString ? RequestParser::parseRequestParams($queryString) : []); ``` On top of that we will provide an additional `_api_header_parameters`. Should be filled only the specified parameters on an operation. @@ -190,11 +197,53 @@ class UuidParameter implements ProviderInterface { } ``` -3. Validate parameters through the QueryParameterValidator. +3. Validate parameters through the ParameterValidator. ### Filters Filters should remain mostly unchanged, the current informations about the `property` to filter should be specified inside a `Parameter`'s `context`. +They alter the Doctrine/Elasticsearch Query, therefore we need one interface per persistence layer supported. The current logic within API Platform is: + +```php +// src/Doctrine/Orm/Extension/FilterExtension.php +foreach ($operation->getFilters() ?? [] as $filterId) { + $filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null; + if ($filter instanceof FilterInterface) { + // Apply the OrderFilter after every other filter to avoid an edge case where OrderFilter would do a LEFT JOIN instead of an INNER JOIN + if ($filter instanceof OrderFilter) { + $orderFilters[] = $filter; + continue; + } + + $context['filters'] ??= []; + $filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); + } +} +``` + +As we want a parameter to have some filters, we'd add the same logic based on the parameters `filter` information, for example: + +```php +// src/Doctrine/Orm/Extension/ParameterExtension.php +$values = $request->attributes->get('_api_query_parameters'); +foreach ($operation->getParameters() as $key => $parameter) { + if (!array_key_exists($key, $values) || !($filterId = $parameter->getFilter())) { + continue; + } + + $filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null; + + if ($filter instanceof FilterInterface) { + $context['parameter'] = $parameter; + $context['value'] = $values[$key]; + $filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); + } +} +``` + +- A `Parameter` doesn't necessary have a filter. +- Any logic regarding order of filters needs to be handled by the callee (just as above). +- For filter composition we may introduce an `OrFilter` or `AndFilter` on an `or` or `and` parameter that would be exposed for users to use. ## Links