Hello API Platform team,
API Platform version(s) affected: 4.3+
Description
Before upgrade I was using SearchFilter in combinaision with DateFilter for a same property. It was working despite different filters use the same property because generated filters are using a different QueryParemeter in the final URL.
Exemple how it worked before migration :
Exact filter expect a list array and date filter expect an associative array with specific keys.
Exemple what is expected after migration :
Without the FilterApi, ExactFilter always triggers when an associative array is passed as value, but it should trigger only for list array values (like the old SearchFilter behaviour).
How to reproduce
First create a QueryParameter metadata to allow combining the filters on same property :
<?php
namespace App\Metadata;
use ApiPlatform\Metadata\QueryParameter;
/**
* API Platform deduplicates QueryParameter metadata by key + class pair
*/
final class MergedDateFilterQueryParameter extends QueryParameter
{
}
Then declare filters like this example on the Doctrine entity you want :
parameters: [
'birthdateExactFilter' => new QueryParameter(key: 'birthdate', filter: new ExactFilter(), property: 'birthdate', description: ''),
'birthdateDateFilter' => new MergedDateFilterQueryParameter(key: 'birthdate', filter: new DateFilter(properties: ['birthdate' => null]), property: 'birthdate', description: '')
]
You will obtain the correct swagger doc, but date filter will not work as ExactFilter will always apply :
Exact filter in the doc :
Date filter in the doc :

As they generates different http keys, they could be combined like the old behaviour of the SearchFilter that allows that.
Possible Solution
In ExactFilter code :
<?php
/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace ApiPlatform\Doctrine\Orm\Filter;
use ApiPlatform\Doctrine\Common\Filter\OpenApiFilterTrait;
use ApiPlatform\Doctrine\Orm\NestedPropertyHelperTrait;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\BackwardCompatibleFilterDescriptionTrait;
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\QueryBuilder;
/**
* @author Vincent Amstoutz <vincent.amstoutz.dev@gmail.com>
*/
final class ExactFilter implements FilterInterface, OpenApiParameterFilterInterface
{
use BackwardCompatibleFilterDescriptionTrait;
use NestedPropertyHelperTrait;
use OpenApiFilterTrait;
public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
{
$parameter = $context['parameter'];
$value = $parameter->getValue();
// ---> This can fix the ExactFilter
if (is_array($value) && !array_is_list($value)) { // Exclude associative arrays as they are not part of the ExactFilter contract (only list array are)
return;
}
if (null === $parameter->getProperty()) {
throw new InvalidArgumentException(\sprintf('The filter parameter with key "%s" must specify a property. Please provide the property explicitly.', $parameter->getKey()));
}
$property = $parameter->getProperty();
$alias = $queryBuilder->getRootAliases()[0];
$parameterName = $queryNameGenerator->generateParameterName($property);
[$alias, $property] = $this->addNestedParameterJoins($property, $alias, $queryBuilder, $queryNameGenerator, $parameter);
if (\is_array($value)) {
$queryBuilder
->{$context['whereClause'] ?? 'andWhere'}(\sprintf('%s.%s IN (:%s)', $alias, $property, $parameterName));
} else {
$operator = $context['operator'] ?? '=';
if (!\in_array($operator, ComparisonFilter::ALLOWED_DQL_OPERATORS, true)) {
throw new InvalidArgumentException(\sprintf('Unsupported operator "%s".', $operator));
}
$queryBuilder
->{$context['whereClause'] ?? 'andWhere'}(\sprintf('%s.%s %s :%s', $alias, $property, $operator, $parameterName));
}
$queryBuilder->setParameter($parameterName, $value);
}
}
Additional Context
Their is a workaround if I create a custom filter like this but it's not elegant :
<?php
// src/Filter/ScalarExactFilter.php
namespace App\Filter;
use ApiPlatform\Doctrine\Orm\Filter\ExactFilter;
use ApiPlatform\Doctrine\Orm\Filter\FilterInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Parameter;
use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter;
use Doctrine\ORM\QueryBuilder;
final class ExactExcludingAssociativeArrayFilter implements FilterInterface, OpenApiParameterFilterInterface
{
private ExactFilter $exactFilter;
public function __construct()
{
$this->exactFilter = new ExactFilter();
}
public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
{
$value = ($context['parameter'] ?? null)?->getValue();
// This filter add the ExactFilter missing guard when it's combined with DateFilter
if (is_array($value) && !array_is_list($value)) {
return;
}
$this->exactFilter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context);
}
public function getOpenApiParameters(Parameter $parameter): OpenApiParameter
{
return $this->exactFilter->getOpenApiParameters($parameter);
}
/**
* Backward compatibility fonction is required before API Platform v5.0
* Don't use this.
*/
public function getDescription(string $resourceClass): array
{
return [];
}
}
Hello API Platform team,
API Platform version(s) affected: 4.3+
Description
Before upgrade I was using SearchFilter in combinaision with DateFilter for a same property. It was working despite different filters use the same property because generated filters are using a different QueryParemeter in the final URL.
Exemple how it worked before migration :
Exact filter expect a list array and date filter expect an associative array with specific keys.
Exemple what is expected after migration :
Without the FilterApi, ExactFilter always triggers when an associative array is passed as value, but it should trigger only for list array values (like the old SearchFilter behaviour).
How to reproduce
First create a QueryParameter metadata to allow combining the filters on same property :
Then declare filters like this example on the Doctrine entity you want :
You will obtain the correct swagger doc, but date filter will not work as ExactFilter will always apply :
Exact filter in the doc :
Date filter in the doc :

As they generates different http keys, they could be combined like the old behaviour of the SearchFilter that allows that.
Possible Solution
In ExactFilter code :
Additional Context
Their is a workaround if I create a custom filter like this but it's not elegant :