Skip to content

Instantly share code, notes, and snippets.

@alexislefebvre
Forked from masseelch/FullTextSearchFilter.php
Last active July 29, 2023 09:52
Show Gist options
  • Select an option

  • Save alexislefebvre/fcbbb9104c787b9ccb739ce3bb5cfe06 to your computer and use it in GitHub Desktop.

Select an option

Save alexislefebvre/fcbbb9104c787b9ccb739ce3bb5cfe06 to your computer and use it in GitHub Desktop.

Revisions

  1. alexislefebvre revised this gist Aug 11, 2020. 1 changed file with 3 additions and 2 deletions.
    5 changes: 3 additions & 2 deletions FullTextSearchFilter.php
    Original file line number Diff line number Diff line change
    @@ -22,12 +22,13 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
    return;
    }

    $orExpressions = [];

    // Split the $value at spaces.
    // For each term 'or' all given properties by strategy.
    // 'And' all 'or'-parts.
    $orExpressions = [];

    $terms = explode(" ", $value);

    foreach ($terms as $index => $term) {
    foreach ($this->properties as $property => $strategy) {
    $strategy = $strategy ?? self::STRATEGY_EXACT;
  2. @masseelch masseelch revised this gist Jul 11, 2020. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions FullTextSearchFilter.php
    Original file line number Diff line number Diff line change
    @@ -86,15 +86,15 @@ protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuild
    case self::STRATEGY_EXACT:
    return $exprBuilder->eq($wrapCase("$alias.$field"), $wrapCase(":$valueParameter"));
    case self::STRATEGY_PARTIAL:
    return $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat('%', $wrapCase(":$valueParameter"), '%'));
    return $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat("'%'", $wrapCase(":$valueParameter"), "'%'"));
    case self::STRATEGY_START:
    return $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat($wrapCase(":$valueParameter"), '%'));
    return $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat($wrapCase(":$valueParameter"), "'%'"));
    case self::STRATEGY_END:
    return $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat('%', $wrapCase(":$valueParameter")));
    return $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat("'%'", $wrapCase(":$valueParameter")));
    case self::STRATEGY_WORD_START:
    return $exprBuilder->orX(
    $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat($wrapCase(":$valueParameter"), '%')),
    $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat('%', $wrapCase(":$valueParameter")))
    $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat($wrapCase(":$valueParameter"), "'%'")),
    $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat("'%'", $wrapCase(":$valueParameter")))
    );
    default:
    throw new InvalidArgumentException(sprintf('strategy %s does not exist.', $strategy));
  3. @masseelch masseelch revised this gist Jul 11, 2020. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions example.php
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,7 @@
    // Use it like this.
    /api?search=this_is_my_search_string%20this_is_the_second_term_of_my_search_string

    <?php

    /**
    * @ApiResource()
  4. @masseelch masseelch revised this gist Jul 11, 2020. 2 changed files with 2 additions and 0 deletions.
    File renamed without changes.
    2 changes: 2 additions & 0 deletions Use like this → example.php
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    // Use it like this.

    /**
    * @ApiResource()
    * @ApiFilter(FullTextSearchFilter::class, properties={
  5. @masseelch masseelch created this gist Jul 11, 2020.
    103 changes: 103 additions & 0 deletions FullTextSearchFilter
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,103 @@
    <?php


    namespace App\Filter;

    use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
    use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
    use ApiPlatform\Core\Exception\InvalidArgumentException;
    use Doctrine\ORM\QueryBuilder;

    class FullTextSearchFilter extends SearchFilter
    {
    private const PROPERTY_NAME = 'search';

    /**
    * {@inheritdoc}
    */
    protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
    {
    // This filter will work with the 'search'-query-parameter only.
    if ($property !== self::PROPERTY_NAME) {
    return;
    }

    // Split the $value at spaces.
    // For each term 'or' all given properties by strategy.
    // 'And' all 'or'-parts.
    $orExpressions = [];

    $terms = explode(" ", $value);
    foreach ($terms as $index => $term) {
    foreach ($this->properties as $property => $strategy) {
    $strategy = $strategy ?? self::STRATEGY_EXACT;
    $alias = $queryBuilder->getRootAliases()[0];
    $field = $property;

    $associations = [];
    if ($this->isPropertyNested($property, $resourceClass)) {
    [$alias, $field, $associations] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass);
    }

    $caseSensitive = true;
    $metadata = $this->getNestedMetadata($resourceClass, $associations);

    if ($metadata->hasField($field)) {
    if ('id' === $field) {
    $term = $this->getIdFromValue($term);
    }

    if (!$this->hasValidValues((array)$term, $this->getDoctrineFieldType($property, $resourceClass))) {
    $this->logger->notice('Invalid filter ignored', [
    'exception' => new InvalidArgumentException(sprintf('Values for field "%s" are not valid according to the doctrine type.', $field)),
    ]);
    continue;
    }

    // prefixing the strategy with i makes it case insensitive
    if (0 === strpos($strategy, 'i')) {
    $strategy = substr($strategy, 1);
    $caseSensitive = false;
    }

    $orExpressions[$index][] = $this->addWhereByStrategy($strategy, $queryBuilder, $queryNameGenerator, $alias, $field, $term, $caseSensitive);
    }
    }
    }

    $exprBuilder = $queryBuilder->expr();
    foreach ($orExpressions as $expr) {
    $queryBuilder->andWhere($exprBuilder->orX(...$expr));
    }
    }

    /**
    * {@inheritDoc}
    */
    protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, $value, bool $caseSensitive)
    {
    $wrapCase = $this->createWrapCase($caseSensitive);
    $valueParameter = $queryNameGenerator->generateParameterName($field);
    $exprBuilder = $queryBuilder->expr();

    $queryBuilder->setParameter($valueParameter, $value);
    switch ($strategy) {
    case null:
    case self::STRATEGY_EXACT:
    return $exprBuilder->eq($wrapCase("$alias.$field"), $wrapCase(":$valueParameter"));
    case self::STRATEGY_PARTIAL:
    return $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat('%', $wrapCase(":$valueParameter"), '%'));
    case self::STRATEGY_START:
    return $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat($wrapCase(":$valueParameter"), '%'));
    case self::STRATEGY_END:
    return $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat('%', $wrapCase(":$valueParameter")));
    case self::STRATEGY_WORD_START:
    return $exprBuilder->orX(
    $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat($wrapCase(":$valueParameter"), '%')),
    $exprBuilder->like($wrapCase("$alias.$field"), $exprBuilder->concat('%', $wrapCase(":$valueParameter")))
    );
    default:
    throw new InvalidArgumentException(sprintf('strategy %s does not exist.', $strategy));
    }
    }
    }
    54 changes: 54 additions & 0 deletions Use like this
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,54 @@
    /**
    * @ApiResource()
    * @ApiFilter(FullTextSearchFilter::class, properties={
    * "id": "exact",
    * "task": "partial",
    * "client.name": "start"
    * })
    * @ORM\Entity(repositoryClass=EntityRepository::class)
    */
    class Job
    {
    /**
    * @ORM\Id()
    * @ORM\GeneratedValue()
    * @ORM\Column(type="integer")
    */
    private $id;

    /**
    * @ORM\ManyToOne(targetEntity=Client::class, inversedBy="entites")
    * @ORM\JoinColumn(nullable=false)
    */
    private $client;


    public function getId(): ?int
    {
    return $this->id;
    }

    public function getTask(): ?string
    {
    return $this->task;
    }

    public function setTask(string $task): self
    {
    $this->task = $task;

    return $this;
    }

    public function getClient(): ?Client
    {
    return $this->client;
    }

    public function setClient(?Client $client): self
    {
    $this->client = $client;

    return $this;
    }
    }