Skip to content

[DON'T MERGE] [Serializer] Add PropertyInfo support in metadata #16893

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
<!-- Class Metadata Factory -->
<service id="serializer.mapping.class_metadata_factory" class="Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory" public="false">
<argument type="service" id="serializer.mapping.chain_loader" />
<argument>null</argument>
<argument>null</argument> <!-- cache -->
<argument type="service" id="property_info" on-invalid="ignore" />
</service>

<!-- Cache -->
Expand Down
31 changes: 30 additions & 1 deletion src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Component\Serializer\Mapping;

use Symfony\Component\PropertyInfo\Type;

/**
* {@inheritdoc}
*
Expand All @@ -36,6 +38,15 @@ class AttributeMetadata implements AttributeMetadataInterface
*/
public $groups = array();

/**
* @var Type|null
*
* @internal This property is public in order to reduce the size of the
* class' serialized representation. Do not access it. Use
* {@link getTypes()} instead.
*/
public $types;

/**
* Constructs a metadata for the given attribute.
*
Expand Down Expand Up @@ -72,6 +83,22 @@ public function getGroups()
return $this->groups;
}

/**
* {@inheritdoc}
*/
public function setTypes(array $types = null)
{
$this->types = $types;
}

/**
* {@inheritdoc}
*/
public function getTypes()
{
return $this->types;
}

/**
* {@inheritdoc}
*/
Expand All @@ -80,6 +107,8 @@ public function merge(AttributeMetadataInterface $attributeMetadata)
foreach ($attributeMetadata->getGroups() as $group) {
$this->addGroup($group);
}

// We don't need to merge types, this is handled by the PropertyInfo component
}

/**
Expand All @@ -89,6 +118,6 @@ public function merge(AttributeMetadataInterface $attributeMetadata)
*/
public function __sleep()
{
return array('name', 'groups');
return array('name', 'groups', 'types');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Component\Serializer\Mapping;

use Symfony\Component\PropertyInfo\Type;

/**
* Stores metadata needed for serializing and deserializing attributes.
*
Expand Down Expand Up @@ -41,6 +43,20 @@ public function addGroup($group);
*/
public function getGroups();

/**
* Sets types of this attribute.
*
* @param Type[]|null $types
*/
public function setTypes(array $types);

/**
* Gets types of this attribute.
*
* @return Type[]|null
*/
public function getTypes();

/**
* Merges an {@see AttributeMetadataInterface} with in the current one.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
namespace Symfony\Component\Serializer\Mapping\Factory;

use Doctrine\Common\Cache\Cache;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
use Symfony\Component\Serializer\Mapping\ClassMetadata;
use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface;

Expand All @@ -27,23 +30,33 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
* @var LoaderInterface
*/
private $loader;

/**
* @var Cache
*/
private $cache;

/**
* @var array
* @var PropertyInfoExtractorInterface
*/
private $loadedClasses;
private $propertyTypeExtractor;

/**
* @var PropertyListExtractorInterface
*/
private $propertyListExtractor;

/**
* @param LoaderInterface $loader
* @param Cache|null $cache
* @var array
*/
public function __construct(LoaderInterface $loader, Cache $cache = null)
private $loadedClasses;

public function __construct(LoaderInterface $loader = null, Cache $cache = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, PropertyListExtractorInterface $propertyListExtractor = null)
{
$this->loader = $loader;
$this->cache = $cache;
$this->propertyTypeExtractor = $propertyTypeExtractor;
$this->propertyListExtractor = $propertyListExtractor;
}

/**
Expand All @@ -69,7 +82,9 @@ public function getMetadataFor($value)
}

$classMetadata = new ClassMetadata($class);
$this->loader->loadClassMetadata($classMetadata);
if ($this->loader) {
$this->loader->loadClassMetadata($classMetadata);
}

$reflectionClass = $classMetadata->getReflectionClass();

Expand All @@ -83,6 +98,34 @@ public function getMetadataFor($value)
$classMetadata->merge($this->getMetadataFor($interface->name));
}

$attributeNames = array();

// Populate types of existing metadata
foreach ($classMetadata->getAttributesMetadata() as $attributeMetadata) {
$attributeName = $attributeMetadata->getName();
$attributeNames[$attributeName] = true;

if ($this->propertyTypeExtractor) {
$attributeMetadata->setTypes($this->propertyTypeExtractor->getTypes($class, $attributeName));
}
}

if ($this->propertyListExtractor) {
// Populate types for not existing metadata
foreach ($this->propertyListExtractor->getProperties($class) as $attributeName) {
if (isset($attributeNames[$attributeName])) {
continue;
}

$attributeMetadata = new AttributeMetadata($attributeName);
if ($this->propertyTypeExtractor) {
$attributeMetadata->setTypes($this->propertyTypeExtractor->getTypes($class, $attributeName));
}

$classMetadata->addAttributeMetadata($attributeMetadata);
}
}

if ($this->cache) {
$this->cache->save($class, $classMetadata);
}
Expand Down
27 changes: 27 additions & 0 deletions src/Symfony/Component/Serializer/Tests/Fixtures/TypeDummy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?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;

/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class TypeDummy
{
/**
* @var string
*/
public $foo;
/**
* @var TypeDummy
*/
public $bar;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\Component\Serializer\Tests\Mapping;

use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\Serializer\Mapping\AttributeMetadata;

/**
Expand All @@ -33,14 +34,26 @@ public function testGetName()
public function testGroups()
{
$attributeMetadata = new AttributeMetadata('group');
$this->assertEquals(array(), $attributeMetadata->getGroups());

$attributeMetadata->addGroup('a');
$attributeMetadata->addGroup('a');
$attributeMetadata->addGroup('b');

$this->assertEquals(array('a', 'b'), $attributeMetadata->getGroups());
}

public function testMerge()
public function testTypes()
{
$attributeMetadata = new AttributeMetadata('type');
$this->assertNull($attributeMetadata->getTypes());

$attributeMetadata->setTypes(array(new Type(Type::BUILTIN_TYPE_STRING)));

$this->assertEquals(array(new Type(Type::BUILTIN_TYPE_STRING)), $attributeMetadata->getTypes());
}

public function testMergeGroups()
{
$attributeMetadata1 = new AttributeMetadata('a1');
$attributeMetadata1->addGroup('a');
Expand All @@ -61,6 +74,8 @@ public function testSerialize()
$attributeMetadata->addGroup('a');
$attributeMetadata->addGroup('b');

$attributeMetadata->setTypes(array(new Type(Type::BUILTIN_TYPE_INT)));

$serialized = serialize($attributeMetadata);
$this->assertEquals($attributeMetadata, unserialize($serialized));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
namespace Symfony\Component\Serializer\Tests\Mapping\Factory;

use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
use Symfony\Component\Serializer\Mapping\ClassMetadata;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\Mapping\Loader\LoaderChain;
Expand Down Expand Up @@ -75,4 +80,22 @@ public function testCacheNotExists()

$this->assertEquals(TestClassMetadataFactory::createClassMetadata(true, true), $metadata);
}

public function testExtractTypes()
{
$factory = new ClassMetadataFactory(null, null, new PhpDocExtractor(), new ReflectionExtractor());

$expectedClassMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\TypeDummy');
$expectedClassMetadata->getReflectionClass();

$foo = new AttributeMetadata('foo');
$foo->setTypes(array(new Type('string')));
$expectedClassMetadata->addAttributeMetadata($foo);

$bar = new AttributeMetadata('bar');
$bar->setTypes(array(new Type('object', false, 'Symfony\Component\Serializer\Tests\Fixtures\TypeDummy')));
$expectedClassMetadata->addAttributeMetadata($bar);

$this->assertEquals($expectedClassMetadata, $factory->getMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\TypeDummy'));
}
}
7 changes: 5 additions & 2 deletions src/Symfony/Component/Serializer/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@
"symfony/yaml": "~2.8|~3.0",
"symfony/config": "~2.8|~3.0",
"symfony/property-access": "~2.8|~3.0",
"symfony/property-info": "~2.8|3.0",
"doctrine/annotations": "~1.0",
"doctrine/cache": "~1.0"
"doctrine/cache": "~1.0",
"phpdocumentor/reflection": "^1.0.7"
},
"suggest": {
"doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.",
"doctrine/cache": "For using the default cached annotation reader and metadata cache.",
"symfony/yaml": "For using the default YAML mapping loader.",
"symfony/config": "For using the XML mapping loader.",
"symfony/property-access": "For using the ObjectNormalizer."
"symfony/property-access": "For using the ObjectNormalizer.",
"symfony/property-info": "For using the ObjectNormalizer."
},
"autoload": {
"psr-4": { "Symfony\\Component\\Serializer\\": "" },
Expand Down