From 22920f89a49d0ebf8a6c0be1ec5cae68f0056901 Mon Sep 17 00:00:00 2001 From: Meron Nagy Date: Tue, 6 May 2025 16:39:05 +0200 Subject: [PATCH 1/2] fix(doctrine): support integer-backed enums in BackedEnumFilter --- src/Doctrine/Common/Filter/BackedEnumFilterTrait.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Doctrine/Common/Filter/BackedEnumFilterTrait.php b/src/Doctrine/Common/Filter/BackedEnumFilterTrait.php index fbc5b0fea6e..409bc50a2d3 100644 --- a/src/Doctrine/Common/Filter/BackedEnumFilterTrait.php +++ b/src/Doctrine/Common/Filter/BackedEnumFilterTrait.php @@ -32,7 +32,7 @@ trait BackedEnumFilterTrait use PropertyHelperTrait; /** - * @var array + * @var array */ private array $enumTypes; @@ -80,6 +80,14 @@ abstract protected function isBackedEnumField(string $property, string $resource private function normalizeValue($value, string $property): mixed { + $firstCase = $this->enumTypes[$property]::cases()[0] ?? null; + if ( + \is_int($firstCase?->value) + && false !== filter_var($value, \FILTER_VALIDATE_INT) + ) { + $value = (int) $value; + } + $values = array_map(fn (\BackedEnum $case) => $case->value, $this->enumTypes[$property]::cases()); if (\in_array($value, $values, true)) { From ad72b23ae79d395c56ef49dfb599c93dad57df15 Mon Sep 17 00:00:00 2001 From: Meron Nagy Date: Fri, 9 May 2025 12:45:00 +0200 Subject: [PATCH 2/2] test(doctrine): add functional tests for BackedEnumFilter covering string- and int-backed enums --- .../Issue7126/DummyForBackedEnumFilter.php | 66 ++++++++++++++ .../Entity/Issue7126/IntegerBackedEnum.php | 20 +++++ .../Entity/Issue7126/StringBackedEnum.php | 20 +++++ tests/Functional/BackedEnumFilterTest.php | 86 +++++++++++++++++++ 4 files changed, 192 insertions(+) create mode 100644 tests/Fixtures/TestBundle/Entity/Issue7126/DummyForBackedEnumFilter.php create mode 100644 tests/Fixtures/TestBundle/Entity/Issue7126/IntegerBackedEnum.php create mode 100644 tests/Fixtures/TestBundle/Entity/Issue7126/StringBackedEnum.php create mode 100644 tests/Functional/BackedEnumFilterTest.php diff --git a/tests/Fixtures/TestBundle/Entity/Issue7126/DummyForBackedEnumFilter.php b/tests/Fixtures/TestBundle/Entity/Issue7126/DummyForBackedEnumFilter.php new file mode 100644 index 00000000000..b60a974e9e0 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Issue7126/DummyForBackedEnumFilter.php @@ -0,0 +1,66 @@ + + * + * 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\Issue7126; + +use ApiPlatform\Doctrine\Orm\Filter\BackedEnumFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\GetCollection; +use Doctrine\ORM\Mapping as ORM; + +#[GetCollection( + uriTemplate: 'backed_enum_filter{._format}', +)] +#[ApiFilter(BackedEnumFilter::class, properties: ['stringBackedEnum', 'integerBackedEnum'])] +#[ORM\Entity] +class DummyForBackedEnumFilter +{ + /** + * @var int The id + */ + #[ORM\Column(type: 'integer')] + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'AUTO')] + private ?int $id = null; + + #[ORM\Column(nullable: true, enumType: StringBackedEnum::class)] + private ?StringBackedEnum $stringBackedEnum = null; + + #[ORM\Column(nullable: true, enumType: IntegerBackedEnum::class)] + private ?IntegerBackedEnum $integerBackedEnum = null; + + public function getId(): ?int + { + return $this->id; + } + + public function getStringBackedEnum(): ?StringBackedEnum + { + return $this->stringBackedEnum; + } + + public function setStringBackedEnum(StringBackedEnum $stringBackedEnum): void + { + $this->stringBackedEnum = $stringBackedEnum; + } + + public function getIntegerBackedEnum(): ?IntegerBackedEnum + { + return $this->integerBackedEnum; + } + + public function setIntegerBackedEnum(IntegerBackedEnum $IntegerBackedEnum): void + { + $this->integerBackedEnum = $IntegerBackedEnum; + } +} diff --git a/tests/Fixtures/TestBundle/Entity/Issue7126/IntegerBackedEnum.php b/tests/Fixtures/TestBundle/Entity/Issue7126/IntegerBackedEnum.php new file mode 100644 index 00000000000..cf2317d1695 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Issue7126/IntegerBackedEnum.php @@ -0,0 +1,20 @@ + + * + * 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\Issue7126; + +enum IntegerBackedEnum: int +{ + case One = 1; + case Two = 2; +} diff --git a/tests/Fixtures/TestBundle/Entity/Issue7126/StringBackedEnum.php b/tests/Fixtures/TestBundle/Entity/Issue7126/StringBackedEnum.php new file mode 100644 index 00000000000..a978cce3066 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Issue7126/StringBackedEnum.php @@ -0,0 +1,20 @@ + + * + * 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\Issue7126; + +enum StringBackedEnum: string +{ + case One = 'one'; + case Two = 'two'; +} diff --git a/tests/Functional/BackedEnumFilterTest.php b/tests/Functional/BackedEnumFilterTest.php new file mode 100644 index 00000000000..224ded4dc49 --- /dev/null +++ b/tests/Functional/BackedEnumFilterTest.php @@ -0,0 +1,86 @@ + + * + * 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; + +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue7126\DummyForBackedEnumFilter; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue7126\IntegerBackedEnum; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue7126\StringBackedEnum; +use ApiPlatform\Tests\RecreateSchemaTrait; +use ApiPlatform\Tests\SetupClassResourcesTrait; + +final class BackedEnumFilterTest extends ApiTestCase +{ + use RecreateSchemaTrait; + use SetupClassResourcesTrait; + + protected static ?bool $alwaysBootKernel = false; + + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [DummyForBackedEnumFilter::class]; + } + + public function testFilterStringBackedEnum(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped(); + } + + $this->recreateSchema($this->getResources()); + $this->loadFixtures(); + $route = 'backed_enum_filter'; + $response = self::createClient()->request('GET', $route.'?stringBackedEnum='.StringBackedEnum::One->value); + $a = $response->toArray(); + $this->assertCount(1, $a['hydra:member']); + $this->assertEquals(StringBackedEnum::One->value, $a['hydra:member'][0]['stringBackedEnum']); + } + + public function testFilterIntegerBackedEnum(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped(); + } + + $this->recreateSchema($this->getResources()); + $this->loadFixtures(); + $route = 'backed_enum_filter'; + $response = self::createClient()->request('GET', $route.'?integerBackedEnum='.IntegerBackedEnum::Two->value); + $a = $response->toArray(); + $this->assertCount(1, $a['hydra:member']); + $this->assertEquals(IntegerBackedEnum::Two->value, $a['hydra:member'][0]['integerBackedEnum']); + } + + public function loadFixtures(): void + { + $container = static::$kernel->getContainer(); + $registry = $container->get('doctrine'); + $manager = $registry->getManager(); + + $dummyOne = new DummyForBackedEnumFilter(); + $dummyOne->setStringBackedEnum(StringBackedEnum::One); + $dummyOne->setIntegerBackedEnum(IntegerBackedEnum::One); + $manager->persist($dummyOne); + + $dummyTwo = new DummyForBackedEnumFilter(); + $dummyTwo->setStringBackedEnum(StringBackedEnum::Two); + $dummyTwo->setIntegerBackedEnum(IntegerBackedEnum::Two); + $manager->persist($dummyTwo); + + $manager->flush(); + } +}