Skip to content

Commit

Permalink
[Serializer] Enabled mapping configuration via attributes.
Browse files Browse the repository at this point in the history
  • Loading branch information
derrabus committed Oct 12, 2020
1 parent 0943ca3 commit 3495429
Show file tree
Hide file tree
Showing 49 changed files with 637 additions and 119 deletions.
20 changes: 15 additions & 5 deletions Annotation/DiscriminatorMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*
* @author Samuel Roze <samuel.roze@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class DiscriminatorMap
{
/**
Expand All @@ -34,20 +35,29 @@ class DiscriminatorMap
private $mapping;

/**
* @param string|array $typeProperty
*
* @throws InvalidArgumentException
*/
public function __construct(array $data)
public function __construct($typeProperty, array $mapping = null)
{
if (empty($data['typeProperty'])) {
if (\is_array($typeProperty)) {
$mapping = $typeProperty['mapping'] ?? null;
$typeProperty = $typeProperty['typeProperty'] ?? null;
} elseif (!\is_string($typeProperty)) {
throw new \TypeError(sprintf('"%s": Argument $typeProperty was expected to be a string or array, got "%s".', __METHOD__, get_debug_type($typeProperty)));
}

if (empty($typeProperty)) {
throw new InvalidArgumentException(sprintf('Parameter "typeProperty" of annotation "%s" cannot be empty.', static::class));
}

if (empty($data['mapping'])) {
if (empty($mapping)) {
throw new InvalidArgumentException(sprintf('Parameter "mapping" of annotation "%s" cannot be empty.', static::class));
}

$this->typeProperty = $data['typeProperty'];
$this->mapping = $data['mapping'];
$this->typeProperty = $typeProperty;
$this->mapping = $mapping;
}

public function getTypeProperty(): string
Expand Down
13 changes: 8 additions & 5 deletions Annotation/Groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
class Groups
{
/**
Expand All @@ -31,20 +32,22 @@ class Groups
/**
* @throws InvalidArgumentException
*/
public function __construct(array $data)
public function __construct(array $groups)
{
if (!isset($data['value']) || !$data['value']) {
if (isset($groups['value'])) {
$groups = (array) $groups['value'];
}
if (empty($groups)) {
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', static::class));
}

$value = (array) $data['value'];
foreach ($value as $group) {
foreach ($groups as $group) {
if (!\is_string($group)) {
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a string or an array of strings.', static::class));
}
}

$this->groups = $value;
$this->groups = $groups;
}

/**
Expand Down
1 change: 1 addition & 0 deletions Annotation/Ignore.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
final class Ignore
{
}
17 changes: 12 additions & 5 deletions Annotation/MaxDepth.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,31 @@
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
class MaxDepth
{
/**
* @var int
*/
private $maxDepth;

public function __construct(array $data)
/**
* @param int|array $maxDepth
*/
public function __construct($maxDepth)
{
if (!isset($data['value'])) {
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', static::class));
if (\is_array($maxDepth)) {
if (!isset($maxDepth['value'])) {
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', static::class));
}
$maxDepth = $maxDepth['value'];
}

if (!\is_int($data['value']) || $data['value'] <= 0) {
if (!\is_int($maxDepth) || $maxDepth <= 0) {
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a positive integer.', static::class));
}

$this->maxDepth = $data['value'];
$this->maxDepth = $maxDepth;
}

public function getMaxDepth()
Expand Down
17 changes: 12 additions & 5 deletions Annotation/SerializedName.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,31 @@
*
* @author Fabien Bourigault <bourigaultfabien@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
final class SerializedName
{
/**
* @var string
*/
private $serializedName;

public function __construct(array $data)
/**
* @param string|array $serializedName
*/
public function __construct($serializedName)
{
if (!isset($data['value'])) {
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', static::class));
if (\is_array($serializedName)) {
if (!isset($serializedName['value'])) {
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', static::class));
}
$serializedName = $serializedName['value'];
}

if (!\is_string($data['value']) || empty($data['value'])) {
if (!\is_string($serializedName) || empty($serializedName)) {
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', static::class));
}

$this->serializedName = $data['value'];
$this->serializedName = $serializedName;
}

public function getSerializedName(): string
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CHANGELOG
* added `UidNormalizer`
* added `FormErrorNormalizer`
* added `MimeMessageNormalizer`
* serializer mapping can be configured using php attributes

5.1.0
-----
Expand Down
45 changes: 41 additions & 4 deletions Mapping/Loader/AnnotationLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,21 @@
* Annotation loader.
*
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Alexander M. Turek <me@derrabus.de>
*/
class AnnotationLoader implements LoaderInterface
{
private const KNOWN_ANNOTATIONS = [
DiscriminatorMap::class => true,
Groups::class => true,
Ignore:: class => true,
MaxDepth::class => true,
SerializedName::class => true,
];

private $reader;

public function __construct(Reader $reader)
public function __construct(Reader $reader = null)
{
$this->reader = $reader;
}
Expand All @@ -47,7 +56,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)

$attributesMetadata = $classMetadata->getAttributesMetadata();

foreach ($this->reader->getClassAnnotations($reflectionClass) as $annotation) {
foreach ($this->loadAnnotations($reflectionClass) as $annotation) {
if ($annotation instanceof DiscriminatorMap) {
$classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping(
$annotation->getTypeProperty(),
Expand All @@ -63,7 +72,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
}

if ($property->getDeclaringClass()->name === $className) {
foreach ($this->reader->getPropertyAnnotations($property) as $annotation) {
foreach ($this->loadAnnotations($property) as $annotation) {
if ($annotation instanceof Groups) {
foreach ($annotation->getGroups() as $group) {
$attributesMetadata[$property->name]->addGroup($group);
Expand Down Expand Up @@ -98,7 +107,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
}
}

foreach ($this->reader->getMethodAnnotations($method) as $annotation) {
foreach ($this->loadAnnotations($method) as $annotation) {
if ($annotation instanceof Groups) {
if (!$accessorOrMutator) {
throw new MappingException(sprintf('Groups on "%s::%s" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
Expand Down Expand Up @@ -129,4 +138,32 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)

return $loaded;
}

/**
* @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflector
*/
public function loadAnnotations(object $reflector): iterable
{
if (\PHP_VERSION_ID >= 80000) {
foreach ($reflector->getAttributes() as $attribute) {
if (self::KNOWN_ANNOTATIONS[$attribute->getName()] ?? false) {
yield $attribute->newInstance();
}
}
}

if (null === $this->reader) {
return;
}

if ($reflector instanceof \ReflectionClass) {
yield from $this->reader->getClassAnnotations($reflector);
}
if ($reflector instanceof \ReflectionMethod) {
yield from $this->reader->getMethodAnnotations($reflector);
}
if ($reflector instanceof \ReflectionProperty) {
yield from $this->reader->getPropertyAnnotations($reflector);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Tests\Fixtures;
namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations;

use Symfony\Component\Serializer\Annotation\DiscriminatorMap;

/**
* @DiscriminatorMap(typeProperty="type", mapping={
* "first"="Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild",
* "second"="Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild",
* "third"="Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyThirdChild",
* "first"="Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild",
* "second"="Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild",
* "third"="Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyThirdChild",
* })
*/
abstract class AbstractDummy
Expand Down
39 changes: 39 additions & 0 deletions Tests/Fixtures/Annotations/AbstractDummyFirstChild.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations;

use Symfony\Component\Serializer\Tests\Fixtures\DummyFirstChildQuux;

class AbstractDummyFirstChild extends AbstractDummy
{
public $bar;

/** @var DummyFirstChildQuux|null */
public $quux;

public function __construct($foo = null, $bar = null)
{
parent::__construct($foo);

$this->bar = $bar;
}

public function getQuux(): ?DummyFirstChildQuux
{
return $this->quux;
}

public function setQuux(DummyFirstChildQuux $quux): void
{
$this->quux = $quux;
}
}
39 changes: 39 additions & 0 deletions Tests/Fixtures/Annotations/AbstractDummySecondChild.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations;

use Symfony\Component\Serializer\Tests\Fixtures\DummySecondChildQuux;

class AbstractDummySecondChild extends AbstractDummy
{
public $baz;

/** @var DummySecondChildQuux|null */
public $quux;

public function __construct($foo = null, $baz = null)
{
parent::__construct($foo);

$this->baz = $baz;
}

public function getQuux(): ?DummySecondChildQuux
{
return $this->quux;
}

public function setQuux(DummySecondChildQuux $quux): void
{
$this->quux = $quux;
}
}
16 changes: 16 additions & 0 deletions Tests/Fixtures/Annotations/AbstractDummyThirdChild.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations;

final class AbstractDummyThirdChild extends AbstractDummyFirstChild
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Tests\Fixtures;
namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations;

use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Tests\Fixtures\GroupDummyInterface;

/**
* @author Kévin Dunglas <dunglas@gmail.com>
Expand Down
Loading

0 comments on commit 3495429

Please sign in to comment.