feat(search) : add MultiSearchFilter for OR search on name + reference
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ use ApiPlatform\Metadata\Patch;
|
|||||||
use ApiPlatform\Metadata\Post;
|
use ApiPlatform\Metadata\Post;
|
||||||
use ApiPlatform\Metadata\Put;
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Entity\Trait\CuidEntityTrait;
|
use App\Entity\Trait\CuidEntityTrait;
|
||||||
|
use App\Filter\MultiSearchFilter;
|
||||||
use App\Repository\ComposantRepository;
|
use App\Repository\ComposantRepository;
|
||||||
use App\State\ComposantProcessor;
|
use App\State\ComposantProcessor;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
@@ -28,6 +29,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ORM\Table(name: 'composants')]
|
#[ORM\Table(name: 'composants')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial', 'reference' => 'ipartial', 'typeComposant' => 'exact', 'typeComposant.name' => 'ipartial'])]
|
#[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial', 'reference' => 'ipartial', 'typeComposant' => 'exact', 'typeComposant.name' => 'ipartial'])]
|
||||||
|
#[ApiFilter(MultiSearchFilter::class, properties: ['name', 'reference'])]
|
||||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt'])]
|
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt'])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
description: 'Composants du catalogue. Un composant représente un élément fonctionnel rattaché à une machine, avec un type, des fournisseurs et des documents.',
|
description: 'Composants du catalogue. Un composant représente un élément fonctionnel rattaché à une machine, avec un type, des fournisseurs et des documents.',
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use ApiPlatform\Metadata\Patch;
|
|||||||
use ApiPlatform\Metadata\Post;
|
use ApiPlatform\Metadata\Post;
|
||||||
use ApiPlatform\Metadata\Put;
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Entity\Trait\CuidEntityTrait;
|
use App\Entity\Trait\CuidEntityTrait;
|
||||||
|
use App\Filter\MultiSearchFilter;
|
||||||
use App\Repository\PieceRepository;
|
use App\Repository\PieceRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
@@ -29,6 +30,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ORM\Table(name: 'pieces')]
|
#[ORM\Table(name: 'pieces')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial', 'reference' => 'ipartial', 'typePiece' => 'exact', 'typePiece.name' => 'ipartial'])]
|
#[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial', 'reference' => 'ipartial', 'typePiece' => 'exact', 'typePiece.name' => 'ipartial'])]
|
||||||
|
#[ApiFilter(MultiSearchFilter::class, properties: ['name', 'reference'])]
|
||||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt'])]
|
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt'])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
description: 'Pièces détachées du catalogue. Une pièce peut être rattachée à plusieurs machines et possède un type, des fournisseurs, des documents et un produit associé.',
|
description: 'Pièces détachées du catalogue. Une pièce peut être rattachée à plusieurs machines et possède un type, des fournisseurs, des documents et un produit associé.',
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use ApiPlatform\Metadata\Patch;
|
|||||||
use ApiPlatform\Metadata\Post;
|
use ApiPlatform\Metadata\Post;
|
||||||
use ApiPlatform\Metadata\Put;
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Entity\Trait\CuidEntityTrait;
|
use App\Entity\Trait\CuidEntityTrait;
|
||||||
|
use App\Filter\MultiSearchFilter;
|
||||||
use App\Repository\ProductRepository;
|
use App\Repository\ProductRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
@@ -27,6 +28,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ORM\Table(name: 'products')]
|
#[ORM\Table(name: 'products')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial', 'reference' => 'ipartial', 'typeProduct' => 'exact', 'typeProduct.name' => 'ipartial'])]
|
#[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial', 'reference' => 'ipartial', 'typeProduct' => 'exact', 'typeProduct.name' => 'ipartial'])]
|
||||||
|
#[ApiFilter(MultiSearchFilter::class, properties: ['name', 'reference'])]
|
||||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt', 'supplierPrice'])]
|
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt', 'supplierPrice'])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
description: 'Produits du catalogue fournisseur. Un produit possède une référence, un prix indicatif, un type, des fournisseurs et des documents. Il peut être lié à des machines.',
|
description: 'Produits du catalogue fournisseur. Un produit possède une référence, un prix indicatif, un type, des fournisseurs et des documents. Il peut être lié à des machines.',
|
||||||
|
|||||||
51
src/Filter/MultiSearchFilter.php
Normal file
51
src/Filter/MultiSearchFilter.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Filter;
|
||||||
|
|
||||||
|
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
|
||||||
|
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||||
|
use ApiPlatform\Metadata\Operation;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
|
final class MultiSearchFilter extends AbstractFilter
|
||||||
|
{
|
||||||
|
public function getDescription(string $resourceClass): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'search' => [
|
||||||
|
'property' => null,
|
||||||
|
'type' => 'string',
|
||||||
|
'required' => false,
|
||||||
|
'description' => 'Search across: '.implode(', ', array_keys($this->properties ?? [])),
|
||||||
|
'openapi' => [
|
||||||
|
'allowEmptyValue' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function filterProperty(string $property, mixed $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
|
||||||
|
{
|
||||||
|
if ('search' !== $property || !$value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fields = $this->properties ?? [];
|
||||||
|
if (empty($fields)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$alias = $queryBuilder->getRootAliases()[0];
|
||||||
|
$orConditions = [];
|
||||||
|
|
||||||
|
foreach (array_keys($fields) as $field) {
|
||||||
|
$paramName = $queryNameGenerator->generateParameterName($field);
|
||||||
|
$orConditions[] = sprintf('LOWER(%s.%s) LIKE LOWER(:%s)', $alias, $field, $paramName);
|
||||||
|
$queryBuilder->setParameter($paramName, '%'.$value.'%');
|
||||||
|
}
|
||||||
|
|
||||||
|
$queryBuilder->andWhere(implode(' OR ', $orConditions));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user