Skip to content

Commit

Permalink
Merge 16dd558 into e7bde8f
Browse files Browse the repository at this point in the history
  • Loading branch information
odoucet committed Feb 19, 2021
2 parents e7bde8f + 16dd558 commit 68e4a55
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 27 deletions.
38 changes: 38 additions & 0 deletions features/doctrine/search_filter.feature
Expand Up @@ -539,6 +539,44 @@ Feature: Search filter on collections
}
"""

@!mongodb
Scenario: Search collection by binary UUID (Ramsey)
Given there is a ramsey identified resource with binary uuid "c19900a9-d2b2-45bf-b040-05c72d321282"
And there is a ramsey identified resource with binary uuid "a96cb2ed-e3dc-4449-9842-830e770cdecc"
When I send a "GET" request to "/ramsey_uuid_binary_dummies?id=c19900a9-d2b2-45bf-b040-05c72d321282"
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON node "hydra:totalItems" should be equal to "1"

@!mongodb
Scenario: Search collection by binary UUID (Ramsey) (multiple values)
Given there is a ramsey identified resource with binary uuid "f71a6469-1bfc-4945-bad1-d6092f09a8c3"
When I send a "GET" request to "/ramsey_uuid_binary_dummies?id[]=c19900a9-d2b2-45bf-b040-05c72d321282&id[]=f71a6469-1bfc-4945-bad1-d6092f09a8c3"
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON node "hydra:totalItems" should be equal to "2"

@!mongodb
Scenario: Search collection by related binary UUID (Ramsey)
Given there is a ramsey identified resource with binary uuid "56fa36c3-2b5e-4813-9e3a-b0bbe2ab5553" having a related resource with binary uuid "02227dc6-a371-4b8b-a34c-bbbf921b8ebd"
And there is a ramsey identified resource with binary uuid "4d796212-4b26-4e19-b092-a32d990b1e7e" having a related resource with binary uuid "31f64c33-6061-4fc1-b0e8-f4711b607c7d"
When I send a "GET" request to "/ramsey_uuid_binary_dummies?relateds=02227dc6-a371-4b8b-a34c-bbbf921b8ebd"
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON node "hydra:totalItems" should be equal to "1"

@!mongodb
Scenario: Search collection by related binary UUID (Ramsey) (multiple values)
Given there is a ramsey identified resource with binary uuid "3248c908-a89d-483a-b75f-25888730d391" having a related resource with binary uuid "d7b2e909-37b0-411e-814c-74e044afbccb"
When I send a "GET" request to "/ramsey_uuid_binary_dummies?relateds[]=02227dc6-a371-4b8b-a34c-bbbf921b8ebd&relateds[]=d7b2e909-37b0-411e-814c-74e044afbccb"
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON node "hydra:totalItems" should be equal to "2"

Scenario: Search for entities within an impossible range
When I send a "GET" request to "/dummies?name=MuYm"
Then the response status code should be 200
Expand Down
56 changes: 43 additions & 13 deletions src/Bridge/Doctrine/Orm/Filter/SearchFilter.php
Expand Up @@ -21,8 +21,10 @@
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Core\Exception\InvalidArgumentException;
use Doctrine\DBAL\Types\Type as DBALType;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\PropertyAccess\PropertyAccess;
Expand Down Expand Up @@ -113,7 +115,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
$caseSensitive = false;
}

$this->addWhereByStrategy($strategy, $queryBuilder, $queryNameGenerator, $alias, $field, $values, $caseSensitive);
$this->addWhereByStrategy($strategy, $queryBuilder, $queryNameGenerator, $alias, $field, $values, $caseSensitive, $metadata);

return;
}
Expand Down Expand Up @@ -149,24 +151,44 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
$associationField = $associationFieldIdentifier;
}

$type = $metadata->getTypeOfField($associationField);

if (1 === \count($values)) {
$queryBuilder
->andWhere($queryBuilder->expr()->eq($associationAlias.'.'.$associationField, ':'.$valueParameter))
->setParameter($valueParameter, $values[0]);
} else {
$queryBuilder
->andWhere($queryBuilder->expr()->in($associationAlias.'.'.$associationField, ':'.$valueParameter))
->setParameter($valueParameter, $values);
->setParameter($valueParameter, $values[0], $type);

return;
}

$parameters = $queryBuilder->getParameters();
$inQuery = [];

foreach ($values as $val) {
$inQuery[] = ':'.$valueParameter;
$parameters->add(new Parameter($valueParameter, $val, $type));
$valueParameter = $queryNameGenerator->generateParameterName($associationField);
}

$queryBuilder
->andWhere($associationAlias.'.'.$associationField.' IN ('.implode(', ', $inQuery).')')
->setParameters($parameters);
}

/**
* Adds where clause according to the strategy.
*
* @throws InvalidArgumentException If strategy does not exist
*/
protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, $values, bool $caseSensitive)
protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, $values, bool $caseSensitive/*, ClassMetadata $metadata*/)
{
if (\func_num_args() > 7 && ($metadata = func_get_arg(7)) instanceof ClassMetadata) {
$type = $metadata->getTypeOfField($field);
} else {
@trigger_error(sprintf('Method %s() will have a 8th argument `$metadata` in version API Platform 3.0.', __FUNCTION__), \E_USER_DEPRECATED);
$type = null;
}

if (!\is_array($values)) {
$values = [$values];
}
Expand All @@ -175,18 +197,26 @@ protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuild
$valueParameter = ':'.$queryNameGenerator->generateParameterName($field);
$aliasedField = sprintf('%s.%s', $alias, $field);

if (null == $strategy || self::STRATEGY_EXACT == $strategy) {
if (1 == \count($values)) {
if (self::STRATEGY_EXACT === $strategy) {
if (1 === \count($values)) {
$queryBuilder
->andWhere($queryBuilder->expr()->eq($wrapCase($aliasedField), $wrapCase($valueParameter)))
->setParameter($valueParameter, $values[0]);
->setParameter($valueParameter, $values[0], $type);

return;
}

$parameters = $queryBuilder->getParameters();
$inQuery = [];
foreach ($values as $value) {
$inQuery[] = $valueParameter;
$parameters->add(new Parameter($valueParameter, $caseSensitive ? $value : strtolower($value), $type));
$valueParameter = ':'.$queryNameGenerator->generateParameterName($field);
}

$queryBuilder
->andWhere($queryBuilder->expr()->in($wrapCase($aliasedField), $valueParameter))
->setParameter($valueParameter, $caseSensitive ? $values : array_map('strtolower', $values));
->andWhere($wrapCase($aliasedField).' IN ('.implode(', ', $inQuery).')')
->setParameters($parameters);

return;
}
Expand Down Expand Up @@ -228,7 +258,7 @@ protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuild
}

$queryBuilder->andWhere($queryBuilder->expr()->orX(...$ors));
array_walk($parameters, [$queryBuilder, 'setParameter']);
array_walk($parameters, [$queryBuilder, 'setParameter'], $type);
}

/**
Expand Down
30 changes: 30 additions & 0 deletions tests/Behat/DoctrineContext.php
Expand Up @@ -139,6 +139,7 @@
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Pet;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Product;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Question;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RamseyUuidBinaryDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RamseyUuidDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedOwnedDummy;
Expand Down Expand Up @@ -1306,6 +1307,35 @@ public function thereIsARamseyIdentifiedResource(string $uuid)
$this->manager->flush();
}

/**
* @Given there is a ramsey identified resource with binary uuid :uuid
*/
public function thereIsARamseyIdentifiedResourceWithBinaryUuid(string $uuid)
{
$dummy = new RamseyUuidBinaryDummy();
$dummy->setId($uuid);

$this->manager->persist($dummy);
$this->manager->flush();
}

/**
* @Given there is a ramsey identified resource with binary uuid :uuid having a related resource with binary uuid :uuid_related
*/
public function thereIsARamseyIdentifiedResourceWithBinaryUuidHavingARelatedResourceWithBinaryUuid(string $uuid, string $uuidRelated)
{
$related = new RamseyUuidBinaryDummy();
$related->setId($uuidRelated);

$dummy = new RamseyUuidBinaryDummy();
$dummy->setId($uuid);
$dummy->addRelated($related);

$this->manager->persist($related);
$this->manager->persist($dummy);
$this->manager->flush();
}

/**
* @Given there is a dummy object with a fourth level relation
*/
Expand Down
23 changes: 10 additions & 13 deletions tests/Bridge/Doctrine/Orm/Filter/SearchFilterTest.php
Expand Up @@ -390,22 +390,18 @@ public function provideApplyTestData(): array
$filterFactory,
],
'exact (multiple values)' => [
sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name IN(:name_p1)', $this->alias, Dummy::class),
sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name IN (:name_p1, :name_p2)', $this->alias, Dummy::class),
[
'name_p1' => [
'CaSE',
'SENSitive',
],
'name_p1' => 'CaSE',
'name_p2' => 'SENSitive',
],
$filterFactory,
],
'exact (multiple values; case insensitive)' => [
sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) IN(:name_p1)', $this->alias, Dummy::class),
sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) IN (:name_p1, :name_p2)', $this->alias, Dummy::class),
[
'name_p1' => [
'case',
'insensitive',
],
'name_p1' => 'case',
'name_p2' => 'insensitive',
],
$filterFactory,
],
Expand Down Expand Up @@ -547,10 +543,11 @@ public function provideApplyTestData(): array
$filterFactory,
],
'mixed IRI and entity ID values for relations' => [
sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummies relatedDummies_a1 WHERE %1$s.relatedDummy IN(:relatedDummy_p1) AND relatedDummies_a1.id = :relatedDummies_p2', $this->alias, Dummy::class),
sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummies relatedDummies_a1 WHERE %1$s.relatedDummy IN (:relatedDummy_p1, :relatedDummy_p2) AND relatedDummies_a1.id = :relatedDummies_p4', $this->alias, Dummy::class),
[
'relatedDummy_p1' => [1, 2],
'relatedDummies_p2' => 1,
'relatedDummy_p1' => 1,
'relatedDummy_p2' => 2,
'relatedDummies_p4' => 1,
],
$filterFactory,
],
Expand Down
89 changes: 89 additions & 0 deletions tests/Fixtures/TestBundle/Entity/RamseyUuidBinaryDummy.php
@@ -0,0 +1,89 @@
<?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\Core\Tests\Fixtures\TestBundle\Entity;

use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;

/**
* @ORM\Entity
* @ApiResource
* @ApiFilter(SearchFilter::class, properties={"id"="exact", "relateds"="exact"})
*/
class RamseyUuidBinaryDummy
{
/**
* @var UuidInterface
*
* @ORM\Id
* @ORM\Column(type="uuid_binary", unique=true)
*/
private $id;

/**
* @var Collection<RamseyUuidBinaryDummy>
*
* @ORM\OneToMany(targetEntity="RamseyUuidBinaryDummy", mappedBy="relatedParent")
*/
private $relateds;

/**
* @var ?RamseyUuidBinaryDummy
*
* @ORM\ManyToOne(targetEntity="RamseyUuidBinaryDummy", inversedBy="relateds")
*/
private $relatedParent;

public function __construct()
{
$this->relateds = new ArrayCollection();
}

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

public function setId(string $uuid): void
{
$this->id = Uuid::fromString($uuid);
}

public function getRelateds(): Collection
{
return $this->relateds;
}

public function addRelated(self $dummy): void
{
$this->relateds->add($dummy);
$dummy->setRelatedParent($this);
}

public function getRelatedParent(): ?self
{
return $this->relatedParent;
}

public function setRelatedParent(self $dummy): void
{
$this->relatedParent = $dummy;
}
}
3 changes: 2 additions & 1 deletion tests/Fixtures/app/config/config_common.yml
Expand Up @@ -8,7 +8,8 @@ doctrine:
path: '%kernel.cache_dir%/db.sqlite'
charset: 'UTF8'
types:
uuid: Ramsey\Uuid\Doctrine\UuidType
uuid: Ramsey\Uuid\Doctrine\UuidType
uuid_binary: Ramsey\Uuid\Doctrine\UuidBinaryType

orm:
auto_generate_proxy_classes: '%kernel.debug%'
Expand Down
1 change: 1 addition & 0 deletions tests/Fixtures/app/config/config_mysql.yml
Expand Up @@ -13,3 +13,4 @@ doctrine:
server_version: '%env(MYSQL_VERSION)%'
types:
uuid: Ramsey\Uuid\Doctrine\UuidType
uuid_binary: Ramsey\Uuid\Doctrine\UuidBinaryType
1 change: 1 addition & 0 deletions tests/Fixtures/app/config/config_postgres.yml
Expand Up @@ -13,3 +13,4 @@ doctrine:
server_version: '%env(POSTGRES_VERSION)%'
types:
uuid: Ramsey\Uuid\Doctrine\UuidType
uuid_binary: Ramsey\Uuid\Doctrine\UuidBinaryType
1 change: 1 addition & 0 deletions tests/Fixtures/app/config/config_sqlite.yml
Expand Up @@ -10,3 +10,4 @@ doctrine:
url: '%env(resolve:DATABASE_URL)%'
types:
uuid: Ramsey\Uuid\Doctrine\UuidType
uuid_binary: Ramsey\Uuid\Doctrine\UuidBinaryType

0 comments on commit 68e4a55

Please sign in to comment.