Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/Doctrine/Odm/Filter/ChainFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Doctrine\Odm\Filter;

use ApiPlatform\Doctrine\Common\Filter\LoggerAwareInterface;
use ApiPlatform\Doctrine\Common\Filter\LoggerAwareTrait;
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface;
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareTrait;
use ApiPlatform\Metadata\BackwardCompatibleFilterDescriptionTrait;
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Parameter;
use Doctrine\ODM\MongoDB\Aggregation\Builder;

/**
* Applies several filters to a single parameter; each filter self-selects by the value shape it supports.
*/
final class ChainFilter implements FilterInterface, OpenApiParameterFilterInterface, ManagerRegistryAwareInterface, LoggerAwareInterface
{
use BackwardCompatibleFilterDescriptionTrait;
use LoggerAwareTrait;
use ManagerRegistryAwareTrait;

/**
* @param iterable<FilterInterface> $filters
*/
public function __construct(private readonly iterable $filters)
{
}

/**
* @param-out array<string, mixed> $context
*/
public function apply(Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void
{
foreach ($this->filters as $filter) {
if ($filter instanceof ManagerRegistryAwareInterface && !$filter->hasManagerRegistry() && $this->hasManagerRegistry()) {
$filter->setManagerRegistry($this->getManagerRegistry());
}

if ($filter instanceof LoggerAwareInterface && !$filter->hasLogger() && $this->hasLogger()) {
$filter->setLogger($this->getLogger());
}

$filter->apply($aggregationBuilder, $resourceClass, $operation, $context);
}
}

public function getOpenApiParameters(Parameter $parameter): array
{
$parameters = [];
foreach ($this->filters as $filter) {
if ($filter instanceof OpenApiParameterFilterInterface) {
$parameters = [...$parameters, ...(array) $filter->getOpenApiParameters($parameter)];
}
}

return $parameters;
}
}
7 changes: 6 additions & 1 deletion src/Doctrine/Odm/Filter/ExactFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,18 @@ final class ExactFilter implements FilterInterface, OpenApiParameterFilterInterf
public function apply(Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void
{
$parameter = $context['parameter'];
$value = $parameter->getValue();

if (\is_array($value) && !array_is_list($value)) {
// associative arrays are operator-maps owned by ComparisonFilter/DateFilter, not equality
return;
}

if (null === $parameter->getProperty()) {
throw new InvalidArgumentException(\sprintf('The filter parameter with key "%s" must specify a property. Please provide the property explicitly.', $parameter->getKey()));
}

$property = $parameter->getProperty();
$value = $parameter->getValue();
$operator = $context['operator'] ?? 'addAnd';
$match = $context['match'] = $context['match'] ??
$aggregationBuilder
Expand Down
6 changes: 6 additions & 0 deletions src/Doctrine/Odm/Filter/PartialSearchFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ public function apply(Builder $aggregationBuilder, string $resourceClass, ?Opera

$property = $parameter->getProperty();
$values = $parameter->getValue();

if (\is_array($values) && !array_is_list($values)) {
// associative arrays are operator-maps owned by ComparisonFilter/DateFilter, not equality
return;
}

$match = $context['match'] = $context['match'] ??
$aggregationBuilder
->matchExpr();
Expand Down
69 changes: 69 additions & 0 deletions src/Doctrine/Orm/Filter/ChainFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Doctrine\Orm\Filter;

use ApiPlatform\Doctrine\Common\Filter\LoggerAwareInterface;
use ApiPlatform\Doctrine\Common\Filter\LoggerAwareTrait;
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface;
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareTrait;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\BackwardCompatibleFilterDescriptionTrait;
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Parameter;
use Doctrine\ORM\QueryBuilder;

/**
* Applies several filters to a single parameter; each filter self-selects by the value shape it supports.
*/
final class ChainFilter implements FilterInterface, OpenApiParameterFilterInterface, ManagerRegistryAwareInterface, LoggerAwareInterface
{
use BackwardCompatibleFilterDescriptionTrait;
use LoggerAwareTrait;
use ManagerRegistryAwareTrait;

/**
* @param iterable<FilterInterface> $filters
*/
public function __construct(private readonly iterable $filters)
{
}

public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
{
foreach ($this->filters as $filter) {
if ($filter instanceof ManagerRegistryAwareInterface && !$filter->hasManagerRegistry() && $this->hasManagerRegistry()) {
$filter->setManagerRegistry($this->getManagerRegistry());
}

if ($filter instanceof LoggerAwareInterface && !$filter->hasLogger() && $this->hasLogger()) {
$filter->setLogger($this->getLogger());
}

$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context);
}
}

public function getOpenApiParameters(Parameter $parameter): array
{
$parameters = [];
foreach ($this->filters as $filter) {
if ($filter instanceof OpenApiParameterFilterInterface) {
$parameters = [...$parameters, ...(array) $filter->getOpenApiParameters($parameter)];
}
}

return $parameters;
}
}
5 changes: 5 additions & 0 deletions src/Doctrine/Orm/Filter/ExactFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q
$parameter = $context['parameter'];
$value = $parameter->getValue();

if (\is_array($value) && !array_is_list($value)) {
// associative arrays are operator-maps owned by ComparisonFilter/DateFilter, not equality
return;
}

if (null === $parameter->getProperty()) {
throw new InvalidArgumentException(\sprintf('The filter parameter with key "%s" must specify a property. Please provide the property explicitly.', $parameter->getKey()));
}
Expand Down
5 changes: 5 additions & 0 deletions src/Doctrine/Orm/Filter/PartialSearchFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q
$field = $alias.'.'.$property;
$values = $parameter->getValue();

if (\is_array($values) && !array_is_list($values)) {
// associative arrays are operator-maps owned by ComparisonFilter/DateFilter, not equality
return;
}

if (!is_iterable($values)) {
$parameterName = $queryNameGenerator->generateParameterName($property);
$queryBuilder->setParameter($parameterName, $this->formatLikeValue($values));
Expand Down
52 changes: 52 additions & 0 deletions tests/Fixtures/TestBundle/Document/ChainFilterParameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;

use ApiPlatform\Doctrine\Odm\Filter\ChainFilter;
use ApiPlatform\Doctrine\Odm\Filter\ComparisonFilter;
use ApiPlatform\Doctrine\Odm\Filter\ExactFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\QueryParameter;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Symfony\Component\TypeInfo\Type\BuiltinType;
use Symfony\Component\TypeInfo\TypeIdentifier;

#[ApiResource]
#[GetCollection(
paginationEnabled: false,
parameters: [
'quantity' => new QueryParameter(
filter: new ChainFilter([
new ExactFilter(),
new ComparisonFilter(new ExactFilter()),
]),
property: 'quantity',
nativeType: new BuiltinType(TypeIdentifier::INT),
castToNativeType: true,
),
],
)]
#[ODM\Document]
class ChainFilterParameter
{
public function __construct(
#[ODM\Id(type: 'int', strategy: 'INCREMENT')]
public ?int $id = null,

#[ODM\Field(type: 'int')]
public ?int $quantity = null,
) {
}
}
50 changes: 50 additions & 0 deletions tests/Fixtures/TestBundle/Entity/ChainFilterParameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;

use ApiPlatform\Doctrine\Orm\Filter\ChainFilter;
use ApiPlatform\Doctrine\Orm\Filter\ComparisonFilter;
use ApiPlatform\Doctrine\Orm\Filter\ExactFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\QueryParameter;
use Doctrine\ORM\Mapping as ORM;

#[ApiResource]
#[GetCollection(
paginationEnabled: false,
parameters: [
'quantity' => new QueryParameter(
filter: new ChainFilter([
new ExactFilter(),
new ComparisonFilter(new ExactFilter()),
]),
property: 'quantity',
),
],
)]
#[ORM\Entity]
class ChainFilterParameter
{
public function __construct(
#[ORM\Column]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'AUTO')]
public ?int $id = null,

#[ORM\Column]
public ?int $quantity = null,
) {
}
}
Loading
Loading