From 061b581cea144a529e726b948eb6369c659db088 Mon Sep 17 00:00:00 2001 From: aaa2000 Date: Fri, 12 Dec 2025 17:06:26 +0100 Subject: [PATCH 1/4] fix(metadata): add uuid filter (#7465) --- composer.json | 2 +- .../Orm/Filter/AbstractUuidFilter.php | 213 +++++++++++ src/Doctrine/Orm/Filter/UlidFilter.php | 58 +++ src/Doctrine/Orm/Filter/UuidBinaryFilter.php | 40 ++ src/Doctrine/Orm/Filter/UuidFilter.php | 18 + src/Doctrine/Orm/composer.json | 2 +- .../Bundle/Resources/config/doctrine_orm.php | 9 + .../Entity/Uuid/RamseyUuidBinaryDevice.php | 48 +++ .../Uuid/RamseyUuidBinaryDeviceEndpoint.php | 55 +++ .../Entity/Uuid/RamseyUuidDevice.php | 48 +++ .../Entity/Uuid/RamseyUuidDeviceEndpoint.php | 55 +++ .../Entity/Uuid/SymfonyUlidDevice.php | 47 +++ .../Entity/Uuid/SymfonyUlidDeviceEndpoint.php | 55 +++ .../Entity/Uuid/SymfonyUuidDevice.php | 47 +++ .../Entity/Uuid/SymfonyUuidDeviceEndpoint.php | 54 +++ tests/Fixtures/app/config/config_common.yml | 1 + .../Uuid/UuidFilterBaseTestCase.php | 359 ++++++++++++++++++ .../UuidFilterWithRamseyUuidBinaryTest.php | 68 ++++ .../Uuid/UuidFilterWithRamseyUuidTest.php | 57 +++ .../Uuid/UuidFilterWithSymfonyUlidTest.php | 103 +++++ .../Uuid/UuidFilterWithSymfonyUuidTest.php | 57 +++ tests/SetupClassResourcesTrait.php | 2 +- 22 files changed, 1395 insertions(+), 3 deletions(-) create mode 100644 src/Doctrine/Orm/Filter/AbstractUuidFilter.php create mode 100644 src/Doctrine/Orm/Filter/UlidFilter.php create mode 100644 src/Doctrine/Orm/Filter/UuidBinaryFilter.php create mode 100644 src/Doctrine/Orm/Filter/UuidFilter.php create mode 100644 tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidBinaryDevice.php create mode 100644 tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidBinaryDeviceEndpoint.php create mode 100644 tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidDevice.php create mode 100644 tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidDeviceEndpoint.php create mode 100644 tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUlidDevice.php create mode 100644 tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUlidDeviceEndpoint.php create mode 100644 tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUuidDevice.php create mode 100644 tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUuidDeviceEndpoint.php create mode 100644 tests/Functional/Uuid/UuidFilterBaseTestCase.php create mode 100644 tests/Functional/Uuid/UuidFilterWithRamseyUuidBinaryTest.php create mode 100644 tests/Functional/Uuid/UuidFilterWithRamseyUuidTest.php create mode 100644 tests/Functional/Uuid/UuidFilterWithSymfonyUlidTest.php create mode 100644 tests/Functional/Uuid/UuidFilterWithSymfonyUuidTest.php diff --git a/composer.json b/composer.json index 1df67d8036d..04dda4477c6 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ "conflict": { "doctrine/common": "<3.2.2", "doctrine/dbal": "<2.10", - "doctrine/orm": "<2.14.0", + "doctrine/orm": "<2.14.0 || 3.0.0", "doctrine/mongodb-odm": "<2.4", "doctrine/persistence": "<1.3", "symfony/framework-bundle": "6.4.6 || 7.0.6", diff --git a/src/Doctrine/Orm/Filter/AbstractUuidFilter.php b/src/Doctrine/Orm/Filter/AbstractUuidFilter.php new file mode 100644 index 00000000000..179c7b1bbf9 --- /dev/null +++ b/src/Doctrine/Orm/Filter/AbstractUuidFilter.php @@ -0,0 +1,213 @@ + + * + * 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\Common\PropertyHelperTrait; +use ApiPlatform\Doctrine\Orm\PropertyHelperTrait as OrmPropertyHelperTrait; +use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\BackwardCompatibleFilterDescriptionTrait; +use ApiPlatform\Metadata\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\JsonSchemaFilterInterface; +use ApiPlatform\Metadata\OpenApiParameterFilterInterface; +use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Parameter; +use ApiPlatform\Metadata\QueryParameter; +use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter; +use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\Type; +use Doctrine\ORM\Query\Expr\Join; +use Doctrine\ORM\QueryBuilder; + +/** + * @internal + */ +class AbstractUuidFilter implements FilterInterface, ManagerRegistryAwareInterface, JsonSchemaFilterInterface, OpenApiParameterFilterInterface, LoggerAwareInterface +{ + use BackwardCompatibleFilterDescriptionTrait; + use LoggerAwareTrait; + use ManagerRegistryAwareTrait; + use OrmPropertyHelperTrait; + use PropertyHelperTrait; + + private const UUID_SCHEMA = [ + 'type' => 'string', + 'format' => 'uuid', + ]; + + public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void + { + $parameter = $context['parameter'] ?? null; + if (!$parameter) { + return; + } + + $this->filterProperty($parameter->getProperty(), $parameter->getValue(), $queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); + } + + private function filterProperty(string $property, mixed $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void + { + $alias = $queryBuilder->getRootAliases()[0]; + $field = $property; + + $associations = []; + if ($this->isPropertyNested($property, $resourceClass)) { + [$alias, $field, $associations] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass, Join::INNER_JOIN); + } + + $metadata = $this->getNestedMetadata($resourceClass, $associations); + + if ($metadata->hasField($field)) { + $value = $this->convertValuesToTheDatabaseRepresentation($queryBuilder, $this->getDoctrineFieldType($property, $resourceClass), $value); + $this->addWhere($queryBuilder, $queryNameGenerator, $alias, $field, $value); + + return; + } + + // metadata doesn't have the field, nor an association on the field + if (!$metadata->hasAssociation($field)) { + $this->logger->notice('Tried to filter on a non-existent field or association', [ + 'field' => $field, + 'resource_class' => $resourceClass, + 'exception' => new InvalidArgumentException(\sprintf('Property "%s" does not exist in resource "%s".', $field, $resourceClass)), + ]); + + return; + } + + // association, let's fetch the entity (or reference to it) if we can so we can make sure we get its orm id + $associationResourceClass = $metadata->getAssociationTargetClass($field); + $associationMetadata = $this->getClassMetadata($associationResourceClass); + $associationFieldIdentifier = $associationMetadata->getIdentifierFieldNames()[0]; + $doctrineTypeField = $this->getDoctrineFieldType($associationFieldIdentifier, $associationResourceClass); + + $associationAlias = $alias; + $associationField = $field; + + if ($metadata->isCollectionValuedAssociation($associationField) || $metadata->isAssociationInverseSide($field)) { + $associationAlias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $alias, $associationField); + $associationField = $associationFieldIdentifier; + } + + $value = $this->convertValuesToTheDatabaseRepresentation($queryBuilder, $doctrineTypeField, $value); + $this->addWhere($queryBuilder, $queryNameGenerator, $associationAlias, $associationField, $value); + } + + /** + * Converts value to their database representation. + */ + private function convertValuesToTheDatabaseRepresentation(QueryBuilder $queryBuilder, ?string $doctrineFieldType, mixed $value): mixed + { + if (null === $doctrineFieldType || !Type::hasType($doctrineFieldType)) { + throw new InvalidArgumentException(\sprintf('The Doctrine type "%s" is not valid or not registered.', $doctrineFieldType)); + } + + $doctrineType = Type::getType($doctrineFieldType); + $platform = $queryBuilder->getEntityManager()->getConnection()->getDatabasePlatform(); + + if (\is_array($value)) { + $databaseValues = []; + foreach ($value as $val) { + try { + $databaseValues[] = $doctrineType->convertToDatabaseValue($val, $platform); + } catch (ConversionException $e) { + $this->logger->notice('Invalid value conversion to database representation', [ + 'exception' => $e, + ]); + } + } + + return $databaseValues; + } + + try { + return $doctrineType->convertToDatabaseValue($value, $platform); + } catch (ConversionException $e) { + $this->logger->notice('Invalid value conversion to database representation', [ + 'exception' => $e, + ]); + + return null; + } + } + + /** + * Adds where clause. + */ + private function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, mixed $value): void + { + $valueParameter = ':'.$queryNameGenerator->generateParameterName($field); + $aliasedField = \sprintf('%s.%s', $alias, $field); + + if (!\is_array($value)) { + $queryBuilder + ->andWhere($queryBuilder->expr()->eq($aliasedField, $valueParameter)) + ->setParameter($valueParameter, $value, $this->getDoctrineParameterType()); + + return; + } + + $queryBuilder + ->andWhere($queryBuilder->expr()->in($aliasedField, $valueParameter)) + ->setParameter($valueParameter, $value, $this->getDoctrineArrayParameterType()); + } + + protected function getDoctrineParameterType(): ?ParameterType + { + return null; + } + + protected function getDoctrineArrayParameterType(): ?ArrayParameterType + { + return null; + } + + public function getOpenApiParameters(Parameter $parameter): array + { + $in = $parameter instanceof QueryParameter ? 'query' : 'header'; + $key = $parameter->getKey(); + + return [ + new OpenApiParameter( + name: $key, + in: $in, + schema: self::UUID_SCHEMA, + style: 'form', + explode: false + ), + new OpenApiParameter( + name: $key.'[]', + in: $in, + description: 'One or more Uuids', + schema: [ + 'type' => 'array', + 'items' => self::UUID_SCHEMA, + ], + style: 'deepObject', + explode: true + ), + ]; + } + + public function getSchema(Parameter $parameter): array + { + return self::UUID_SCHEMA; + } +} diff --git a/src/Doctrine/Orm/Filter/UlidFilter.php b/src/Doctrine/Orm/Filter/UlidFilter.php new file mode 100644 index 00000000000..39ae017b72c --- /dev/null +++ b/src/Doctrine/Orm/Filter/UlidFilter.php @@ -0,0 +1,58 @@ + + * + * 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\Metadata\Parameter; +use ApiPlatform\Metadata\QueryParameter; +use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter; + +final class UlidFilter extends AbstractUuidFilter +{ + private const ULID_SCHEMA = [ + 'type' => 'string', + 'format' => 'ulid', + ]; + + public function getOpenApiParameters(Parameter $parameter): array + { + $in = $parameter instanceof QueryParameter ? 'query' : 'header'; + $key = $parameter->getKey(); + + return [ + new OpenApiParameter( + name: $key, + in: $in, + schema: self::ULID_SCHEMA, + style: 'form', + explode: false + ), + new OpenApiParameter( + name: $key.'[]', + in: $in, + description: 'One or more Ulids', + schema: [ + 'type' => 'array', + 'items' => self::ULID_SCHEMA, + ], + style: 'deepObject', + explode: true + ), + ]; + } + + public function getSchema(Parameter $parameter): array + { + return self::ULID_SCHEMA; + } +} diff --git a/src/Doctrine/Orm/Filter/UuidBinaryFilter.php b/src/Doctrine/Orm/Filter/UuidBinaryFilter.php new file mode 100644 index 00000000000..d2dd43caabc --- /dev/null +++ b/src/Doctrine/Orm/Filter/UuidBinaryFilter.php @@ -0,0 +1,40 @@ + + * + * 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 Composer\InstalledVersions; +use Composer\Semver\VersionParser; +use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\ParameterType; + +final class UuidBinaryFilter extends AbstractUuidFilter +{ + public function __construct() + { + if (!InstalledVersions::satisfies(new VersionParser(), 'doctrine/orm', '^3.0.1')) { + // @see https://github.com/doctrine/orm/pull/11287 + throw new \LogicException('The "doctrine/orm" package version 3.0.1 or higher is required to use the UuidBinaryFilter. Please upgrade your dependencies.'); + } + } + + protected function getDoctrineParameterType(): ParameterType + { + return ParameterType::BINARY; + } + + protected function getDoctrineArrayParameterType(): ArrayParameterType + { + return ArrayParameterType::BINARY; + } +} diff --git a/src/Doctrine/Orm/Filter/UuidFilter.php b/src/Doctrine/Orm/Filter/UuidFilter.php new file mode 100644 index 00000000000..416615c3949 --- /dev/null +++ b/src/Doctrine/Orm/Filter/UuidFilter.php @@ -0,0 +1,18 @@ + + * + * 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; + +final class UuidFilter extends AbstractUuidFilter +{ +} diff --git a/src/Doctrine/Orm/composer.json b/src/Doctrine/Orm/composer.json index 0fb956e21f9..fd04dd4553d 100644 --- a/src/Doctrine/Orm/composer.json +++ b/src/Doctrine/Orm/composer.json @@ -27,7 +27,7 @@ "api-platform/doctrine-common": "^4.2.9", "api-platform/metadata": "^4.2", "api-platform/state": "^4.2.4", - "doctrine/orm": "^2.17 || ^3.0" + "doctrine/orm": "^2.17 || ^3.0.1" }, "require-dev": { "doctrine/doctrine-bundle": "^2.11 || ^3.1", diff --git a/src/Symfony/Bundle/Resources/config/doctrine_orm.php b/src/Symfony/Bundle/Resources/config/doctrine_orm.php index 57504522571..6e72d94d269 100644 --- a/src/Symfony/Bundle/Resources/config/doctrine_orm.php +++ b/src/Symfony/Bundle/Resources/config/doctrine_orm.php @@ -222,6 +222,15 @@ ->parent('api_platform.doctrine.orm.search_filter') ->args([[]]); + $services->set('api_platform.doctrine.orm.uuid_filter', 'ApiPlatform\Doctrine\Orm\Filter\UuidFilter'); + $services->alias('ApiPlatform\Doctrine\Orm\Filter\UuidFilter', 'api_platform.doctrine.orm.uuid_filter'); + + $services->set('api_platform.doctrine.orm.ulid_filter', 'ApiPlatform\Doctrine\Orm\Filter\UlidFilter'); + $services->alias('ApiPlatform\Doctrine\Orm\Filter\UlidFilter', 'api_platform.doctrine.orm.ulid_filter'); + + $services->set('api_platform.doctrine.orm.uuid_binary_filter', 'ApiPlatform\Doctrine\Orm\Filter\UuidBinaryFilter'); + $services->alias('ApiPlatform\Doctrine\Orm\Filter\UuidBinaryFilter', 'api_platform.doctrine.orm.uuid_binary_filter'); + $services->set('api_platform.doctrine.orm.metadata.resource.metadata_collection_factory', 'ApiPlatform\Doctrine\Orm\Metadata\Resource\DoctrineOrmResourceCollectionMetadataFactory') ->decorate('api_platform.metadata.resource.metadata_collection_factory', null, 40) ->args([ diff --git a/tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidBinaryDevice.php b/tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidBinaryDevice.php new file mode 100644 index 00000000000..8674dc8495b --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidBinaryDevice.php @@ -0,0 +1,48 @@ + + * + * 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\Uuid; + +use ApiPlatform\Doctrine\Orm\Filter\UuidBinaryFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Metadata\QueryParameter; +use Doctrine\ORM\Mapping as ORM; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; + +#[ApiResource(operations: [ + new Get(), + new GetCollection( + parameters: [ + 'id' => new QueryParameter( + filter: new UuidBinaryFilter(), + ), + ] + ), + new Post(), +])] +#[ORM\Entity] +class RamseyUuidBinaryDevice +{ + #[ORM\Id] + #[ORM\Column(type: 'uuid_binary', unique: true)] + public UuidInterface $id; + + public function __construct(?UuidInterface $id = null) + { + $this->id = $id ?? Uuid::uuid7(); + } +} diff --git a/tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidBinaryDeviceEndpoint.php b/tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidBinaryDeviceEndpoint.php new file mode 100644 index 00000000000..a0967282724 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidBinaryDeviceEndpoint.php @@ -0,0 +1,55 @@ + + * + * 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\Uuid; + +use ApiPlatform\Doctrine\Orm\Filter\UuidBinaryFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Metadata\QueryParameter; +use Doctrine\ORM\Mapping as ORM; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; + +#[ApiResource(operations: [ + new Get(), + new GetCollection( + parameters: [ + 'id' => new QueryParameter( + filter: new UuidBinaryFilter(), + ), + 'myDevice' => new QueryParameter( + filter: new UuidBinaryFilter(), + ), + ] + ), + new Post(), +])] +#[ORM\Entity] +class RamseyUuidBinaryDeviceEndpoint +{ + #[ORM\Id] + #[ORM\Column(type: 'uuid_binary', unique: true)] + public UuidInterface $id; + + #[ORM\ManyToOne] + public ?RamseyUuidBinaryDevice $myDevice = null; + + public function __construct(?UuidInterface $id = null, ?RamseyUuidBinaryDevice $myDevice = null) + { + $this->id = $id ?? Uuid::uuid7(); + $this->myDevice = $myDevice; + } +} diff --git a/tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidDevice.php b/tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidDevice.php new file mode 100644 index 00000000000..96b0a6e0b44 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidDevice.php @@ -0,0 +1,48 @@ + + * + * 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\Uuid; + +use ApiPlatform\Doctrine\Orm\Filter\UuidFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Metadata\QueryParameter; +use Doctrine\ORM\Mapping as ORM; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; + +#[ApiResource(operations: [ + new Get(), + new GetCollection( + parameters: [ + 'id' => new QueryParameter( + filter: new UuidFilter(), + ), + ] + ), + new Post(), +])] +#[ORM\Entity] +class RamseyUuidDevice +{ + #[ORM\Id] + #[ORM\Column(type: 'uuid', unique: true)] + public UuidInterface $id; + + public function __construct(?UuidInterface $id = null) + { + $this->id = $id ?? Uuid::uuid7(); + } +} diff --git a/tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidDeviceEndpoint.php b/tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidDeviceEndpoint.php new file mode 100644 index 00000000000..e2c1f146907 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Uuid/RamseyUuidDeviceEndpoint.php @@ -0,0 +1,55 @@ + + * + * 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\Uuid; + +use ApiPlatform\Doctrine\Orm\Filter\UuidFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Metadata\QueryParameter; +use Doctrine\ORM\Mapping as ORM; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; + +#[ApiResource(operations: [ + new Get(), + new GetCollection( + parameters: [ + 'id' => new QueryParameter( + filter: new UuidFilter(), + ), + 'myDevice' => new QueryParameter( + filter: new UuidFilter(), + ), + ] + ), + new Post(), +])] +#[ORM\Entity] +class RamseyUuidDeviceEndpoint +{ + #[ORM\Id] + #[ORM\Column(type: 'uuid', unique: true)] + public UuidInterface $id; + + #[ORM\ManyToOne] + public ?RamseyUuidDevice $myDevice = null; + + public function __construct(?UuidInterface $id = null, ?RamseyUuidDevice $myDevice = null) + { + $this->id = $id ?? Uuid::uuid7(); + $this->myDevice = $myDevice; + } +} diff --git a/tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUlidDevice.php b/tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUlidDevice.php new file mode 100644 index 00000000000..19d39ef8c99 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUlidDevice.php @@ -0,0 +1,47 @@ + + * + * 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\Uuid; + +use ApiPlatform\Doctrine\Orm\Filter\UlidFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Metadata\QueryParameter; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Uid\Ulid; + +#[ApiResource(operations: [ + new Get(), + new GetCollection( + parameters: [ + 'id' => new QueryParameter( + filter: new UlidFilter(), + ), + ] + ), + new Post(), +])] +#[ORM\Entity] +class SymfonyUlidDevice +{ + #[ORM\Id] + #[ORM\Column(type: 'ulid', unique: true)] + public Ulid $id; + + public function __construct(?Ulid $id = null) + { + $this->id = $id ?? new Ulid(); + } +} diff --git a/tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUlidDeviceEndpoint.php b/tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUlidDeviceEndpoint.php new file mode 100644 index 00000000000..8f717b0e6a0 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUlidDeviceEndpoint.php @@ -0,0 +1,55 @@ + + * + * 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\Uuid; + +use ApiPlatform\Doctrine\Orm\Filter\UlidFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Metadata\QueryParameter; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Types\UlidType; +use Symfony\Component\Uid\Ulid; + +#[ApiResource(operations: [ + new Get(), + new GetCollection( + parameters: [ + 'id' => new QueryParameter( + filter: new UlidFilter(), + ), + 'myDevice' => new QueryParameter( + filter: new UlidFilter(), + ), + ] + ), + new Post(), +])] +#[ORM\Entity] +class SymfonyUlidDeviceEndpoint +{ + #[ORM\Id] + #[ORM\Column(type: UlidType::NAME, unique: true)] + public Ulid $id; + + #[ORM\ManyToOne] + public ?SymfonyUlidDevice $myDevice = null; + + public function __construct(?Ulid $id = null, ?SymfonyUlidDevice $myDevice = null) + { + $this->id = $id ?? new Ulid(); + $this->myDevice = $myDevice; + } +} diff --git a/tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUuidDevice.php b/tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUuidDevice.php new file mode 100644 index 00000000000..04d85978ab3 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUuidDevice.php @@ -0,0 +1,47 @@ + + * + * 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\Uuid; + +use ApiPlatform\Doctrine\Orm\Filter\UuidFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Metadata\QueryParameter; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Uid\Uuid; + +#[ApiResource(operations: [ + new Get(), + new GetCollection( + parameters: [ + 'id' => new QueryParameter( + filter: new UuidFilter(), + ), + ] + ), + new Post(), +])] +#[ORM\Entity] +class SymfonyUuidDevice +{ + #[ORM\Id] + #[ORM\Column(type: 'symfony_uuid', unique: true)] + public Uuid $id; + + public function __construct(?Uuid $id = null) + { + $this->id = $id ?? Uuid::v7(); + } +} diff --git a/tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUuidDeviceEndpoint.php b/tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUuidDeviceEndpoint.php new file mode 100644 index 00000000000..af9cc3ffa2c --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Uuid/SymfonyUuidDeviceEndpoint.php @@ -0,0 +1,54 @@ + + * + * 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\Uuid; + +use ApiPlatform\Doctrine\Orm\Filter\UuidFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Metadata\QueryParameter; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Uid\Uuid; + +#[ApiResource(operations: [ + new Get(), + new GetCollection( + parameters: [ + 'id' => new QueryParameter( + filter: new UuidFilter(), + ), + 'myDevice' => new QueryParameter( + filter: new UuidFilter(), + ), + ] + ), + new Post(), +])] +#[ORM\Entity] +class SymfonyUuidDeviceEndpoint +{ + #[ORM\Id] + #[ORM\Column(type: 'symfony_uuid', unique: true)] + public Uuid $id; + + #[ORM\ManyToOne] + public ?SymfonyUuidDevice $myDevice = null; + + public function __construct(?Uuid $id = null, ?SymfonyUuidDevice $myDevice = null) + { + $this->id = $id ?? Uuid::v7(); + $this->myDevice = $myDevice; + } +} diff --git a/tests/Fixtures/app/config/config_common.yml b/tests/Fixtures/app/config/config_common.yml index 0a7fd3f133f..e6d7667d55e 100644 --- a/tests/Fixtures/app/config/config_common.yml +++ b/tests/Fixtures/app/config/config_common.yml @@ -10,6 +10,7 @@ doctrine: charset: 'UTF8' types: uuid: Ramsey\Uuid\Doctrine\UuidType + uuid_binary: Ramsey\Uuid\Doctrine\UuidBinaryType symfony_uuid: Symfony\Bridge\Doctrine\Types\UuidType orm: diff --git a/tests/Functional/Uuid/UuidFilterBaseTestCase.php b/tests/Functional/Uuid/UuidFilterBaseTestCase.php new file mode 100644 index 00000000000..b38702c0266 --- /dev/null +++ b/tests/Functional/Uuid/UuidFilterBaseTestCase.php @@ -0,0 +1,359 @@ + + * + * 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\Functional\Uuid; + +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Tests\RecreateSchemaTrait; +use ApiPlatform\Tests\SetupClassResourcesTrait; + +abstract class UuidFilterBaseTestCase extends ApiTestCase +{ + use RecreateSchemaTrait; + use SetupClassResourcesTrait; + + protected static ?bool $alwaysBootKernel = false; + + protected function setUp(): void + { + parent::setUp(); + + if ($this->isMongoDB()) { + $this->markTestSkipped(); + } + } + + /** + * @return class-string + */ + abstract protected static function getDeviceEndpointClass(): string; + + /** + * @return class-string + */ + abstract protected static function getDeviceClass(): string; + + abstract public function getUrlPrefix(): string; + + abstract public function geTypePrefix(): string; + + public function createDeviceEndpoint(mixed ...$args): object + { + return (new \ReflectionClass(static::getDeviceEndpointClass()))->newInstanceArgs($args); + } + + public function createDevice(mixed ...$args): object + { + return (new \ReflectionClass(static::getDeviceClass()))->newInstanceArgs($args); + } + + public function testSearchFilterByUuid(): void + { + $this->recreateSchema(static::getResources()); + + $manager = $this->getManager(); + $manager->persist($device = $this->createDevice()); + $manager->persist($this->createDevice()); + $manager->flush(); + + $response = self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_devices', [ + 'query' => [ + 'id' => (string) $device->id, + ], + ]); + + self::assertResponseIsSuccessful(); + $json = $response->toArray(); + + self::assertArraySubset(['hydra:totalItems' => 1], $json); + self::assertArraySubset( + [ + 'hydra:member' => [ + [ + '@id' => '/'.$this->getUrlPrefix().'_devices/'.$device->id, + '@type' => $this->geTypePrefix().'Device', + 'id' => (string) $device->id, + ], + ], + ], + $json + ); + } + + public function testSearchFilterByManyUuid(): void + { + $this->recreateSchema(static::getResources()); + + $manager = $this->getManager(); + $manager->persist($device = $this->createDevice()); + $manager->persist($otherDevice = $this->createDevice()); + $manager->persist($this->createDevice()); + $manager->flush(); + + $response = self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_devices', [ + 'query' => [ + 'id' => [ + (string) $device->id, + (string) $otherDevice->id, + ], + ], + ]); + + self::assertResponseIsSuccessful(); + $json = $response->toArray(); + + self::assertArraySubset(['hydra:totalItems' => 2], $json); + self::assertArraySubset( + [ + 'hydra:member' => [ + [ + '@id' => '/'.$this->getUrlPrefix().'_devices/'.$device->id, + '@type' => $this->geTypePrefix().'Device', + 'id' => (string) $device->id, + ], + [ + '@id' => '/'.$this->getUrlPrefix().'_devices/'.$otherDevice->id, + '@type' => $this->geTypePrefix().'Device', + 'id' => (string) $otherDevice->id, + ], + ], + ], + $json + ); + } + + public function testSearchFilterByInvalidUuid(): void + { + $this->recreateSchema(static::getResources()); + + $manager = $this->getManager(); + $manager->persist($this->createDevice()); + $manager->persist($this->createDevice()); + $manager->flush(); + + $response = self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_devices', [ + 'query' => [ + 'id' => 'invalid-uuid', + ], + ]); + + self::assertResponseIsSuccessful(); + $json = $response->toArray(); + + self::assertArraySubset(['hydra:totalItems' => 0], $json); + self::assertArraySubset( + [ + 'hydra:member' => [], + ], + $json + ); + } + + public function testSearchFilterByManyInvalidUuid(): void + { + $this->recreateSchema(static::getResources()); + + $manager = $this->getManager(); + $manager->persist($this->createDevice()); + $manager->persist($this->createDevice()); + $manager->flush(); + + $response = self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_devices', [ + 'query' => [ + 'id' => ['invalid-uuid', 'other-invalid-uuid'], + ], + ]); + + self::assertResponseIsSuccessful(); + $json = $response->toArray(); + + self::assertArraySubset(['hydra:totalItems' => 0], $json); + self::assertArraySubset( + [ + 'hydra:member' => [], + ], + $json + ); + } + + public function testSearchFilterOnManyToOneRelationByUuid(): void + { + $this->recreateSchema(static::getResources()); + + $manager = $this->getManager(); + $manager->persist($fooDevice = $this->createDevice()); + $manager->persist($barDevice = $this->createDevice()); + $manager->persist($this->createDeviceEndpoint(null, $fooDevice)); + $manager->persist($barDeviceEndpoint = $this->createDeviceEndpoint(null, $barDevice)); + $manager->flush(); + + $response = self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_device_endpoints', [ + 'query' => [ + 'myDevice' => (string) $barDevice->id, + ], + ]); + + self::assertResponseIsSuccessful(); + $json = $response->toArray(); + + self::assertArraySubset(['hydra:totalItems' => 1], $json); + self::assertArraySubset( + [ + 'hydra:member' => [ + [ + '@id' => '/'.$this->getUrlPrefix().'_device_endpoints/'.$barDeviceEndpoint->id, + '@type' => $this->geTypePrefix().'DeviceEndpoint', + 'id' => (string) $barDeviceEndpoint->id, + ], + ], + ], + $json + ); + } + + public function testSearchFilterOnManyToOneRelationByManyUuid(): void + { + $this->recreateSchema(static::getResources()); + + $manager = $this->getManager(); + $manager->persist($fooDevice = $this->createDevice()); + $manager->persist($barDevice = $this->createDevice()); + $manager->persist($bazDevice = $this->createDevice()); + $manager->persist($fooDeviceEndpoint = $this->createDeviceEndpoint(null, $fooDevice)); + $manager->persist($barDeviceEndpoint = $this->createDeviceEndpoint(null, $barDevice)); + $manager->persist($this->createDeviceEndpoint(null, $bazDevice)); + $manager->flush(); + + $response = self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_device_endpoints', [ + 'query' => [ + 'myDevice' => [ + (string) $fooDevice->id, + (string) $barDevice->id, + ], + ], + ]); + + self::assertResponseIsSuccessful(); + $json = $response->toArray(); + + self::assertArraySubset(['hydra:totalItems' => 2], $json); + self::assertArraySubset( + [ + 'hydra:member' => [ + [ + '@id' => '/'.$this->getUrlPrefix().'_device_endpoints/'.$fooDeviceEndpoint->id, + '@type' => $this->geTypePrefix().'DeviceEndpoint', + 'id' => (string) $fooDeviceEndpoint->id, + ], + [ + '@id' => '/'.$this->getUrlPrefix().'_device_endpoints/'.$barDeviceEndpoint->id, + '@type' => $this->geTypePrefix().'DeviceEndpoint', + 'id' => (string) $barDeviceEndpoint->id, + ], + ], + ], + $json + ); + } + + public function testSearchFilterOnManyToOneRelationByInvalidUuids(): void + { + $this->recreateSchema(static::getResources()); + + $manager = $this->getManager(); + $manager->persist($fooDevice = $this->createDevice()); + $manager->persist($barDevice = $this->createDevice()); + $manager->persist($bazDevice = $this->createDevice()); + $manager->persist($this->createDeviceEndpoint(null, $fooDevice)); + $manager->persist($this->createDeviceEndpoint(null, $barDevice)); + $manager->persist($this->createDeviceEndpoint(null, $bazDevice)); + $manager->flush(); + + $response = self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_device_endpoints', [ + 'query' => [ + 'myDevice' => [ + 'invalid-uuid', + 'other-invalid-uuid', + ], + ], + ]); + + self::assertResponseIsSuccessful(); + $json = $response->toArray(); + + self::assertArraySubset(['hydra:totalItems' => 0], $json); + self::assertArraySubset( + [ + 'hydra:member' => [], + ], + $json + ); + } + + public function testGetOpenApiDescription(): void + { + $response = self::createClient()->request('GET', '/docs', [ + 'headers' => ['Accept' => 'application/vnd.openapi+json'], + ]); + + $json = $response->toArray(); + + self::assertContains( + [ + 'name' => 'id', + 'in' => 'query', + 'description' => '', + 'required' => false, + 'deprecated' => false, + 'schema' => [ + 'type' => 'string', + 'format' => 'uuid', + ], + 'style' => 'form', + 'explode' => false, + ], + $json['paths']['/'.$this->getUrlPrefix().'_device_endpoints']['get']['parameters'] + ); + + self::assertContains( + [ + 'name' => 'id[]', + 'in' => 'query', + 'description' => 'One or more Uuids', + 'required' => false, + 'deprecated' => false, + 'schema' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'string', + 'format' => 'uuid', + ], + ], + 'style' => 'deepObject', + 'explode' => true, + ], + $json['paths']['/'.$this->getUrlPrefix().'_device_endpoints']['get']['parameters'] + ); + } + + protected function tearDown(): void + { + if ($this->isMongoDB()) { + return; + } + + $this->recreateSchema(static::getResources()); + + parent::tearDown(); + } +} diff --git a/tests/Functional/Uuid/UuidFilterWithRamseyUuidBinaryTest.php b/tests/Functional/Uuid/UuidFilterWithRamseyUuidBinaryTest.php new file mode 100644 index 00000000000..7bbc038c9cc --- /dev/null +++ b/tests/Functional/Uuid/UuidFilterWithRamseyUuidBinaryTest.php @@ -0,0 +1,68 @@ + + * + * 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\Functional\Uuid; + +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Uuid\RamseyUuidBinaryDevice; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Uuid\RamseyUuidBinaryDeviceEndpoint; +use Composer\InstalledVersions; +use Composer\Semver\VersionParser; + +class UuidFilterWithRamseyUuidBinaryTest extends UuidFilterBaseTestCase +{ + protected function setUp(): void + { + if (!InstalledVersions::satisfies(new VersionParser(), 'doctrine/orm', '^3.0.1')) { + $this->markTestSkipped('The "doctrine/orm" package version 3.0.1 or higher is required to use the UuidBinaryFilter'); + } + + parent::setUp(); + } + + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [ + self::getDeviceClass(), + self::getDeviceEndpointClass(), + ]; + } + + /** + * @return class-string + */ + protected static function getDeviceEndpointClass(): string + { + return RamseyUuidBinaryDeviceEndpoint::class; + } + + /** + * @return class-string + */ + protected static function getDeviceClass(): string + { + return RamseyUuidBinaryDevice::class; + } + + public function getUrlPrefix(): string + { + return 'ramsey_uuid_binary'; + } + + public function geTypePrefix(): string + { + return 'RamseyUuidBinary'; + } +} diff --git a/tests/Functional/Uuid/UuidFilterWithRamseyUuidTest.php b/tests/Functional/Uuid/UuidFilterWithRamseyUuidTest.php new file mode 100644 index 00000000000..3482591c475 --- /dev/null +++ b/tests/Functional/Uuid/UuidFilterWithRamseyUuidTest.php @@ -0,0 +1,57 @@ + + * + * 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\Functional\Uuid; + +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Uuid\RamseyUuidDevice; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Uuid\RamseyUuidDeviceEndpoint; + +class UuidFilterWithRamseyUuidTest extends UuidFilterBaseTestCase +{ + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [ + self::getDeviceClass(), + self::getDeviceEndpointClass(), + ]; + } + + /** + * @return class-string + */ + protected static function getDeviceEndpointClass(): string + { + return RamseyUuidDeviceEndpoint::class; + } + + /** + * @return class-string + */ + protected static function getDeviceClass(): string + { + return RamseyUuidDevice::class; + } + + public function getUrlPrefix(): string + { + return 'ramsey_uuid'; + } + + public function geTypePrefix(): string + { + return 'RamseyUuid'; + } +} diff --git a/tests/Functional/Uuid/UuidFilterWithSymfonyUlidTest.php b/tests/Functional/Uuid/UuidFilterWithSymfonyUlidTest.php new file mode 100644 index 00000000000..6ae18b273a2 --- /dev/null +++ b/tests/Functional/Uuid/UuidFilterWithSymfonyUlidTest.php @@ -0,0 +1,103 @@ + + * + * 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\Functional\Uuid; + +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Uuid\SymfonyUlidDevice; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Uuid\SymfonyUlidDeviceEndpoint; + +class UuidFilterWithSymfonyUlidTest extends UuidFilterBaseTestCase +{ + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [ + self::getDeviceClass(), + self::getDeviceEndpointClass(), + ]; + } + + /** + * @return class-string + */ + protected static function getDeviceEndpointClass(): string + { + return SymfonyUlidDeviceEndpoint::class; + } + + /** + * @return class-string + */ + protected static function getDeviceClass(): string + { + return SymfonyUlidDevice::class; + } + + public function getUrlPrefix(): string + { + return 'symfony_ulid'; + } + + public function geTypePrefix(): string + { + return 'SymfonyUlid'; + } + + public function testGetOpenApiDescription(): void + { + $response = self::createClient()->request('GET', '/docs', [ + 'headers' => ['Accept' => 'application/vnd.openapi+json'], + ]); + + $json = $response->toArray(); + + self::assertContains( + [ + 'name' => 'id', + 'in' => 'query', + 'description' => '', + 'required' => false, + 'deprecated' => false, + 'schema' => [ + 'type' => 'string', + 'format' => 'ulid', + ], + 'style' => 'form', + 'explode' => false, + ], + $json['paths']['/'.$this->getUrlPrefix().'_device_endpoints']['get']['parameters'] + ); + + self::assertContains( + [ + 'name' => 'id[]', + 'in' => 'query', + 'description' => 'One or more Ulids', + 'required' => false, + 'deprecated' => false, + 'schema' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'string', + 'format' => 'ulid', + ], + ], + 'style' => 'deepObject', + 'explode' => true, + ], + $json['paths']['/'.$this->getUrlPrefix().'_device_endpoints']['get']['parameters'] + ); + } +} diff --git a/tests/Functional/Uuid/UuidFilterWithSymfonyUuidTest.php b/tests/Functional/Uuid/UuidFilterWithSymfonyUuidTest.php new file mode 100644 index 00000000000..ffcdcaf766e --- /dev/null +++ b/tests/Functional/Uuid/UuidFilterWithSymfonyUuidTest.php @@ -0,0 +1,57 @@ + + * + * 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\Functional\Uuid; + +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Uuid\SymfonyUuidDevice; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Uuid\SymfonyUuidDeviceEndpoint; + +class UuidFilterWithSymfonyUuidTest extends UuidFilterBaseTestCase +{ + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [ + self::getDeviceClass(), + self::getDeviceEndpointClass(), + ]; + } + + /** + * @return class-string + */ + protected static function getDeviceEndpointClass(): string + { + return SymfonyUuidDeviceEndpoint::class; + } + + /** + * @return class-string + */ + protected static function getDeviceClass(): string + { + return SymfonyUuidDevice::class; + } + + public function getUrlPrefix(): string + { + return 'symfony_uuid'; + } + + public function geTypePrefix(): string + { + return 'SymfonyUuid'; + } +} diff --git a/tests/SetupClassResourcesTrait.php b/tests/SetupClassResourcesTrait.php index 0fdcc232511..32f08efc2f9 100644 --- a/tests/SetupClassResourcesTrait.php +++ b/tests/SetupClassResourcesTrait.php @@ -21,7 +21,7 @@ trait SetupClassResourcesTrait public static function setUpBeforeClass(): void { - static::writeResources(self::getResources()); + static::writeResources(static::getResources()); } public static function tearDownAfterClass(): void From 6eef77c4553e4a79d5d4592c1810e0eb1815182d Mon Sep 17 00:00:00 2001 From: aaa2000 Date: Tue, 6 Jan 2026 14:44:40 +0100 Subject: [PATCH 2/4] Throw exception if the value could not converted to database representation --- .../Orm/Filter/AbstractUuidFilter.php | 29 +++++----------- .../Uuid/UuidFilterBaseTestCase.php | 34 +++---------------- 2 files changed, 13 insertions(+), 50 deletions(-) diff --git a/src/Doctrine/Orm/Filter/AbstractUuidFilter.php b/src/Doctrine/Orm/Filter/AbstractUuidFilter.php index 179c7b1bbf9..a18c4da19fc 100644 --- a/src/Doctrine/Orm/Filter/AbstractUuidFilter.php +++ b/src/Doctrine/Orm/Filter/AbstractUuidFilter.php @@ -122,30 +122,19 @@ private function convertValuesToTheDatabaseRepresentation(QueryBuilder $queryBui $doctrineType = Type::getType($doctrineFieldType); $platform = $queryBuilder->getEntityManager()->getConnection()->getDatabasePlatform(); - if (\is_array($value)) { - $databaseValues = []; - foreach ($value as $val) { - try { - $databaseValues[] = $doctrineType->convertToDatabaseValue($val, $platform); - } catch (ConversionException $e) { - $this->logger->notice('Invalid value conversion to database representation', [ - 'exception' => $e, - ]); - } + $convertValue = static function (mixed $value) use ($doctrineType, $platform) { + try { + return $doctrineType->convertToDatabaseValue($value, $platform); + } catch (ConversionException $e) { + throw new InvalidArgumentException(\sprintf('The value "%s" could not be converted to database representation.', $value), previous: $e); } + }; - return $databaseValues; + if (\is_array($value)) { + return array_map($convertValue, $value); } - try { - return $doctrineType->convertToDatabaseValue($value, $platform); - } catch (ConversionException $e) { - $this->logger->notice('Invalid value conversion to database representation', [ - 'exception' => $e, - ]); - - return null; - } + return $convertValue($value); } /** diff --git a/tests/Functional/Uuid/UuidFilterBaseTestCase.php b/tests/Functional/Uuid/UuidFilterBaseTestCase.php index b38702c0266..e2490b4e70e 100644 --- a/tests/Functional/Uuid/UuidFilterBaseTestCase.php +++ b/tests/Functional/Uuid/UuidFilterBaseTestCase.php @@ -16,6 +16,7 @@ use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; use ApiPlatform\Tests\RecreateSchemaTrait; use ApiPlatform\Tests\SetupClassResourcesTrait; +use Symfony\Component\HttpFoundation\Response; abstract class UuidFilterBaseTestCase extends ApiTestCase { @@ -147,16 +148,7 @@ public function testSearchFilterByInvalidUuid(): void ], ]); - self::assertResponseIsSuccessful(); - $json = $response->toArray(); - - self::assertArraySubset(['hydra:totalItems' => 0], $json); - self::assertArraySubset( - [ - 'hydra:member' => [], - ], - $json - ); + $this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST); } public function testSearchFilterByManyInvalidUuid(): void @@ -174,16 +166,7 @@ public function testSearchFilterByManyInvalidUuid(): void ], ]); - self::assertResponseIsSuccessful(); - $json = $response->toArray(); - - self::assertArraySubset(['hydra:totalItems' => 0], $json); - self::assertArraySubset( - [ - 'hydra:member' => [], - ], - $json - ); + $this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST); } public function testSearchFilterOnManyToOneRelationByUuid(): void @@ -288,16 +271,7 @@ public function testSearchFilterOnManyToOneRelationByInvalidUuids(): void ], ]); - self::assertResponseIsSuccessful(); - $json = $response->toArray(); - - self::assertArraySubset(['hydra:totalItems' => 0], $json); - self::assertArraySubset( - [ - 'hydra:member' => [], - ], - $json - ); + $this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST); } public function testGetOpenApiDescription(): void From 0405c886390b78bc07d6fcfe3f7fc7d40db0fb31 Mon Sep 17 00:00:00 2001 From: aaa2000 Date: Tue, 6 Jan 2026 11:31:04 +0100 Subject: [PATCH 3/4] Returns open api parameters according to schema --- .../Orm/Filter/AbstractUuidFilter.php | 50 +++- .../Uuid/UuidFilterWithCustomSchema.php | 67 ++++++ .../Uuid/UuidFilterWithCustomSchemaTest.php | 213 ++++++++++++++++++ 3 files changed, 323 insertions(+), 7 deletions(-) create mode 100644 tests/Fixtures/TestBundle/Entity/Uuid/UuidFilterWithCustomSchema.php create mode 100644 tests/Functional/Uuid/UuidFilterWithCustomSchemaTest.php diff --git a/src/Doctrine/Orm/Filter/AbstractUuidFilter.php b/src/Doctrine/Orm/Filter/AbstractUuidFilter.php index a18c4da19fc..3b32a538c1d 100644 --- a/src/Doctrine/Orm/Filter/AbstractUuidFilter.php +++ b/src/Doctrine/Orm/Filter/AbstractUuidFilter.php @@ -172,16 +172,38 @@ public function getOpenApiParameters(Parameter $parameter): array { $in = $parameter instanceof QueryParameter ? 'query' : 'header'; $key = $parameter->getKey(); + $schema = $parameter->getSchema(); + $addStringParam = false; + $addArrayParam = false; + $openApiParameters = []; + + if (null === $schema) { + $addStringParam = true; + $addArrayParam = true; + } - return [ - new OpenApiParameter( + foreach ($schema['oneOf'] ?? [$schema] as $item) { + if (!isset($item['type']) || 'string' === $item['type']) { + $addStringParam = true; + } + + if (!isset($item['type']) || 'array' === $item['type']) { + $addArrayParam = true; + } + } + + if ($addStringParam) { + $openApiParameters[] = new OpenApiParameter( name: $key, in: $in, schema: self::UUID_SCHEMA, style: 'form', explode: false - ), - new OpenApiParameter( + ); + } + + if ($addArrayParam) { + $openApiParameters[] = new OpenApiParameter( name: $key.'[]', in: $in, description: 'One or more Uuids', @@ -191,12 +213,26 @@ public function getOpenApiParameters(Parameter $parameter): array ], style: 'deepObject', explode: true - ), - ]; + ); + } + + return $openApiParameters; } public function getSchema(Parameter $parameter): array { - return self::UUID_SCHEMA; + if (null !== $parameter->getSchema()) { + return $parameter->getSchema(); + } + + return [ + 'oneOf' => [ + self::UUID_SCHEMA, + [ + 'type' => 'array', + 'items' => self::UUID_SCHEMA, + ], + ], + ]; } } diff --git a/tests/Fixtures/TestBundle/Entity/Uuid/UuidFilterWithCustomSchema.php b/tests/Fixtures/TestBundle/Entity/Uuid/UuidFilterWithCustomSchema.php new file mode 100644 index 00000000000..318fcb88dd1 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Uuid/UuidFilterWithCustomSchema.php @@ -0,0 +1,67 @@ + + * + * 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\Uuid; + +use ApiPlatform\Doctrine\Orm\Filter\UuidFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Metadata\QueryParameter; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Uid\Uuid; + +#[ApiResource(operations: [ + new Get(), + new GetCollection( + parameters: [ + 'id' => new QueryParameter( + filter: new UuidFilter(), + ), + 'idfoo' => new QueryParameter( + schema: ['type' => 'string', 'format' => 'uuid'], + filter: new UuidFilter(), + ), + 'idbar' => new QueryParameter( + schema: ['type' => 'array'], + filter: new UuidFilter(), + ), + 'idquz' => new QueryParameter( + schema: [ + 'oneOf' => [ + ['type' => 'string'], + [ + 'type' => 'array', + 'items' => ['type' => 'string'], + ], + ], + ], + filter: new UuidFilter(), + ), + ] + ), + new Post(), +])] +#[ORM\Entity] +class UuidFilterWithCustomSchema +{ + #[ORM\Id] + #[ORM\Column(type: 'symfony_uuid', unique: true)] + public Uuid $id; + + public function __construct(?Uuid $id = null) + { + $this->id = $id ?? Uuid::v7(); + } +} diff --git a/tests/Functional/Uuid/UuidFilterWithCustomSchemaTest.php b/tests/Functional/Uuid/UuidFilterWithCustomSchemaTest.php new file mode 100644 index 00000000000..6209d49feef --- /dev/null +++ b/tests/Functional/Uuid/UuidFilterWithCustomSchemaTest.php @@ -0,0 +1,213 @@ + + * + * 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\Functional\Uuid; + +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Uuid\UuidFilterWithCustomSchema; +use ApiPlatform\Tests\RecreateSchemaTrait; +use ApiPlatform\Tests\SetupClassResourcesTrait; + +class UuidFilterWithCustomSchemaTest extends ApiTestCase +{ + use RecreateSchemaTrait; + use SetupClassResourcesTrait; + + protected static ?bool $alwaysBootKernel = false; + + protected function setUp(): void + { + parent::setUp(); + + if ($this->isMongoDB()) { + $this->markTestSkipped(); + } + } + + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [ + UuidFilterWithCustomSchema::class, + ]; + } + + public function testGetOpenApiDescriptionWhenNoCustomerSchema(): void + { + $response = self::createClient()->request('GET', '/docs', [ + 'headers' => ['Accept' => 'application/vnd.openapi+json'], + ]); + + $json = $response->toArray(); + + self::assertContains( + [ + 'name' => 'id', + 'in' => 'query', + 'description' => '', + 'required' => false, + 'deprecated' => false, + 'schema' => [ + 'type' => 'string', + 'format' => 'uuid', + ], + 'style' => 'form', + 'explode' => false, + ], + $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters'] + ); + + self::assertContains( + [ + 'name' => 'id[]', + 'in' => 'query', + 'description' => 'One or more Uuids', + 'required' => false, + 'deprecated' => false, + 'schema' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'string', + 'format' => 'uuid', + ], + ], + 'style' => 'deepObject', + 'explode' => true, + ], + $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters'] + ); + } + + public function testGetOpenApiDescriptionWhenSchemaIsOnlyString(): void + { + $response = self::createClient()->request('GET', '/docs', [ + 'headers' => ['Accept' => 'application/vnd.openapi+json'], + ]); + + $json = $response->toArray(); + + self::assertContains( + [ + 'name' => 'idfoo', + 'in' => 'query', + 'description' => '', + 'required' => false, + 'deprecated' => false, + 'schema' => [ + 'type' => 'string', + 'format' => 'uuid', + ], + 'style' => 'form', + 'explode' => false, + ], + $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters'] + ); + + self::assertNotContains( + 'idfoo[]', + array_column($json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters'], 'name') + ); + } + + public function testGetOpenApiDescriptionWhenSchemaIsOnlyArray(): void + { + $response = self::createClient()->request('GET', '/docs', [ + 'headers' => ['Accept' => 'application/vnd.openapi+json'], + ]); + + $json = $response->toArray(); + + self::assertNotContains( + 'idbar', + array_column($json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters'], 'name') + ); + self::assertContains( + [ + 'name' => 'idbar[]', + 'in' => 'query', + 'description' => 'One or more Uuids', + 'required' => false, + 'deprecated' => false, + 'schema' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'string', + 'format' => 'uuid', + ], + ], + 'style' => 'deepObject', + 'explode' => true, + ], + $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters'] + ); + } + + public function testGetOpenApiDescriptionIsOneOfArrayOrString(): void + { + $response = self::createClient()->request('GET', '/docs', [ + 'headers' => ['Accept' => 'application/vnd.openapi+json'], + ]); + + $json = $response->toArray(); + + self::assertContains( + [ + 'name' => 'idquz', + 'in' => 'query', + 'description' => '', + 'required' => false, + 'deprecated' => false, + 'schema' => [ + 'type' => 'string', + 'format' => 'uuid', + ], + 'style' => 'form', + 'explode' => false, + ], + $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters'] + ); + + self::assertContains( + [ + 'name' => 'idquz[]', + 'in' => 'query', + 'description' => 'One or more Uuids', + 'required' => false, + 'deprecated' => false, + 'schema' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'string', + 'format' => 'uuid', + ], + ], + 'style' => 'deepObject', + 'explode' => true, + ], + $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters'] + ); + } + + protected function tearDown(): void + { + if ($this->isMongoDB()) { + return; + } + + $this->recreateSchema(static::getResources()); + + parent::tearDown(); + } +} From 1b68192264556cad24fb6d37e322c71d092e9574 Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 8 Jan 2026 15:08:55 +0100 Subject: [PATCH 4/4] schema/openapi parameter --- .../Orm/Filter/AbstractUuidFilter.php | 71 +++++++++---------- src/Doctrine/Orm/Filter/UlidFilter.php | 58 +++++++++++---- .../Uuid/UuidFilterBaseTestCase.php | 10 +-- .../Uuid/UuidFilterWithCustomSchemaTest.php | 30 ++------ .../Uuid/UuidFilterWithSymfonyUlidTest.php | 10 +-- 5 files changed, 87 insertions(+), 92 deletions(-) diff --git a/src/Doctrine/Orm/Filter/AbstractUuidFilter.php b/src/Doctrine/Orm/Filter/AbstractUuidFilter.php index 3b32a538c1d..b96f3e9bde4 100644 --- a/src/Doctrine/Orm/Filter/AbstractUuidFilter.php +++ b/src/Doctrine/Orm/Filter/AbstractUuidFilter.php @@ -168,61 +168,56 @@ protected function getDoctrineArrayParameterType(): ?ArrayParameterType return null; } - public function getOpenApiParameters(Parameter $parameter): array + public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array|null { $in = $parameter instanceof QueryParameter ? 'query' : 'header'; - $key = $parameter->getKey(); $schema = $parameter->getSchema(); - $addStringParam = false; - $addArrayParam = false; - $openApiParameters = []; + $isArraySchema = 'array' === ($schema['type'] ?? null); + $hasNonArrayType = isset($schema['type']) && 'array' !== $schema['type']; - if (null === $schema) { - $addStringParam = true; - $addArrayParam = true; - } - - foreach ($schema['oneOf'] ?? [$schema] as $item) { - if (!isset($item['type']) || 'string' === $item['type']) { - $addStringParam = true; - } - - if (!isset($item['type']) || 'array' === $item['type']) { - $addArrayParam = true; - } - } + // Get filter's base schema + $baseSchema = self::UUID_SCHEMA; + $arraySchema = ['type' => 'array', 'items' => $baseSchema]; - if ($addStringParam) { - $openApiParameters[] = new OpenApiParameter( - name: $key, + if ($isArraySchema) { + return new OpenApiParameter( + name: $parameter->getKey().'[]', in: $in, - schema: self::UUID_SCHEMA, - style: 'form', - explode: false + schema: $arraySchema, + style: 'deepObject', + explode: true, ); } - if ($addArrayParam) { - $openApiParameters[] = new OpenApiParameter( - name: $key.'[]', + if ($hasNonArrayType) { + return new OpenApiParameter( + name: $parameter->getKey(), in: $in, - description: 'One or more Uuids', - schema: [ - 'type' => 'array', - 'items' => self::UUID_SCHEMA, - ], - style: 'deepObject', - explode: true + schema: $baseSchema, ); } - return $openApiParameters; + // oneOf or no specific type constraint - return both with explicit schemas + return [ + new OpenApiParameter( + name: $parameter->getKey(), + in: $in, + schema: $baseSchema, + ), + new OpenApiParameter( + name: $parameter->getKey().'[]', + in: $in, + schema: $arraySchema, + style: 'deepObject', + explode: true, + ), + ]; } public function getSchema(Parameter $parameter): array { - if (null !== $parameter->getSchema()) { - return $parameter->getSchema(); + if (false === $parameter->getCastToArray()) { + return self::UUID_SCHEMA; } return [ diff --git a/src/Doctrine/Orm/Filter/UlidFilter.php b/src/Doctrine/Orm/Filter/UlidFilter.php index 39ae017b72c..4f54560a836 100644 --- a/src/Doctrine/Orm/Filter/UlidFilter.php +++ b/src/Doctrine/Orm/Filter/UlidFilter.php @@ -24,35 +24,65 @@ final class UlidFilter extends AbstractUuidFilter 'format' => 'ulid', ]; - public function getOpenApiParameters(Parameter $parameter): array + public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array { $in = $parameter instanceof QueryParameter ? 'query' : 'header'; - $key = $parameter->getKey(); + $schema = $parameter->getSchema(); + $isArraySchema = 'array' === ($schema['type'] ?? null); + $hasNonArrayType = isset($schema['type']) && 'array' !== $schema['type']; + $baseSchema = self::ULID_SCHEMA; + $arraySchema = ['type' => 'array', 'items' => $baseSchema]; + + if ($isArraySchema) { + return new OpenApiParameter( + name: $parameter->getKey().'[]', + in: $in, + schema: $arraySchema, + style: 'deepObject', + explode: true, + ); + } + + if ($hasNonArrayType) { + return new OpenApiParameter( + name: $parameter->getKey(), + in: $in, + schema: $baseSchema, + ); + } + + // oneOf or no specific type constraint - return both with explicit schemas return [ new OpenApiParameter( - name: $key, + name: $parameter->getKey(), in: $in, - schema: self::ULID_SCHEMA, - style: 'form', - explode: false + schema: $baseSchema, ), new OpenApiParameter( - name: $key.'[]', + name: $parameter->getKey().'[]', in: $in, - description: 'One or more Ulids', - schema: [ - 'type' => 'array', - 'items' => self::ULID_SCHEMA, - ], + schema: $arraySchema, style: 'deepObject', - explode: true + explode: true, ), ]; } public function getSchema(Parameter $parameter): array { - return self::ULID_SCHEMA; + if (false === $parameter->getCastToArray()) { + return self::ULID_SCHEMA; + } + + return [ + 'oneOf' => [ + self::ULID_SCHEMA, + [ + 'type' => 'array', + 'items' => self::ULID_SCHEMA, + ], + ], + ]; } } diff --git a/tests/Functional/Uuid/UuidFilterBaseTestCase.php b/tests/Functional/Uuid/UuidFilterBaseTestCase.php index e2490b4e70e..c6227b33c9f 100644 --- a/tests/Functional/Uuid/UuidFilterBaseTestCase.php +++ b/tests/Functional/Uuid/UuidFilterBaseTestCase.php @@ -286,26 +286,20 @@ public function testGetOpenApiDescription(): void [ 'name' => 'id', 'in' => 'query', - 'description' => '', 'required' => false, - 'deprecated' => false, 'schema' => [ 'type' => 'string', 'format' => 'uuid', ], - 'style' => 'form', - 'explode' => false, ], - $json['paths']['/'.$this->getUrlPrefix().'_device_endpoints']['get']['parameters'] + array_map(fn ($p) => array_intersect_key($p, ['name' => 1, 'in' => 1, 'required' => 1, 'schema' => 1]), $json['paths']['/'.$this->getUrlPrefix().'_device_endpoints']['get']['parameters']) ); self::assertContains( [ 'name' => 'id[]', 'in' => 'query', - 'description' => 'One or more Uuids', 'required' => false, - 'deprecated' => false, 'schema' => [ 'type' => 'array', 'items' => [ @@ -316,7 +310,7 @@ public function testGetOpenApiDescription(): void 'style' => 'deepObject', 'explode' => true, ], - $json['paths']['/'.$this->getUrlPrefix().'_device_endpoints']['get']['parameters'] + array_map(fn ($p) => array_intersect_key($p, ['name' => 1, 'in' => 1, 'required' => 1, 'schema' => 1, 'style' => 1, 'explode' => 1]), $json['paths']['/'.$this->getUrlPrefix().'_device_endpoints']['get']['parameters']) ); } diff --git a/tests/Functional/Uuid/UuidFilterWithCustomSchemaTest.php b/tests/Functional/Uuid/UuidFilterWithCustomSchemaTest.php index 6209d49feef..22abeaf81c2 100644 --- a/tests/Functional/Uuid/UuidFilterWithCustomSchemaTest.php +++ b/tests/Functional/Uuid/UuidFilterWithCustomSchemaTest.php @@ -56,26 +56,20 @@ public function testGetOpenApiDescriptionWhenNoCustomerSchema(): void [ 'name' => 'id', 'in' => 'query', - 'description' => '', 'required' => false, - 'deprecated' => false, 'schema' => [ 'type' => 'string', 'format' => 'uuid', ], - 'style' => 'form', - 'explode' => false, ], - $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters'] + array_map(fn ($p) => array_intersect_key($p, ['name' => 1, 'in' => 1, 'required' => 1, 'schema' => 1]), $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters']) ); self::assertContains( [ 'name' => 'id[]', 'in' => 'query', - 'description' => 'One or more Uuids', 'required' => false, - 'deprecated' => false, 'schema' => [ 'type' => 'array', 'items' => [ @@ -86,7 +80,7 @@ public function testGetOpenApiDescriptionWhenNoCustomerSchema(): void 'style' => 'deepObject', 'explode' => true, ], - $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters'] + array_map(fn ($p) => array_intersect_key($p, ['name' => 1, 'in' => 1, 'required' => 1, 'schema' => 1, 'style' => 1, 'explode' => 1]), $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters']) ); } @@ -102,17 +96,13 @@ public function testGetOpenApiDescriptionWhenSchemaIsOnlyString(): void [ 'name' => 'idfoo', 'in' => 'query', - 'description' => '', 'required' => false, - 'deprecated' => false, 'schema' => [ 'type' => 'string', 'format' => 'uuid', ], - 'style' => 'form', - 'explode' => false, ], - $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters'] + array_map(fn ($p) => array_intersect_key($p, ['name' => 1, 'in' => 1, 'required' => 1, 'schema' => 1]), $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters']) ); self::assertNotContains( @@ -137,9 +127,7 @@ public function testGetOpenApiDescriptionWhenSchemaIsOnlyArray(): void [ 'name' => 'idbar[]', 'in' => 'query', - 'description' => 'One or more Uuids', 'required' => false, - 'deprecated' => false, 'schema' => [ 'type' => 'array', 'items' => [ @@ -150,7 +138,7 @@ public function testGetOpenApiDescriptionWhenSchemaIsOnlyArray(): void 'style' => 'deepObject', 'explode' => true, ], - $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters'] + array_map(fn ($p) => array_intersect_key($p, ['name' => 1, 'in' => 1, 'required' => 1, 'schema' => 1, 'style' => 1, 'explode' => 1]), $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters']) ); } @@ -166,26 +154,20 @@ public function testGetOpenApiDescriptionIsOneOfArrayOrString(): void [ 'name' => 'idquz', 'in' => 'query', - 'description' => '', 'required' => false, - 'deprecated' => false, 'schema' => [ 'type' => 'string', 'format' => 'uuid', ], - 'style' => 'form', - 'explode' => false, ], - $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters'] + array_map(fn ($p) => array_intersect_key($p, ['name' => 1, 'in' => 1, 'required' => 1, 'schema' => 1]), $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters']) ); self::assertContains( [ 'name' => 'idquz[]', 'in' => 'query', - 'description' => 'One or more Uuids', 'required' => false, - 'deprecated' => false, 'schema' => [ 'type' => 'array', 'items' => [ @@ -196,7 +178,7 @@ public function testGetOpenApiDescriptionIsOneOfArrayOrString(): void 'style' => 'deepObject', 'explode' => true, ], - $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters'] + array_map(fn ($p) => array_intersect_key($p, ['name' => 1, 'in' => 1, 'required' => 1, 'schema' => 1, 'style' => 1, 'explode' => 1]), $json['paths']['/uuid_filter_with_custom_schemas']['get']['parameters']) ); } diff --git a/tests/Functional/Uuid/UuidFilterWithSymfonyUlidTest.php b/tests/Functional/Uuid/UuidFilterWithSymfonyUlidTest.php index 6ae18b273a2..b28b7b70dd1 100644 --- a/tests/Functional/Uuid/UuidFilterWithSymfonyUlidTest.php +++ b/tests/Functional/Uuid/UuidFilterWithSymfonyUlidTest.php @@ -67,26 +67,20 @@ public function testGetOpenApiDescription(): void [ 'name' => 'id', 'in' => 'query', - 'description' => '', 'required' => false, - 'deprecated' => false, 'schema' => [ 'type' => 'string', 'format' => 'ulid', ], - 'style' => 'form', - 'explode' => false, ], - $json['paths']['/'.$this->getUrlPrefix().'_device_endpoints']['get']['parameters'] + array_map(fn ($p) => array_intersect_key($p, ['name' => 1, 'in' => 1, 'required' => 1, 'schema' => 1]), $json['paths']['/'.$this->getUrlPrefix().'_device_endpoints']['get']['parameters']) ); self::assertContains( [ 'name' => 'id[]', 'in' => 'query', - 'description' => 'One or more Ulids', 'required' => false, - 'deprecated' => false, 'schema' => [ 'type' => 'array', 'items' => [ @@ -97,7 +91,7 @@ public function testGetOpenApiDescription(): void 'style' => 'deepObject', 'explode' => true, ], - $json['paths']['/'.$this->getUrlPrefix().'_device_endpoints']['get']['parameters'] + array_map(fn ($p) => array_intersect_key($p, ['name' => 1, 'in' => 1, 'required' => 1, 'schema' => 1, 'style' => 1, 'explode' => 1]), $json['paths']['/'.$this->getUrlPrefix().'_device_endpoints']['get']['parameters']) ); } }