Skip to content

Instantly share code, notes, and snippets.

@renta
Last active August 19, 2021 17:39
Show Gist options
  • Save renta/b6ece3fec7896440fe52a9ec0e76571a to your computer and use it in GitHub Desktop.
Save renta/b6ece3fec7896440fe52a9ec0e76571a to your computer and use it in GitHub Desktop.
Custom OR filter for an Api Platform
<?php
namespace App\Filter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use Doctrine\ORM\QueryBuilder;
final class OrSearchFilter extends AbstractContextAwareFilter
{
private const FILTER_KEY = 'orSearch';
/**
* Passes a property through the filter.
*
* @param string $property
* @param $value
* @param QueryBuilder $queryBuilder
* @param QueryNameGeneratorInterface $queryNameGenerator
* @param string $resourceClass
* @param string|null $operationName
*/
protected function filterProperty(
string $property,
$value,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
string $operationName = null
): void {
if (null === $value || false === strpos($property, self::FILTER_KEY)) {
return;
}
$parameterName = $queryNameGenerator->generateParameterName($property);
$search = [];
$mappedJoins = [];
foreach ($this->properties as $groupName => $fields) {
foreach ($fields as $field) {
$joins = explode('.', $field);
for ($lastAlias = 'o', $i = 0, $num = \count($joins); $i < $num; $i++) {
$currentAlias = $joins[$i];
if ($i === $num - 1) {
$search[] = "LOWER({$lastAlias}.{$currentAlias}) LIKE LOWER(:{$parameterName})";
} else {
$join = "{$lastAlias}.{$currentAlias}";
if (!\in_array($join, $mappedJoins, true)) {
$queryBuilder->leftJoin($join, $currentAlias);
$mappedJoins[] = $join;
}
}
$lastAlias = $currentAlias;
}
}
}
$queryBuilder->andWhere(implode(' OR ', $search));
$queryBuilder->setParameter($parameterName, '%' . $value . '%');
}
/**
* Gets the description of this filter for the given resource.
*
* Returns an array with the filter parameter names as keys and array with the following data as values:
* - property: the property where the filter is applied
* - type: the type of the filter
* - required: if this filter is required
* - strategy: the used strategy
* - swagger (optional): additional parameters for the path operation,
* e.g. 'swagger' => [
* 'description' => 'My Description',
* 'name' => 'My Name',
* 'type' => 'integer',
* ]
* The description can contain additional data specific to a filter.
*
* @see \ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer::getFiltersParameters
*
* @param string $resourceClass
*
* @return array
*/
public function getDescription(string $resourceClass): array
{
$description = [];
foreach ($this->properties as $groupName => $fields) {
$description[self::FILTER_KEY . '_' . $groupName] = [
'property' => self::FILTER_KEY,
'type' => 'string',
'required' => false,
'swagger' => ['description' => 'OrSearchFilter on ' . implode(', ', $fields)],
];
}
return $description;
}
}
services:
#... other services
App\Filter\OrSearchFilter:
<?php
namespace App\Entity;
use App\Filter\OrSearchFilter;
use ApiPlatform\Core\Annotation\ApiFilter;
//...more imports here
/**
* @ApiFilter(
* OrSearchFilter::class, properties={
* "fullname": {"firstName", "lastName"}
* }
* )
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
*/
class User implements UserInterface
{
//...more fields here
/**
* @Assert\Length(min="1", max="100")
* @ORM\Column(type="string", length=100, nullable=true)
*/
private $firstName;
/**
* @Assert\Length(min="1", max="100")
* @ORM\Column(type="string", length=100, nullable=true)
*/
private $lastName;
@kgonella
Copy link

kgonella commented Apr 2, 2019

thanks for your share !

@next-sentence
Copy link

@sneakyx

I wanted to filter by companies zip f.e. all companies with zip=12345 or 67890

you can use default SearchFilter

localhost:8000/api/v1/posts?company.zip[]=12345&company.zip[]=67890

read docs

@masacc
Copy link

masacc commented Aug 31, 2020

Thanks @renta !

In case someone need something a little bit different, I made a mix from this gist and another one (https://gist.github.com/masseelch/47931f3a745409f8f44c69efa9ecb05c) :

https://gist.github.com/masacc/94df641b3cb9814cbdaeb3f158d2e1f7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment