Skip to content

Commit

Permalink
go further on parameter attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Feb 12, 2024
1 parent e53934d commit 33d45b6
Showing 1 changed file with 62 additions and 13 deletions.
75 changes: 62 additions & 13 deletions docs/adr/0006-filters.md
Expand Up @@ -99,33 +99,40 @@ 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>|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.
On top of that we will provide an additional `_api_header_parameters` as we would like to introduce a `QueryParameter` and an `HeaderParameter`.

### Parameter Provider

Expand Down Expand Up @@ -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

Expand Down

0 comments on commit 33d45b6

Please sign in to comment.