diff --git a/doc/reference/annotations.rst b/doc/reference/annotations.rst index 651e7b5b6..8f1e083bc 100644 --- a/doc/reference/annotations.rst +++ b/doc/reference/annotations.rst @@ -17,6 +17,39 @@ please use the alternative syntax ``#[Groups(groups: ['value' => 'any value here - Some support for unions exists. For unions of primitive types, the system will try to resolve them automatically. For classes that contain union attributes, the ``#[UnionDiscriminator]`` attribute must be used to specify the type of the union. +Enum support +~~~~~~~~~~~~~~ + +Enum support is disabled by default, to enable it run: + +.. code-block :: php + + $builder = SerializerBuilder::create(); + $builder->enableEnumSupport(); + + $serializer = $builder->build(); + + +With the enum support enabled, enums are automatically detected using typed properties typehints. +When typed properties are no available (virtual properties as example), it is necessary to explicitly typehint +the underlying type using the ``#[Type]`` attribute. + +- If the enum is a ``BackedEnum``, the case value will be used for serialization and deserialization by default; +- If the enum is not a ``BackedEnum``, the case name will be used for serialization and deserialization by default; + +Union support +~~~~~~~~~~~~~~ + +Union support is disabled by default, to enable it run: + +.. code-block :: php + + $builder = SerializerBuilder::create(); + $builder->enableUnionSupport(); + + $serializer = $builder->build(); + + Converting your annotations to attributes ----------------------------------------- @@ -958,22 +991,3 @@ Resulting XML: -Enum support -~~~~~~~~~~~~~~ - -Enum support is disabled by default, to enable it run: - -.. code-block :: php - - $builder = SerializerBuilder::create(); - $builder->enableEnumSupport(); - - $serializer = $builder->build(); - - -With the enum support enabled, enums are automatically detected using typed properties typehints. -When typed properties are no available (virtual properties as example), it is necessary to explicitly typehint -the underlying type using the ``#[Type]`` attribute. - -- If the enum is a ``BackedEnum``, the case value will be used for serialization and deserialization by default; -- If the enum is not a ``BackedEnum``, the case name will be used for serialization and deserialization by default; diff --git a/src/Builder/DefaultDriverFactory.php b/src/Builder/DefaultDriverFactory.php index 74ca1d9ed..6eb3dc7d6 100644 --- a/src/Builder/DefaultDriverFactory.php +++ b/src/Builder/DefaultDriverFactory.php @@ -24,25 +24,15 @@ final class DefaultDriverFactory implements DriverFactoryInterface { - /** - * @var ParserInterface - */ - private $typeParser; - - /** - * @var bool - */ - private $enableEnumSupport = false; - - /** - * @var PropertyNamingStrategyInterface - */ - private $propertyNamingStrategy; - - /** - * @var CompilableExpressionEvaluatorInterface - */ - private $expressionEvaluator; + private ParserInterface $typeParser; + + private bool $enableEnumSupport = false; + + private bool $enableUnionSupport = false; + + private PropertyNamingStrategyInterface $propertyNamingStrategy; + + private ?CompilableExpressionEvaluatorInterface $expressionEvaluator; public function __construct(PropertyNamingStrategyInterface $propertyNamingStrategy, ?ParserInterface $typeParser = null, ?CompilableExpressionEvaluatorInterface $expressionEvaluator = null) { @@ -56,6 +46,11 @@ public function enableEnumSupport(bool $enableEnumSupport = true): void $this->enableEnumSupport = $enableEnumSupport; } + public function enableUnionSupport(bool $enableUnionSupport = true): void + { + $this->enableUnionSupport = $enableUnionSupport; + } + public function createDriver(array $metadataDirs, ?Reader $annotationReader = null): DriverInterface { if (PHP_VERSION_ID < 80000 && empty($metadataDirs) && !interface_exists(Reader::class)) { @@ -93,7 +88,7 @@ public function createDriver(array $metadataDirs, ?Reader $annotationReader = nu $driver = new EnumPropertiesDriver($driver); } - $driver = new TypedPropertiesDriver($driver, $this->typeParser); + $driver = new TypedPropertiesDriver($driver, $this->typeParser, [], $this->enableUnionSupport); if (PHP_VERSION_ID >= 80000) { $driver = new DefaultValuePropertyDriver($driver); diff --git a/src/Metadata/Driver/TypedPropertiesDriver.php b/src/Metadata/Driver/TypedPropertiesDriver.php index 5c447a946..f8957c28f 100644 --- a/src/Metadata/Driver/TypedPropertiesDriver.php +++ b/src/Metadata/Driver/TypedPropertiesDriver.php @@ -22,29 +22,24 @@ class TypedPropertiesDriver implements DriverInterface { - /** - * @var DriverInterface - */ - protected $delegate; - - /** - * @var ParserInterface - */ - protected $typeParser; + protected DriverInterface $delegate; + protected ParserInterface $typeParser; /** * @var string[] */ - private $allowList; + private array $allowList; + private bool $allowUnionProperties; /** * @param string[] $allowList */ - public function __construct(DriverInterface $delegate, ?ParserInterface $typeParser = null, array $allowList = []) + public function __construct(DriverInterface $delegate, ?ParserInterface $typeParser = null, array $allowList = [], bool $allowUnionProperties = false) { $this->delegate = $delegate; $this->typeParser = $typeParser ?: new Parser(); $this->allowList = array_merge($allowList, $this->getDefaultWhiteList()); + $this->allowUnionProperties = $allowUnionProperties; } /** @@ -176,6 +171,10 @@ private function shouldTypeHint(?ReflectionType $reflectionType): bool */ private function shouldTypeHintUnion(?ReflectionType $reflectionType) { + if (!$this->allowUnionProperties) { + return false; + } + if (!$reflectionType instanceof \ReflectionUnionType) { return false; } diff --git a/src/SerializerBuilder.php b/src/SerializerBuilder.php index f5d9c5404..07b71c6c9 100644 --- a/src/SerializerBuilder.php +++ b/src/SerializerBuilder.php @@ -91,6 +91,11 @@ final class SerializerBuilder */ private $enableEnumSupport = false; + /** + * @var bool + */ + private $enableUnionSupport = false; + /** * @var bool */ @@ -284,7 +289,7 @@ public function addDefaultHandlers(): self $this->handlerRegistry->registerSubscribingHandler(new EnumHandler()); } - if (PHP_VERSION_ID >= 80000) { + if ($this->enableUnionSupport) { $this->handlerRegistry->registerSubscribingHandler(new UnionHandler()); } @@ -540,6 +545,17 @@ public function enableEnumSupport(bool $enableEnumSupport = true): self return $this; } + public function enableUnionSupport(bool $enableUnionSupport = true): self + { + if ($enableUnionSupport && PHP_VERSION_ID < 80000) { + throw new InvalidArgumentException('Enum support can be enabled only on PHP 8.1 or higher.'); + } + + $this->enableUnionSupport = $enableUnionSupport; + + return $this; + } + public function setMetadataCache(CacheInterface $cache): self { $this->metadataCache = $cache; @@ -569,6 +585,7 @@ public function build(): Serializer $this->expressionEvaluator instanceof CompilableExpressionEvaluatorInterface ? $this->expressionEvaluator : null, ); $this->driverFactory->enableEnumSupport($this->enableEnumSupport); + $this->driverFactory->enableUnionSupport($this->enableUnionSupport); } if ($this->docBlockTyperResolver) { diff --git a/tests/Metadata/Driver/DefaultDriverFactoryTest.php b/tests/Metadata/Driver/DefaultDriverFactoryTest.php index 6f068c875..98aa5abb0 100644 --- a/tests/Metadata/Driver/DefaultDriverFactoryTest.php +++ b/tests/Metadata/Driver/DefaultDriverFactoryTest.php @@ -31,7 +31,7 @@ public function testDefaultDriverFactoryLoadsTypedPropertiesDriver() ]; foreach ($expectedPropertyTypes as $property => $type) { - self::assertEquals(['name' => $type, 'params' => []], $m->propertyMetadata[$property]->type); + self::assertEquals(['name' => $type, 'params' => []], $m->propertyMetadata[$property]->type, 'for ' . $property); } } } diff --git a/tests/Metadata/Driver/TypedPropertiesDriverTest.php b/tests/Metadata/Driver/TypedPropertiesDriverTest.php index 70540d2f1..757a82305 100644 --- a/tests/Metadata/Driver/TypedPropertiesDriverTest.php +++ b/tests/Metadata/Driver/TypedPropertiesDriverTest.php @@ -9,6 +9,7 @@ use JMS\Serializer\Metadata\Driver\AnnotationDriver; use JMS\Serializer\Metadata\Driver\TypedPropertiesDriver; use JMS\Serializer\Naming\IdenticalPropertyNamingStrategy; +use JMS\Serializer\Tests\Fixtures\TypedProperties\UnionTypedProperties; use JMS\Serializer\Tests\Fixtures\TypedProperties\User; use PHPUnit\Framework\TestCase; use ReflectionClass; @@ -33,6 +34,13 @@ public function testInferPropertiesFromTypes() } } + public function testInferUnionTypesShouldResultInManyTypes() + { + $m = $this->resolve(UnionTypedProperties::class); + + self::assertNull($m->propertyMetadata['data']->type); + } + private function resolve(string $classToResolve): ClassMetadata { $baseDriver = new AnnotationDriver(new AnnotationReader(), new IdenticalPropertyNamingStrategy()); diff --git a/tests/Metadata/Driver/UnionTypedPropertiesDriverTest.php b/tests/Metadata/Driver/UnionTypedPropertiesDriverTest.php index d6e0a3168..7aa06d8b7 100644 --- a/tests/Metadata/Driver/UnionTypedPropertiesDriverTest.php +++ b/tests/Metadata/Driver/UnionTypedPropertiesDriverTest.php @@ -95,7 +95,7 @@ private function resolve(string $classToResolve): ClassMetadata new NullDriver($namingStrategy), ]); - $driver = new TypedPropertiesDriver($driver); + $driver = new TypedPropertiesDriver($driver, null, [], true); $m = $driver->loadMetadataForClass(new ReflectionClass($classToResolve)); self::assertNotNull($m); diff --git a/tests/Serializer/BaseSerializationTestCase.php b/tests/Serializer/BaseSerializationTestCase.php index 52626e14e..d804a4437 100644 --- a/tests/Serializer/BaseSerializationTestCase.php +++ b/tests/Serializer/BaseSerializationTestCase.php @@ -2162,6 +2162,10 @@ static function (DeserializationVisitorInterface $visitor, $data, $type, Context $builder->enableEnumSupport(); } + if (PHP_VERSION_ID >= 80000) { + $builder->enableUnionSupport(); + } + $this->extendBuilder($builder); $this->serializer = $builder->build(); }