Skip to content

Commit

Permalink
Disable Union support by default. Add option to enable it optionally
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcin Czarnecki committed Dec 8, 2024
1 parent b4285f4 commit a05e5ae
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 53 deletions.
52 changes: 33 additions & 19 deletions doc/reference/annotations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------------------------------------

Expand Down Expand Up @@ -958,22 +991,3 @@ Resulting XML:
</blog>
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;
35 changes: 15 additions & 20 deletions src/Builder/DefaultDriverFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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)) {
Expand Down Expand Up @@ -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);
Expand Down
21 changes: 10 additions & 11 deletions src/Metadata/Driver/TypedPropertiesDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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;
}
Expand Down
19 changes: 18 additions & 1 deletion src/SerializerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ final class SerializerBuilder
*/
private $enableEnumSupport = false;

/**
* @var bool
*/
private $enableUnionSupport = false;

/**
* @var bool
*/
Expand Down Expand Up @@ -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());
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion tests/Metadata/Driver/DefaultDriverFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
8 changes: 8 additions & 0 deletions tests/Metadata/Driver/TypedPropertiesDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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());
Expand Down
2 changes: 1 addition & 1 deletion tests/Metadata/Driver/UnionTypedPropertiesDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions tests/Serializer/BaseSerializationTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down

0 comments on commit a05e5ae

Please sign in to comment.