diff --git a/fixtures/Entity/DummyWithEnum.php b/fixtures/Entity/DummyWithEnum.php new file mode 100644 index 000000000..1d5e38835 --- /dev/null +++ b/fixtures/Entity/DummyWithEnum.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Nelmio\Alice\Entity; + +use Nelmio\Alice\Entity\Enum\DummyEnum; + +class DummyWithEnum +{ + private DummyEnum $dummyEnum; + + public function setDummyEnum(DummyEnum $dummyEnum): void + { + $this->dummyEnum = $dummyEnum; + } + + public function getDummyEnum(): DummyEnum + { + return $this->dummyEnum; + } +} diff --git a/fixtures/Entity/Enum/DummyEnum.php b/fixtures/Entity/Enum/DummyEnum.php new file mode 100644 index 000000000..68a322879 --- /dev/null +++ b/fixtures/Entity/Enum/DummyEnum.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 Nelmio\Alice\Entity\Enum; + +enum DummyEnum: string +{ + case Case1 = 'case1'; + case Case2 = 'case2'; +} diff --git a/src/Generator/Hydrator/Property/SymfonyPropertyAccessorHydrator.php b/src/Generator/Hydrator/Property/SymfonyPropertyAccessorHydrator.php index 1a5d47f39..c28a271c6 100644 --- a/src/Generator/Hydrator/Property/SymfonyPropertyAccessorHydrator.php +++ b/src/Generator/Hydrator/Property/SymfonyPropertyAccessorHydrator.php @@ -13,6 +13,7 @@ namespace Nelmio\Alice\Generator\Hydrator\Property; +use function enum_exists; use Nelmio\Alice\Definition\Object\SimpleObject; use Nelmio\Alice\Definition\Property; use Nelmio\Alice\Generator\GenerationContext; @@ -24,6 +25,10 @@ use Nelmio\Alice\Throwable\Exception\Generator\Hydrator\InaccessiblePropertyException; use Nelmio\Alice\Throwable\Exception\Generator\Hydrator\InvalidArgumentException; use Nelmio\Alice\Throwable\Exception\Generator\Hydrator\NoSuchPropertyException; +use ReflectionEnum; +use ReflectionException; +use ReflectionProperty; +use ReflectionType; use Symfony\Component\PropertyAccess\Exception\AccessException as SymfonyAccessException; use Symfony\Component\PropertyAccess\Exception\ExceptionInterface as SymfonyPropertyAccessException; use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException as SymfonyInvalidArgumentException; @@ -62,7 +67,15 @@ public function hydrate(ObjectInterface $object, Property $property, GenerationC } catch (SymfonyAccessException $exception) { throw HydrationExceptionFactory::createForInaccessibleProperty($object, $property, 0, $exception); } catch (SymfonyInvalidArgumentException $exception) { - throw HydrationExceptionFactory::createForInvalidProperty($object, $property, 0, $exception); + // as a fallback check if the property might be an enum + if ( + null !== ($reflectionType = $this->isEnumProperty($instance, $property)) + && null !== $newProperty = $this->castValueToEnum($reflectionType, $property) + ) { + $this->hydrate($object, $newProperty, $context); + } else { + throw HydrationExceptionFactory::createForInvalidProperty($object, $property, 0, $exception); + } } catch (SymfonyPropertyAccessException $exception) { throw HydrationExceptionFactory::create($object, $property, 0, $exception); } catch (TypeError $error) { @@ -71,4 +84,37 @@ public function hydrate(ObjectInterface $object, Property $property, GenerationC return new SimpleObject($object->getId(), $instance); } + + private function isEnumProperty($instance, Property $property): ?ReflectionType + { + try { + $reflectionType = (new ReflectionProperty($instance, $property->getName()))->getType(); + } catch (ReflectionException) { + // property might not exist + return null; + } + + if (null === $reflectionType) { + // might not have a type + return null; + } + + if (!enum_exists($reflectionType->getName())) { + // might not be an enum + return null; + } + + return $reflectionType; + } + + private function castValueToEnum(ReflectionType $reflectionType, Property $property): ?Property + { + foreach ((new ReflectionEnum($reflectionType->getName()))->getCases() as $reflectionCase) { + if ($property->getValue() === $reflectionCase->getValue()->value ?? $reflectionCase->getValue()->name) { + return $property->withValue($reflectionCase->getValue()); + } + } + + return null; + } } diff --git a/tests/Generator/Hydrator/Property/SymfonyPropertyAccessorHydratorTest.php b/tests/Generator/Hydrator/Property/SymfonyPropertyAccessorHydratorTest.php index 74fea955d..6151f6ad7 100644 --- a/tests/Generator/Hydrator/Property/SymfonyPropertyAccessorHydratorTest.php +++ b/tests/Generator/Hydrator/Property/SymfonyPropertyAccessorHydratorTest.php @@ -17,6 +17,8 @@ use Nelmio\Alice\Definition\Property; use Nelmio\Alice\Dummy as NelmioDummy; use Nelmio\Alice\Entity\DummyWithDate; +use Nelmio\Alice\Entity\DummyWithEnum; +use Nelmio\Alice\Entity\Enum\DummyEnum; use Nelmio\Alice\Entity\Hydrator\Dummy; use Nelmio\Alice\Generator\GenerationContext; use Nelmio\Alice\Generator\Hydrator\PropertyHydratorInterface; @@ -49,7 +51,7 @@ class SymfonyPropertyAccessorHydratorTest extends TestCase * @var PropertyAccessorInterface */ private $propertyAccessor; - + protected function setUp(): void { $this->propertyAccessor = new PropertyAccessor(); @@ -148,6 +150,35 @@ public function testThrowsInvalidArgumentExceptionIfInvalidTypeIsGiven(): void } } + public function testReturnsHydratedObjectWithEnum(): void + { + $object = new SimpleObject('dummy', new DummyWithEnum()); + $property = new Property('dummyEnum', 'case1'); + + $result = $this->hydrator->hydrate($object, $property, new GenerationContext()); + + static::assertEquals(DummyEnum::Case1, $result->getInstance()->getDummyEnum()); + } + + public function testThrowsInvalidArgumentExceptionIfEnumCaseIsNotFound(): void + { + try { + $object = new SimpleObject('dummy', new DummyWithEnum()); + $property = new Property('dummyEnum', 'case3'); + + $this->hydrator->hydrate($object, $property, new GenerationContext()); + + static::fail('Expected exception to be thrown.'); + } catch (InvalidArgumentException $exception) { + static::assertEquals( + 'Invalid value given for the property "dummyEnum" of the object "dummy" (class: Nelmio\Alice\Entity\DummyWithEnum).', + $exception->getMessage() + ); + static::assertEquals(0, $exception->getCode()); + static::assertNotNull($exception->getPrevious()); + } + } + public function testCatchesAnySymfonyPropertyAccessorToThrowAnHydratorException(): void { try {