Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}
}
12 changes: 12 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,17 @@ public function testInferPropertiesFromTypes()
}
}

public function testUnionTypesAreNotResolved()
{
if (PHP_VERSION_ID < 80000) {
$this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class));
}

$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