Skip to content
Merged
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
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();

// associative arrays are operator-maps owned by ComparisonFilter/DateFilter, not equality
if (\is_array($value) && !array_is_list($value)) {
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();

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

if (!is_iterable($values)) {
$parameterName = $queryNameGenerator->generateParameterName($property);
$queryBuilder->setParameter($parameterName, $this->formatLikeValue($values));
Expand Down
57 changes: 57 additions & 0 deletions tests/Fixtures/TestBundle/Entity/ExactAndComparisonParameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?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\ComparisonFilter;
use ApiPlatform\Doctrine\Orm\Filter\ExactFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\QueryParameter;
use ApiPlatform\Tests\Fixtures\TestBundle\Parameter\OperatorMapQueryParameter;
use Doctrine\ORM\Mapping as ORM;

#[ApiResource(openapi: false)]
#[GetCollection(
uriTemplate: 'exact_and_comparison_parameter{._format}',
parameters: [
'quantity' => new QueryParameter(filter: new ExactFilter(), property: 'quantity'),
'quantityComparison' => new OperatorMapQueryParameter(key: 'quantity', filter: new ComparisonFilter(new ExactFilter()), property: 'quantity'),
]
)]
#[ORM\Entity]
class ExactAndComparisonParameter
{
#[ORM\Column(type: 'integer')]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private ?int $id = null;

#[ORM\Column(type: 'integer')]
private int $quantity = 0;

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

public function getQuantity(): int
{
return $this->quantity;
}

public function setQuantity(int $quantity): void
{
$this->quantity = $quantity;
}
}
24 changes: 24 additions & 0 deletions tests/Fixtures/TestBundle/Parameter/OperatorMapQueryParameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?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\Parameter;

use ApiPlatform\Metadata\QueryParameter;

/**
* Distinct class so an operator-map filter can share an HTTP key with a scalar filter:
* Parameters dedup by (key, parameter class), so two parameters on the same key must differ in class.
*/
final class OperatorMapQueryParameter extends QueryParameter
{
}
38 changes: 38 additions & 0 deletions tests/Functional/Parameters/DoctrineTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\FilterWithStateOptions;
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\FilterWithStateOptionsAndNoApiFilter;
use ApiPlatform\Tests\Fixtures\TestBundle\Document\SearchFilterParameter as SearchFilterParameterDocument;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ExactAndComparisonParameter;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\FilterWithStateOptionsAndNoApiFilterEntity;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\FilterWithStateOptionsEntity;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ProductWithQueryParameter;
Expand All @@ -42,9 +43,46 @@ public static function getResources(): array
FilterWithStateOptions::class,
FilterWithStateOptionsAndNoApiFilter::class,
ProductWithQueryParameter::class,
ExactAndComparisonParameter::class,
];
}

public function testExactFilterIgnoresOperatorMap(): void
{
if ($this->isMongoDB()) {
$this->markTestSkipped('Not tested with mongodb.');
}

$resource = ExactAndComparisonParameter::class;
$this->recreateSchema([$resource]);

$container = static::$kernel->getContainer();
$manager = $container->get('doctrine')->getManager();
foreach ([5, 8, 10, 15] as $q) {
$e = new ExactAndComparisonParameter();
$e->setQuantity($q);
$manager->persist($e);
}
$manager->flush();

$route = 'exact_and_comparison_parameter';

// Exact match: ?quantity=10 must return only the row with quantity = 10.
$response = self::createClient()->request('GET', $route.'?quantity=10');
$this->assertResponseIsSuccessful();
$members = $response->toArray()['hydra:member'];
$this->assertCount(1, $members);
$this->assertSame(10, $members[0]['quantity']);

// Operator map: ?quantity[lt]=10 must apply only the comparison (< 10),
// the exact filter must NOT also inject `quantity IN ('lt' => ...)`.
$response = self::createClient()->request('GET', $route.'?quantity[lt]=10');
$this->assertResponseIsSuccessful();
$quantities = array_map(static fn ($m) => $m['quantity'], $response->toArray()['hydra:member']);
sort($quantities);
$this->assertSame([5, 8], $quantities);
}

public function testDoctrineEntitySearchFilter(): void
{
$resource = $this->isMongoDB() ? SearchFilterParameterDocument::class : SearchFilterParameter::class;
Expand Down
Loading