Skip to content

Instantly share code, notes, and snippets.

@nicolasbonnici
Last active November 2, 2024 19:52
Show Gist options
  • Save nicolasbonnici/3d598c8981ec1ac9769b4b7f5a9031f6 to your computer and use it in GitHub Desktop.
Save nicolasbonnici/3d598c8981ec1ac9769b4b7f5a9031f6 to your computer and use it in GitHub Desktop.
SearchOrFilter for API Platform 4
<?php
namespace App\Filter;
use ApiPlatform\Doctrine\Orm\Filter\FilterInterface;
use Doctrine\ORM\Query\Expr\Comparison;
use Doctrine\ORM\QueryBuilder;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
class SearchOrFilter implements FilterInterface
{
private ?CamelCaseToSnakeCaseNameConverter $nameConverter = null;
public function __construct(private readonly RequestStack $requestStack, private readonly array $properties = [])
{
}
private function getNameConverter(): CamelCaseToSnakeCaseNameConverter
{
return $this->nameConverter ??= new CamelCaseToSnakeCaseNameConverter();
}
public function apply(
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
$operation = null,
array $context = []
): void
{
$request = $this->requestStack->getCurrentRequest();
if (!$request) {
return;
}
$alias = $queryBuilder->getRootAliases()[0];
$orConditions = [];
foreach ($this->properties as $property => $settings) {
$type = is_array($settings) ? $settings['type'] : $settings;
$value = $request->get($this->getNameConverter()->normalize($property));
if ($value === null) {
continue;
}
$parameterName = $queryNameGenerator->generateParameterName($property);
$orConditions[] = $this->buildCondition(
$queryBuilder,
$alias,
$property,
$parameterName,
$value,
$type
);
}
if (!empty($orConditions)) {
$queryBuilder->andWhere($queryBuilder->expr()->orX(...$orConditions));
}
}
private function buildCondition(
QueryBuilder $queryBuilder,
string $alias,
string $property,
string $parameterName,
string $value,
string $type
): Comparison
{
$isPartial = in_array($type, ['partial', 'ipartial'], true);
$isInsensitive = in_array($type, ['ipartial', 'iexact'], true);
$value = $isPartial ? '%' . $value . '%' : $value;
$queryBuilder->setParameter($parameterName, $value);
$column = $isInsensitive
? sprintf('LOWER(%s.%s)', $alias, $property)
: sprintf('%s.%s', $alias, $property);
$param = $isInsensitive
? sprintf('LOWER(:%s)', $parameterName)
: sprintf(':%s', $parameterName);
return $isPartial
? $queryBuilder->expr()->like($column, $param)
: $queryBuilder->expr()->eq($column, $param);
}
public function getDescription(string $resourceClass): array
{
return array_map(fn($settings, $property) => [
'property' => $property,
'type' => Type::BUILTIN_TYPE_STRING,
'required' => false,
'description' => sprintf('Filter using OR condition on %s', $property),
], $this->properties, array_keys($this->properties));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment