From 6c085eea4e1b984174d7dafcd80ffa7e11fabdfb Mon Sep 17 00:00:00 2001 From: Beno!t POLASZEK Date: Thu, 8 Jun 2023 10:08:12 +0200 Subject: [PATCH] feat(doctrine): uid search filter support --- features/doctrine/search_filter.feature | 39 ++++++++++++++++ .../Common/Filter/SearchFilterTrait.php | 18 +++++++- tests/Behat/DoctrineContext.php | 14 ++++++ .../TestBundle/Document/UidBasedId.php | 42 ++++++++++++++++++ .../Fixtures/TestBundle/Entity/UidBasedId.php | 44 +++++++++++++++++++ 5 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 tests/Fixtures/TestBundle/Document/UidBasedId.php create mode 100644 tests/Fixtures/TestBundle/Entity/UidBasedId.php diff --git a/features/doctrine/search_filter.feature b/features/doctrine/search_filter.feature index 50718f81963..449061fbdaa 100644 --- a/features/doctrine/search_filter.feature +++ b/features/doctrine/search_filter.feature @@ -729,6 +729,45 @@ Feature: Search filter on collections } """ + @createSchema + Scenario: Get collection by ulid 01H2ZS93NBKJW5W4Y01S8TZ43M + Given there is a UidBasedId resource with id "01H2ZS93NBKJW5W4Y01S8TZ43M" + When I send a "GET" request to "/uid_based_ids?id=/uid_based_ids/01H2ZS93NBKJW5W4Y01S8TZ43M" + 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 should be valid according to this schema: + """ + { + "type": "object", + "properties": { + "@context": {"pattern": "^/contexts/UidBasedId"}, + "@id": {"pattern": "^/uid_based_ids"}, + "@type": {"pattern": "^hydra:Collection$"}, + "hydra:member": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@id": { + "oneOf": [ + {"pattern": "^/uid_based_ids/01H2ZS93NBKJW5W4Y01S8TZ43M"} + ] + } + } + } + }, + "hydra:view": { + "type": "object", + "properties": { + "@id": {"pattern": "^/uid_based_ids\\?id=%2Fuid_based_ids%2F01H2ZS93NBKJW5W4Y01S8TZ43M"}, + "@type": {"pattern": "^hydra:PartialCollectionView$"} + } + } + } + } + """ + @createSchema Scenario: Get collection ordered by a non valid properties When I send a "GET" request to "/dummies?unknown=0" diff --git a/src/Doctrine/Common/Filter/SearchFilterTrait.php b/src/Doctrine/Common/Filter/SearchFilterTrait.php index a9a57bc8b39..088cf1a5630 100644 --- a/src/Doctrine/Common/Filter/SearchFilterTrait.php +++ b/src/Doctrine/Common/Filter/SearchFilterTrait.php @@ -21,6 +21,7 @@ use ApiPlatform\Metadata\IriConverterInterface; use Psr\Log\LoggerInterface; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Component\Uid\AbstractUid; /** * Trait for filtering the collection by given properties. @@ -127,12 +128,20 @@ protected function getIdFromValue(string $value): mixed $item = $iriConverter->getResourceFromIri($value, ['fetch_data' => false]); if (null === $this->identifiersExtractor) { - return $this->getPropertyAccessor()->getValue($item, 'id'); + $id = $this->getPropertyAccessor()->getValue($item, 'id'); + + return $this->toUuidValue($id); } $identifiers = $this->identifiersExtractor->getIdentifiersFromItem($item); - return 1 === \count($identifiers) ? array_pop($identifiers) : $identifiers; + if (1 === \count($identifiers)) { + $id = array_pop($identifiers); + + return $this->toUuidValue($id); + } + + return array_map([$this, 'toUuidValue'], $identifiers); } catch (InvalidArgumentException) { // Do nothing, return the raw value } @@ -140,6 +149,11 @@ protected function getIdFromValue(string $value): mixed return $value; } + private function toUuidValue(mixed $id): mixed + { + return ($id instanceof AbstractUid) ? $id->toBinary() : $id; + } + /** * Normalize the values array. */ diff --git a/tests/Behat/DoctrineContext.php b/tests/Behat/DoctrineContext.php index 8894e737d37..7c5fc7f47c0 100644 --- a/tests/Behat/DoctrineContext.php +++ b/tests/Behat/DoctrineContext.php @@ -97,6 +97,7 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Document\SoMany as SoManyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\Taxon as TaxonDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\ThirdLevel as ThirdLevelDocument; +use ApiPlatform\Tests\Fixtures\TestBundle\Document\UidBasedId as UidBasedIdDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\UrlEncodedId as UrlEncodedIdDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\User as UserDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\VideoGame as VideoGameDocument; @@ -200,6 +201,7 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Taxon; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ThirdLevel; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\TreeDummy; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\UidBasedId; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\UrlEncodedId; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\User; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\UuidIdentifierDummy; @@ -217,6 +219,7 @@ use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ObjectManager; use Ramsey\Uuid\Uuid; +use Symfony\Component\Uid\Ulid; use Symfony\Component\Uid\Uuid as SymfonyUuid; /** @@ -1436,6 +1439,17 @@ public function thereIsAUrlEncodedIdResource(): void $this->manager->clear(); } + /** + * @Given there is a UidBasedId resource with id :id + */ + public function thereIsAUidBasedIdResource(string $id): void + { + $uidBasedIdResource = ($this->isOrm() ? new UidBasedId(Ulid::fromBase32($id)) : new UidBasedIdDocument(Ulid::fromBase32($id))); + $this->manager->persist($uidBasedIdResource); + $this->manager->flush(); + $this->manager->clear(); + } + /** * @Given there is a Program */ diff --git a/tests/Fixtures/TestBundle/Document/UidBasedId.php b/tests/Fixtures/TestBundle/Document/UidBasedId.php new file mode 100644 index 00000000000..ddf71703ef3 --- /dev/null +++ b/tests/Fixtures/TestBundle/Document/UidBasedId.php @@ -0,0 +1,42 @@ + + * + * 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\Orm\Filter\SearchFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; +use Symfony\Component\Uid\Ulid; + +/** + * @author Beno!t POLASZEK + * + * Resource with an Uid-based ID + */ +#[ApiResource(operations: [new Get(), new Post(), new GetCollection()])] +#[ApiFilter(SearchFilter::class, properties: ['id' => 'exact'])] +#[ODM\Document] +class UidBasedId +{ + #[ODM\Id(strategy: 'none')] + public Ulid $id; + + public function __construct(?Ulid $id) + { + $this->id = $id ?? new Ulid(); + } +} diff --git a/tests/Fixtures/TestBundle/Entity/UidBasedId.php b/tests/Fixtures/TestBundle/Entity/UidBasedId.php new file mode 100644 index 00000000000..1eb8887b67d --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/UidBasedId.php @@ -0,0 +1,44 @@ + + * + * 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\SearchFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Uid\Ulid; + +/** + * @author Beno!t POLASZEK + * + * Resource with an Uid-based ID + */ +#[ApiResource(operations: [new Get(), new Post(), new GetCollection()])] +#[ApiFilter(SearchFilter::class, properties: ['id' => 'exact'])] +#[ORM\Entity] +class UidBasedId +{ + #[ORM\Column(type: 'ulid')] + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'NONE')] + public Ulid $id; + + public function __construct(?Ulid $id) + { + $this->id = $id ?? new Ulid(); + } +}