-
-
Save renta/b6ece3fec7896440fe52a9ec0e76571a to your computer and use it in GitHub Desktop.
| <?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; |
I've found the answer about how to use this with YAML files and I'm leaving it here to other users.
- You need to register the "OrSearchFilter" class as a Service.
services:
'App\Filter\OrSearchFilter':
tags: [ 'api_platform.filter' ]
If you use the @APIFilter() annotation, you're done, but if you're using YAML files, you need to register a new Service for your Filter definition:
services:
my_awesome_filters.or_filter:
parent: 'App\Filter\OrSearchFilter'
arguments: [ '@doctrine', '@request_stack', '@?logger', { propertyThatWillBeUsedAsParameter: [ 'propertyOne', 'anotherAwesomeProperty' ] } ]
tags: [ 'api_platform.filter' ]
And use the ID of the service "my_awesome_filters.or_filter" as the example in your "filters" definition inside ApiPlatform resources.
resources:
App\Entity\Product:
shortName: 'product'
attributes:
filters:
- "my_awesome_filters.or_filter"
Hi,
thanks for your work, but can't get it running.
The array $fields on Line 41 is always null.
I wanted to filter by companies zip f.e. all companies with zip=12345 or 67890
My config:
services.yml
my.filter.or_filter: class: AppBundle\Filters\OrFilter tags: [ 'api_platform.filter' ]
my entity
<?php
namespace AppBundle\Entity\Blog;
use ApiPlatform\Core\Annotation\ApiFilter;
use AppBundle\Entity\Registration\User;
...
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\HasLifecycleCallbacks;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\NumericFilter;
use AppBundle\Filters\OrFilter;
/**
* Post
*
* @ApiResource(attributes={
* "normalization_context"={"groups"={"api_read", "api_admin_read"}},
* "filters"={"post.search_filter"}
* })
* @ApiFilter(OrFilter::class, properties={"company.zip"})
* @ORM\Table(name="post")
* @ORM\Entity(repositoryClass="AppBundle\Repository\Blog\PostRepository")
* @ORM\HasLifecycleCallbacks
*
*/
my request:
localhost:8000/api/v1/posts?orSearch_company.zip=12345.67890
the dump shows this:
array:1 [
"company.zip" => null
]
What am I doing wrong?
Thanks in advance!
sneaky
thanks for your share !
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
Thank you for all this @renta ! I've a issue trying to make it work with YML configuration files.
Can't figure out how to specify the "properties" option in YML, any ideas?