api-platform / api-platform/core
Improve the user's control over the execution order of Filters
- 主要語言
- PHP
- 星號
- 2.6k
- 分支
- 980
- 平均合併
- 2 天 5 小時
- 30 天內合併 PR
- 48
描述
**Description**
A feature of Orm Filters (seemingly undocumented, but that's a different issue) is that the order in which they are declared (as attributes of an entity) is the order in which they are then executed.
However, OrderFilter are executed last regardless, as can be seen in FilterExtension :
```php
$orderFilters = [];
foreach ($resourceFilters 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);
}
}
foreach ($orderFilters as $orderFilter) {
$context['filters'] ??= [];
$orderFilter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context);
}
```
This means that if I want to run a custom filter AFTER every OrderFilter, I simply cannot.
This is an issue because if other filters apply `$queryBuilder->orderBy(...)` or `$queryBuilder->addOrderBy(...)`, there is no way to organize the code so that the `ORDER BY` from OrderFilter has a higher-priority (it is applied last, and uses `$queryBuilder->addOrderBy(...)` so it preserves previous order).
**Example**
Let's say I want to be able to order books by title or by author name (using ApiPlatform's OrderFilter, which is adequate for the task. But I also have a custom filter to run full-text searches in my book's content.
Let's say that I want :
- that if I run a search and I don't specify any order on 'title' and 'author.name', then my search results are ordered by relevance (specified in the custom MyCustomFullTextSearchFilter class, using `$queryBuilder->addOrderBy(...)`).
- that if I run a search but I also specify an order (like `books?order[title]=asc&content=foo`), then the results are ordered by title first (in my use case, I don't care much whether the results are ordered by title only, or by title first and relevance second, but I suppose it would be best to provide options for both scenarios).
```php
#[ApiResource(/* ... */)]
#[ApiFilter(filterClass: OrderFilter::class, properties: ['title', 'author.name'])]
#[ApiFilter(filterClass: MyCustomFullTextSearchFilter::class, properties: ['content'])]
class Book {
/* ... */
}
```
Here it wouldn't matter the order in which I place my filters or the way I implement MyCustomFullTextSearchFilter, I won't achieve my two objectives (unless I apply custom logic inside the filter to "guess" whether OrderFilter is going to be applied and use it to conditionally order by relevance, or something like this).
**Partial solution / Discussion**
In FilterExtension, it might be helpful to replace
```php
if ($filter instanceof OrderFilter) {
$orderFilters[] = $filter;
continue;
}
```
with
```php
if ($filter instanceof OrderFilterInterface) {
$orderFilters[] = $filter;
continue;
}
```
OrderFilter already implements OrderFilterInterface, and it would allow the user to extend OrderFilterInterace as well as a way to make a custom filter run after OrderFilters. However it would require educating the user about what can and cannot be done in a filter that implements OrderFilterInterface, as not to fall back into the scenario from the comment ("Apply the OrderFilter after every other filter to avoid an edge case where OrderFilter would do a LEFT JOIN instead of an INNER JOIN").
貢獻指南
評估
這個 Issue 還沒有評估資料。