feature #12092 [Serializer] Serialization groups support (dunglas)
This PR was submitted for the master branch but it was merged into the 2.7 branch instead (closes #12092).
Discussion
----------
[Serializer] Serialization groups support
| Q | A
| ------------- | ---
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| License | MIT
| Doc PR | symfony/symfony-docs#4675
This PR is a first attempt adding serialization groups to the `Serializer` component. Btw, it also add supports of ignored attributes for denormalization (only normalization is currently supported).
Groups support is totally optional and is not enabled by default (in that case, the `Serializer` will have the current behavior). No BC spotted for now.
To use it:
```php
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
class MyObj
{
/**
* @Groups({"group1", "group2"})
*/
public $foo;
/**
* @Groups({"group3"})
*/
public $bar;
}
$obj = new MyObj();
$obj->foo = 'foo';
$obj->bar = 'bar';
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizer = new PropertyNormalizer($classMetadataFactory);
$serializer = new Serializer([$normalizer]);
$data = $serializer->normalize($obj, null, ['groups' => ['group1']]);
// $data = ['foo' => 'foo'];
$obj2 = $serializer->denormalize(['foo' => 'foo', 'bar' => 'bar'], 'MyObj', null, ['groups' => ['group1', 'group3']);
// $obj2 = MyObj(foo: 'foo', bar: 'bar')
```
Some work still need to be done:
- [x] Add XML mapping
- [x] Add YAML mapping
- [x] Refactor tests
The `ClassMetadata` code is largely inspired from the code of the `Validator` component. Duplicated code in `PropertyNormalizer` and `GetSetMethodNormalizer` has been moved in a new `AbstractNormalizer` class.
This PR also make the interface of `PropertyNormalizer` fluent (like the current behavior of `GetSetMethodNormalizer`.
Commits
-------
57a191b
[Serializer] Serialization groups support
This commit is contained in:
commit
64d6ddb870
63
src/Symfony/Component/Serializer/Annotation/Groups.php
Normal file
63
src/Symfony/Component/Serializer/Annotation/Groups.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?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\Annotation;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation class for @Groups().
|
||||||
|
*
|
||||||
|
* @Annotation
|
||||||
|
* @Target({"PROPERTY", "METHOD"})
|
||||||
|
*
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class Groups
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $groups;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $data
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
if (!isset($data['value']) || !$data['value']) {
|
||||||
|
throw new InvalidArgumentException(sprintf("Parameter of annotation '%s' cannot be empty.", get_class($this)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($data['value'])) {
|
||||||
|
throw new InvalidArgumentException(sprintf("Parameter of annotation '%s' must be an array of strings.", get_class($this)));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($data['value'] as $group) {
|
||||||
|
if (!is_string($group)) {
|
||||||
|
throw new InvalidArgumentException(sprintf("Parameter of annotation '%s' must be an array of strings.", get_class($this)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->groups = $data['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets groups
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getGroups()
|
||||||
|
{
|
||||||
|
return $this->groups;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
<?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\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MappingException
|
||||||
|
*
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class MappingException extends RuntimeException
|
||||||
|
{
|
||||||
|
}
|
134
src/Symfony/Component/Serializer/Mapping/ClassMetadata.php
Normal file
134
src/Symfony/Component/Serializer/Mapping/ClassMetadata.php
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
<?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\Mapping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores all metadata needed for serializing objects of specific class.
|
||||||
|
*
|
||||||
|
* Primarily, the metadata stores serialization groups.
|
||||||
|
*
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class ClassMetadata
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*
|
||||||
|
* @internal This property is public in order to reduce the size of the
|
||||||
|
* class' serialized representation. Do not access it. Use
|
||||||
|
* {@link getClassName()} instead.
|
||||||
|
*/
|
||||||
|
public $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*
|
||||||
|
* @internal This property is public in order to reduce the size of the
|
||||||
|
* class' serialized representation. Do not access it. Use
|
||||||
|
* {@link getGroups()} instead.
|
||||||
|
*/
|
||||||
|
public $attributesGroups = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \ReflectionClass
|
||||||
|
*/
|
||||||
|
private $reflClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a metadata for the given class.
|
||||||
|
*
|
||||||
|
* @param string $class
|
||||||
|
*/
|
||||||
|
public function __construct($class)
|
||||||
|
{
|
||||||
|
$this->name = $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the backing PHP class.
|
||||||
|
*
|
||||||
|
* @return string The name of the backing class.
|
||||||
|
*/
|
||||||
|
public function getClassName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets serialization groups.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getAttributesGroups()
|
||||||
|
{
|
||||||
|
return $this->attributesGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an attribute to a serialization group
|
||||||
|
*
|
||||||
|
* @param string $attribute
|
||||||
|
* @param string $group
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function addAttributeGroup($attribute, $group)
|
||||||
|
{
|
||||||
|
if (!is_string($attribute) || !is_string($group)) {
|
||||||
|
throw new \InvalidArgumentException('Arguments must be strings.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->groups[$group]) || !in_array($attribute, $this->attributesGroups[$group])) {
|
||||||
|
$this->attributesGroups[$group][] = $attribute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges attributes' groups.
|
||||||
|
*
|
||||||
|
* @param ClassMetadata $classMetadata
|
||||||
|
*/
|
||||||
|
public function mergeAttributesGroups(ClassMetadata $classMetadata)
|
||||||
|
{
|
||||||
|
foreach ($classMetadata->getAttributesGroups() as $group => $attributes) {
|
||||||
|
foreach ($attributes as $attribute) {
|
||||||
|
$this->addAttributeGroup($attribute, $group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a ReflectionClass instance for this class.
|
||||||
|
*
|
||||||
|
* @return \ReflectionClass
|
||||||
|
*/
|
||||||
|
public function getReflectionClass()
|
||||||
|
{
|
||||||
|
if (!$this->reflClass) {
|
||||||
|
$this->reflClass = new \ReflectionClass($this->getClassName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->reflClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the names of the properties that should be serialized.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function __sleep()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'name',
|
||||||
|
'attributesGroups',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
<?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\Mapping\Factory;
|
||||||
|
|
||||||
|
use Doctrine\Common\Cache\Cache;
|
||||||
|
use Symfony\Component\Serializer\Mapping\ClassMetadata;
|
||||||
|
use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link ClassMetadata}.
|
||||||
|
*
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class ClassMetadataFactory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var LoaderInterface
|
||||||
|
*/
|
||||||
|
private $loader;
|
||||||
|
/**
|
||||||
|
* @var Cache
|
||||||
|
*/
|
||||||
|
private $cache;
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $loadedClasses;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param LoaderInterface $loader
|
||||||
|
* @param Cache|null $cache
|
||||||
|
*/
|
||||||
|
public function __construct(LoaderInterface $loader, Cache $cache = null)
|
||||||
|
{
|
||||||
|
$this->loader = $loader;
|
||||||
|
$this->cache = $cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the method was called with the same class name (or an object of that
|
||||||
|
* class) before, the same metadata instance is returned.
|
||||||
|
*
|
||||||
|
* If the factory was configured with a cache, this method will first look
|
||||||
|
* for an existing metadata instance in the cache. If an existing instance
|
||||||
|
* is found, it will be returned without further ado.
|
||||||
|
*
|
||||||
|
* Otherwise, a new metadata instance is created. If the factory was
|
||||||
|
* configured with a loader, the metadata is passed to the
|
||||||
|
* {@link LoaderInterface::loadClassMetadata()} method for further
|
||||||
|
* configuration. At last, the new object is returned.
|
||||||
|
*
|
||||||
|
* @param string|object $value
|
||||||
|
* @return ClassMetadata
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
|
||||||
|
*/
|
||||||
|
public function getMetadataFor($value)
|
||||||
|
{
|
||||||
|
$class = $this->getClass($value);
|
||||||
|
if (!$class) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('Cannot create metadata for non-objects. Got: %s', gettype($value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->loadedClasses[$class])) {
|
||||||
|
return $this->loadedClasses[$class];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->cache && ($this->loadedClasses[$class] = $this->cache->fetch($class))) {
|
||||||
|
return $this->loadedClasses[$class];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists($class) && !interface_exists($class)) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('The class or interface "%s" does not exist.', $class));
|
||||||
|
}
|
||||||
|
|
||||||
|
$metadata = new ClassMetadata($class);
|
||||||
|
|
||||||
|
$reflClass = $metadata->getReflectionClass();
|
||||||
|
|
||||||
|
// Include constraints from the parent class
|
||||||
|
if ($parent = $reflClass->getParentClass()) {
|
||||||
|
$metadata->mergeAttributesGroups($this->getMetadataFor($parent->name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include constraints from all implemented interfaces
|
||||||
|
foreach ($reflClass->getInterfaces() as $interface) {
|
||||||
|
$metadata->mergeAttributesGroups($this->getMetadataFor($interface->name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->loader) {
|
||||||
|
$this->loader->loadClassMetadata($metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->cache) {
|
||||||
|
$this->cache->save($class, $metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->loadedClasses[$class] = $metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if class has metadata.
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasMetadataFor($value)
|
||||||
|
{
|
||||||
|
$class = $this->getClass($value);
|
||||||
|
|
||||||
|
return class_exists($class) || interface_exists($class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a class name for a given class or instance.
|
||||||
|
*
|
||||||
|
* @param $value
|
||||||
|
* @return string|bool
|
||||||
|
*/
|
||||||
|
private function getClass($value)
|
||||||
|
{
|
||||||
|
if (!is_object($value) && !is_string($value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ltrim(is_object($value) ? get_class($value) : $value, '\\');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
<?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\Mapping\Loader;
|
||||||
|
|
||||||
|
use Doctrine\Common\Annotations\Reader;
|
||||||
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
|
use Symfony\Component\Serializer\Mapping\ClassMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation loader.
|
||||||
|
*
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class AnnotationLoader implements LoaderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Reader
|
||||||
|
*/
|
||||||
|
private $reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Reader $reader
|
||||||
|
*/
|
||||||
|
public function __construct(Reader $reader)
|
||||||
|
{
|
||||||
|
$this->reader = $reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function loadClassMetadata(ClassMetadata $metadata)
|
||||||
|
{
|
||||||
|
$reflClass = $metadata->getReflectionClass();
|
||||||
|
$className = $reflClass->name;
|
||||||
|
$loaded = false;
|
||||||
|
|
||||||
|
foreach ($reflClass->getProperties() as $property) {
|
||||||
|
if ($property->getDeclaringClass()->name == $className) {
|
||||||
|
foreach ($this->reader->getPropertyAnnotations($property) as $groups) {
|
||||||
|
if ($groups instanceof Groups) {
|
||||||
|
foreach ($groups->getGroups() as $group) {
|
||||||
|
$metadata->addAttributeGroup($property->name, $group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$loaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($reflClass->getMethods() as $method) {
|
||||||
|
if ($method->getDeclaringClass()->name == $className) {
|
||||||
|
foreach ($this->reader->getMethodAnnotations($method) as $groups) {
|
||||||
|
if ($groups instanceof Groups) {
|
||||||
|
if (preg_match('/^(get|is)(.+)$/i', $method->name, $matches)) {
|
||||||
|
foreach ($groups->getGroups() as $group) {
|
||||||
|
$metadata->addAttributeGroup(lcfirst($matches[2]), $group);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new \BadMethodCallException(sprintf('Groups on "%s::%s" cannot be added. Groups can only be added on methods beginning with "get" or "is".', $className, $method->name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$loaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $loaded;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
<?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\Mapping\Loader;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Exception\MappingException;
|
||||||
|
|
||||||
|
abstract class FileLoader implements LoaderInterface
|
||||||
|
{
|
||||||
|
protected $file;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param string $file The mapping file to load
|
||||||
|
*
|
||||||
|
* @throws MappingException if the mapping file does not exist
|
||||||
|
* @throws MappingException if the mapping file is not readable
|
||||||
|
*/
|
||||||
|
public function __construct($file)
|
||||||
|
{
|
||||||
|
if (!is_file($file)) {
|
||||||
|
throw new MappingException(sprintf('The mapping file %s does not exist', $file));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_readable($file)) {
|
||||||
|
throw new MappingException(sprintf('The mapping file %s is not readable', $file));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->file = $file;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
<?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\Mapping\Loader;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Exception\MappingException;
|
||||||
|
use Symfony\Component\Serializer\Mapping\ClassMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls multiple LoaderInterface instances in a chain
|
||||||
|
*
|
||||||
|
* This class accepts multiple instances of LoaderInterface to be passed to the
|
||||||
|
* constructor. When loadClassMetadata() is called, the same method is called
|
||||||
|
* in <em>all</em> of these loaders, regardless of whether any of them was
|
||||||
|
* successful or not.
|
||||||
|
*
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*/
|
||||||
|
class LoaderChain implements LoaderInterface
|
||||||
|
{
|
||||||
|
protected $loaders;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts a list of LoaderInterface instances
|
||||||
|
*
|
||||||
|
* @param LoaderInterface[] $loaders An array of LoaderInterface instances
|
||||||
|
*
|
||||||
|
* @throws MappingException If any of the loaders does not implement LoaderInterface
|
||||||
|
*/
|
||||||
|
public function __construct(array $loaders)
|
||||||
|
{
|
||||||
|
foreach ($loaders as $loader) {
|
||||||
|
if (!$loader instanceof LoaderInterface) {
|
||||||
|
throw new MappingException(sprintf('Class %s is expected to implement LoaderInterface', get_class($loader)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->loaders = $loaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function loadClassMetadata(ClassMetadata $metadata)
|
||||||
|
{
|
||||||
|
$success = false;
|
||||||
|
|
||||||
|
foreach ($this->loaders as $loader) {
|
||||||
|
$success = $loader->loadClassMetadata($metadata) || $success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $success;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
<?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\Mapping\Loader;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Mapping\ClassMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads class metadata.
|
||||||
|
*
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
interface LoaderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Load class metadata.
|
||||||
|
*
|
||||||
|
* @param ClassMetadata $metadata A metadata
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function loadClassMetadata(ClassMetadata $metadata);
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
<?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\Mapping\Loader;
|
||||||
|
|
||||||
|
use Symfony\Component\Config\Util\XmlUtils;
|
||||||
|
use Symfony\Component\Serializer\Exception\MappingException;
|
||||||
|
use Symfony\Component\Serializer\Mapping\ClassMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads XML mapping files.
|
||||||
|
*
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class XmlFileLoader extends FileLoader
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* An array of SimpleXMLElement instances.
|
||||||
|
*
|
||||||
|
* @var \SimpleXMLElement[]|null
|
||||||
|
*/
|
||||||
|
private $classes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function loadClassMetadata(ClassMetadata $metadata)
|
||||||
|
{
|
||||||
|
if (null === $this->classes) {
|
||||||
|
$this->classes = array();
|
||||||
|
$xml = $this->parseFile($this->file);
|
||||||
|
|
||||||
|
foreach ($xml->class as $class) {
|
||||||
|
$this->classes[(string) $class['name']] = $class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->classes[$metadata->getClassName()])) {
|
||||||
|
$xml = $this->classes[$metadata->getClassName()];
|
||||||
|
|
||||||
|
foreach ($xml->attribute as $attribute) {
|
||||||
|
foreach ($attribute->group as $group) {
|
||||||
|
$metadata->addAttributeGroup((string) $attribute['name'], (string) $group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a XML File.
|
||||||
|
*
|
||||||
|
* @param string $file Path of file
|
||||||
|
*
|
||||||
|
* @return \SimpleXMLElement
|
||||||
|
*
|
||||||
|
* @throws MappingException
|
||||||
|
*/
|
||||||
|
private function parseFile($file)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$dom = XmlUtils::loadFile($file, __DIR__.'/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new MappingException($e->getMessage(), $e->getCode(), $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return simplexml_import_dom($dom);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
<?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\Mapping\Loader;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Exception\MappingException;
|
||||||
|
use Symfony\Component\Serializer\Mapping\ClassMetadata;
|
||||||
|
use Symfony\Component\Yaml\Parser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* YAML File Loader
|
||||||
|
*
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class YamlFileLoader extends FileLoader
|
||||||
|
{
|
||||||
|
private $yamlParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of YAML class descriptions
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $classes = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function loadClassMetadata(ClassMetadata $metadata)
|
||||||
|
{
|
||||||
|
if (null === $this->classes) {
|
||||||
|
if (!stream_is_local($this->file)) {
|
||||||
|
throw new MappingException(sprintf('This is not a local file "%s".', $this->file));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $this->yamlParser) {
|
||||||
|
$this->yamlParser = new Parser();
|
||||||
|
}
|
||||||
|
|
||||||
|
$classes = $this->yamlParser->parse(file_get_contents($this->file));
|
||||||
|
|
||||||
|
if (empty($classes)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not an array
|
||||||
|
if (!is_array($classes)) {
|
||||||
|
throw new MappingException(sprintf('The file "%s" must contain a YAML array.', $this->file));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->classes = $classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->classes[$metadata->getClassName()])) {
|
||||||
|
$yaml = $this->classes[$metadata->getClassName()];
|
||||||
|
|
||||||
|
if (isset($yaml['attributes']) && is_array($yaml['attributes'])) {
|
||||||
|
foreach ($yaml['attributes'] as $attribute => $data) {
|
||||||
|
if (isset($data['groups'])) {
|
||||||
|
foreach ($data['groups'] as $group) {
|
||||||
|
$metadata->addAttributeGroup($attribute, $group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<xsd:schema xmlns="http://symfony.com/schema/dic/serializer-mapping"
|
||||||
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
|
targetNamespace="http://symfony.com/schema/dic/serializer-mapping"
|
||||||
|
elementFormDefault="qualified">
|
||||||
|
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation><![CDATA[
|
||||||
|
Symfony Serializer Mapping Schema, version 1.0
|
||||||
|
Authors: Kévin Dunglas
|
||||||
|
|
||||||
|
A serializer mapping connects attributes with serialization groups.
|
||||||
|
]]></xsd:documentation>
|
||||||
|
</xsd:annotation>
|
||||||
|
|
||||||
|
<xsd:element name="serializer" type="serializer" />
|
||||||
|
|
||||||
|
<xsd:complexType name="serializer">
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation><![CDATA[
|
||||||
|
The root element of the serializer mapping definition.
|
||||||
|
]]></xsd:documentation>
|
||||||
|
</xsd:annotation>
|
||||||
|
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xsd:element name="class" type="class" />
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
|
||||||
|
<xsd:complexType name="class">
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation><![CDATA[
|
||||||
|
Contains serialization groups for a single class.
|
||||||
|
|
||||||
|
Nested elements may be class property and/or getter definitions.
|
||||||
|
]]></xsd:documentation>
|
||||||
|
</xsd:annotation>
|
||||||
|
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xsd:element name="attribute" type="attribute" minOccurs="0" maxOccurs="unbounded" />
|
||||||
|
</xsd:choice>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
|
||||||
|
<xsd:complexType name="attribute">
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation><![CDATA[
|
||||||
|
Contains serialization groups for a attributes. The name of the attribute should be given in the "name" option.
|
||||||
|
]]></xsd:documentation>
|
||||||
|
</xsd:annotation>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="group" type="xsd:string" maxOccurs="unbounded" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
|
||||||
|
</xsd:schema>
|
@ -0,0 +1,130 @@
|
|||||||
|
<?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\Normalizer;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizer implementation.
|
||||||
|
*
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
abstract class AbstractNormalizer extends SerializerAwareNormalizer implements NormalizerInterface, DenormalizerInterface
|
||||||
|
{
|
||||||
|
protected $classMetadataFactory;
|
||||||
|
protected $callbacks = array();
|
||||||
|
protected $ignoredAttributes = array();
|
||||||
|
protected $camelizedAttributes = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link ClassMetadataFactory} to use.
|
||||||
|
*
|
||||||
|
* @param ClassMetadataFactory $classMetadataFactory
|
||||||
|
*/
|
||||||
|
public function __construct(ClassMetadataFactory $classMetadataFactory = null)
|
||||||
|
{
|
||||||
|
$this->classMetadataFactory = $classMetadataFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set normalization callbacks.
|
||||||
|
*
|
||||||
|
* @param array $callbacks help normalize the result
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException if a non-callable callback is set
|
||||||
|
*/
|
||||||
|
public function setCallbacks(array $callbacks)
|
||||||
|
{
|
||||||
|
foreach ($callbacks as $attribute => $callback) {
|
||||||
|
if (!is_callable($callback)) {
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'The given callback for attribute "%s" is not callable.',
|
||||||
|
$attribute
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->callbacks = $callbacks;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set ignored attributes for normalization and denormalization.
|
||||||
|
*
|
||||||
|
* @param array $ignoredAttributes
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setIgnoredAttributes(array $ignoredAttributes)
|
||||||
|
{
|
||||||
|
$this->ignoredAttributes = $ignoredAttributes;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set attributes to be camelized on denormalize.
|
||||||
|
*
|
||||||
|
* @param array $camelizedAttributes
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setCamelizedAttributes(array $camelizedAttributes)
|
||||||
|
{
|
||||||
|
$this->camelizedAttributes = $camelizedAttributes;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format an attribute name, for example to convert a snake_case name to camelCase.
|
||||||
|
*
|
||||||
|
* @param string $attributeName
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function formatAttribute($attributeName)
|
||||||
|
{
|
||||||
|
if (in_array($attributeName, $this->camelizedAttributes)) {
|
||||||
|
return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) {
|
||||||
|
return ('.' === $match[1] ? '_' : '').strtoupper($match[2]);
|
||||||
|
}, $attributeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $attributeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets attributes to normalize using groups.
|
||||||
|
*
|
||||||
|
* @param string|object $classOrObject
|
||||||
|
* @param array $context
|
||||||
|
* @return array|bool
|
||||||
|
*/
|
||||||
|
protected function getAllowedAttributes($classOrObject, array $context)
|
||||||
|
{
|
||||||
|
if (!$this->classMetadataFactory || !isset($context['groups']) || !is_array($context['groups'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedAttributes = array();
|
||||||
|
foreach ($this->classMetadataFactory->getMetadataFor($classOrObject)->getAttributesGroups() as $group => $attributes) {
|
||||||
|
if (in_array($group, $context['groups'])) {
|
||||||
|
$allowedAttributes = array_merge($allowedAttributes, $attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_unique($allowedAttributes);
|
||||||
|
}
|
||||||
|
}
|
@ -36,13 +36,10 @@ use Symfony\Component\Serializer\Exception\RuntimeException;
|
|||||||
* @author Nils Adermann <naderman@naderman.de>
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
*/
|
*/
|
||||||
class GetSetMethodNormalizer extends SerializerAwareNormalizer implements NormalizerInterface, DenormalizerInterface
|
class GetSetMethodNormalizer extends AbstractNormalizer
|
||||||
{
|
{
|
||||||
protected $circularReferenceLimit = 1;
|
protected $circularReferenceLimit = 1;
|
||||||
protected $circularReferenceHandler;
|
protected $circularReferenceHandler;
|
||||||
protected $callbacks = array();
|
|
||||||
protected $ignoredAttributes = array();
|
|
||||||
protected $camelizedAttributes = array();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set circular reference limit.
|
* Set circular reference limit.
|
||||||
@ -78,55 +75,6 @@ class GetSetMethodNormalizer extends SerializerAwareNormalizer implements Normal
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set normalization callbacks.
|
|
||||||
*
|
|
||||||
* @param callable[] $callbacks help normalize the result
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException if a non-callable callback is set
|
|
||||||
*
|
|
||||||
* @return self
|
|
||||||
*/
|
|
||||||
public function setCallbacks(array $callbacks)
|
|
||||||
{
|
|
||||||
foreach ($callbacks as $attribute => $callback) {
|
|
||||||
if (!is_callable($callback)) {
|
|
||||||
throw new InvalidArgumentException(sprintf('The given callback for attribute "%s" is not callable.', $attribute));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->callbacks = $callbacks;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set ignored attributes for normalization.
|
|
||||||
*
|
|
||||||
* @param array $ignoredAttributes
|
|
||||||
*
|
|
||||||
* @return self
|
|
||||||
*/
|
|
||||||
public function setIgnoredAttributes(array $ignoredAttributes)
|
|
||||||
{
|
|
||||||
$this->ignoredAttributes = $ignoredAttributes;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set attributes to be camelized on denormalize.
|
|
||||||
*
|
|
||||||
* @param array $camelizedAttributes
|
|
||||||
*
|
|
||||||
* @return self
|
|
||||||
*/
|
|
||||||
public function setCamelizedAttributes(array $camelizedAttributes)
|
|
||||||
{
|
|
||||||
$this->camelizedAttributes = $camelizedAttributes;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
@ -152,6 +100,7 @@ class GetSetMethodNormalizer extends SerializerAwareNormalizer implements Normal
|
|||||||
|
|
||||||
$reflectionObject = new \ReflectionObject($object);
|
$reflectionObject = new \ReflectionObject($object);
|
||||||
$reflectionMethods = $reflectionObject->getMethods(\ReflectionMethod::IS_PUBLIC);
|
$reflectionMethods = $reflectionObject->getMethods(\ReflectionMethod::IS_PUBLIC);
|
||||||
|
$allowedAttributes = $this->getAllowedAttributes($object, $context);
|
||||||
|
|
||||||
$attributes = array();
|
$attributes = array();
|
||||||
foreach ($reflectionMethods as $method) {
|
foreach ($reflectionMethods as $method) {
|
||||||
@ -162,6 +111,10 @@ class GetSetMethodNormalizer extends SerializerAwareNormalizer implements Normal
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (false !== $allowedAttributes && !in_array($attributeName, $allowedAttributes)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$attributeValue = $method->invoke($object);
|
$attributeValue = $method->invoke($object);
|
||||||
if (array_key_exists($attributeName, $this->callbacks)) {
|
if (array_key_exists($attributeName, $this->callbacks)) {
|
||||||
$attributeValue = call_user_func($this->callbacks[$attributeName], $attributeValue);
|
$attributeValue = call_user_func($this->callbacks[$attributeName], $attributeValue);
|
||||||
@ -186,6 +139,8 @@ class GetSetMethodNormalizer extends SerializerAwareNormalizer implements Normal
|
|||||||
*/
|
*/
|
||||||
public function denormalize($data, $class, $format = null, array $context = array())
|
public function denormalize($data, $class, $format = null, array $context = array())
|
||||||
{
|
{
|
||||||
|
$allowedAttributes = $this->getAllowedAttributes($class, $context);
|
||||||
|
|
||||||
if (is_array($data) || is_object($data) && $data instanceof \ArrayAccess) {
|
if (is_array($data) || is_object($data) && $data instanceof \ArrayAccess) {
|
||||||
$normalizedData = $data;
|
$normalizedData = $data;
|
||||||
} elseif (is_object($data)) {
|
} elseif (is_object($data)) {
|
||||||
@ -208,7 +163,9 @@ class GetSetMethodNormalizer extends SerializerAwareNormalizer implements Normal
|
|||||||
foreach ($constructorParameters as $constructorParameter) {
|
foreach ($constructorParameters as $constructorParameter) {
|
||||||
$paramName = lcfirst($this->formatAttribute($constructorParameter->name));
|
$paramName = lcfirst($this->formatAttribute($constructorParameter->name));
|
||||||
|
|
||||||
if (isset($normalizedData[$paramName])) {
|
$allowed = $allowedAttributes === false || in_array($paramName, $allowedAttributes);
|
||||||
|
$ignored = in_array($paramName, $this->ignoredAttributes);
|
||||||
|
if ($allowed && !$ignored && isset($normalizedData[$paramName])) {
|
||||||
$params[] = $normalizedData[$paramName];
|
$params[] = $normalizedData[$paramName];
|
||||||
// don't run set for a parameter passed to the constructor
|
// don't run set for a parameter passed to the constructor
|
||||||
unset($normalizedData[$paramName]);
|
unset($normalizedData[$paramName]);
|
||||||
@ -229,38 +186,21 @@ class GetSetMethodNormalizer extends SerializerAwareNormalizer implements Normal
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($normalizedData as $attribute => $value) {
|
foreach ($normalizedData as $attribute => $value) {
|
||||||
$setter = 'set'.$this->formatAttribute($attribute);
|
$allowed = $allowedAttributes === false || in_array($attribute, $allowedAttributes);
|
||||||
|
$ignored = in_array($attribute, $this->ignoredAttributes);
|
||||||
|
|
||||||
if (method_exists($object, $setter)) {
|
if ($allowed && !$ignored) {
|
||||||
$object->$setter($value);
|
$setter = 'set'.$this->formatAttribute($attribute);
|
||||||
|
|
||||||
|
if (method_exists($object, $setter)) {
|
||||||
|
$object->$setter($value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $object;
|
return $object;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Format attribute name to access parameters or methods
|
|
||||||
* As option, if attribute name is found on camelizedAttributes array
|
|
||||||
* returns attribute name in camelcase format.
|
|
||||||
*
|
|
||||||
* @param string $attributeName
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function formatAttribute($attributeName)
|
|
||||||
{
|
|
||||||
if (in_array($attributeName, $this->camelizedAttributes)) {
|
|
||||||
return preg_replace_callback(
|
|
||||||
'/(^|_|\.)+(.)/', function ($match) {
|
|
||||||
return ('.' === $match[1] ? '_' : '').strtoupper($match[2]);
|
|
||||||
}, $attributeName
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $attributeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Serializer\Normalizer;
|
namespace Symfony\Component\Serializer\Normalizer;
|
||||||
|
|
||||||
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
|
|
||||||
use Symfony\Component\Serializer\Exception\RuntimeException;
|
use Symfony\Component\Serializer\Exception\RuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,53 +28,10 @@ use Symfony\Component\Serializer\Exception\RuntimeException;
|
|||||||
* property with the corresponding name exists. If found, the property gets the value.
|
* property with the corresponding name exists. If found, the property gets the value.
|
||||||
*
|
*
|
||||||
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
* @author Matthieu Napoli <matthieu@mnapoli.fr>
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
*/
|
*/
|
||||||
class PropertyNormalizer extends SerializerAwareNormalizer implements NormalizerInterface, DenormalizerInterface
|
class PropertyNormalizer extends AbstractNormalizer
|
||||||
{
|
{
|
||||||
private $callbacks = array();
|
|
||||||
private $ignoredAttributes = array();
|
|
||||||
private $camelizedAttributes = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set normalization callbacks
|
|
||||||
*
|
|
||||||
* @param array $callbacks help normalize the result
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException if a non-callable callback is set
|
|
||||||
*/
|
|
||||||
public function setCallbacks(array $callbacks)
|
|
||||||
{
|
|
||||||
foreach ($callbacks as $attribute => $callback) {
|
|
||||||
if (!is_callable($callback)) {
|
|
||||||
throw new InvalidArgumentException(sprintf(
|
|
||||||
'The given callback for attribute "%s" is not callable.',
|
|
||||||
$attribute
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->callbacks = $callbacks;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set ignored attributes for normalization.
|
|
||||||
*
|
|
||||||
* @param array $ignoredAttributes
|
|
||||||
*/
|
|
||||||
public function setIgnoredAttributes(array $ignoredAttributes)
|
|
||||||
{
|
|
||||||
$this->ignoredAttributes = $ignoredAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set attributes to be camelized on denormalize
|
|
||||||
*
|
|
||||||
* @param array $camelizedAttributes
|
|
||||||
*/
|
|
||||||
public function setCamelizedAttributes(array $camelizedAttributes)
|
|
||||||
{
|
|
||||||
$this->camelizedAttributes = $camelizedAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
@ -83,12 +39,17 @@ class PropertyNormalizer extends SerializerAwareNormalizer implements Normalizer
|
|||||||
{
|
{
|
||||||
$reflectionObject = new \ReflectionObject($object);
|
$reflectionObject = new \ReflectionObject($object);
|
||||||
$attributes = array();
|
$attributes = array();
|
||||||
|
$allowedAttributes = $this->getAllowedAttributes($object, $context);
|
||||||
|
|
||||||
foreach ($reflectionObject->getProperties() as $property) {
|
foreach ($reflectionObject->getProperties() as $property) {
|
||||||
if (in_array($property->name, $this->ignoredAttributes)) {
|
if (in_array($property->name, $this->ignoredAttributes)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (false !== $allowedAttributes && !in_array($property->name, $allowedAttributes)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Override visibility
|
// Override visibility
|
||||||
if (! $property->isPublic()) {
|
if (! $property->isPublic()) {
|
||||||
$property->setAccessible(true);
|
$property->setAccessible(true);
|
||||||
@ -114,6 +75,8 @@ class PropertyNormalizer extends SerializerAwareNormalizer implements Normalizer
|
|||||||
*/
|
*/
|
||||||
public function denormalize($data, $class, $format = null, array $context = array())
|
public function denormalize($data, $class, $format = null, array $context = array())
|
||||||
{
|
{
|
||||||
|
$allowedAttributes = $this->getAllowedAttributes($class, $context);
|
||||||
|
|
||||||
$reflectionClass = new \ReflectionClass($class);
|
$reflectionClass = new \ReflectionClass($class);
|
||||||
$constructor = $reflectionClass->getConstructor();
|
$constructor = $reflectionClass->getConstructor();
|
||||||
|
|
||||||
@ -124,7 +87,9 @@ class PropertyNormalizer extends SerializerAwareNormalizer implements Normalizer
|
|||||||
foreach ($constructorParameters as $constructorParameter) {
|
foreach ($constructorParameters as $constructorParameter) {
|
||||||
$paramName = lcfirst($this->formatAttribute($constructorParameter->name));
|
$paramName = lcfirst($this->formatAttribute($constructorParameter->name));
|
||||||
|
|
||||||
if (isset($data[$paramName])) {
|
$allowed = $allowedAttributes === false || in_array($paramName, $allowedAttributes);
|
||||||
|
$ignored = in_array($paramName, $this->ignoredAttributes);
|
||||||
|
if ($allowed && !$ignored && isset($data[$paramName])) {
|
||||||
$params[] = $data[$paramName];
|
$params[] = $data[$paramName];
|
||||||
// don't run set for a parameter passed to the constructor
|
// don't run set for a parameter passed to the constructor
|
||||||
unset($data[$paramName]);
|
unset($data[$paramName]);
|
||||||
@ -146,7 +111,9 @@ class PropertyNormalizer extends SerializerAwareNormalizer implements Normalizer
|
|||||||
foreach ($data as $propertyName => $value) {
|
foreach ($data as $propertyName => $value) {
|
||||||
$propertyName = lcfirst($this->formatAttribute($propertyName));
|
$propertyName = lcfirst($this->formatAttribute($propertyName));
|
||||||
|
|
||||||
if ($reflectionClass->hasProperty($propertyName)) {
|
$allowed = $allowedAttributes === false || in_array($propertyName, $allowedAttributes);
|
||||||
|
$ignored = in_array($propertyName, $this->ignoredAttributes);
|
||||||
|
if ($allowed && !$ignored && $reflectionClass->hasProperty($propertyName)) {
|
||||||
$property = $reflectionClass->getProperty($propertyName);
|
$property = $reflectionClass->getProperty($propertyName);
|
||||||
|
|
||||||
// Override visibility
|
// Override visibility
|
||||||
@ -177,24 +144,6 @@ class PropertyNormalizer extends SerializerAwareNormalizer implements Normalizer
|
|||||||
return $this->supports($type);
|
return $this->supports($type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Format an attribute name, for example to convert a snake_case name to camelCase.
|
|
||||||
*
|
|
||||||
* @param string $attributeName
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function formatAttribute($attributeName)
|
|
||||||
{
|
|
||||||
if (in_array($attributeName, $this->camelizedAttributes)) {
|
|
||||||
return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) {
|
|
||||||
return ('.' === $match[1] ? '_' : '').strtoupper($match[2]);
|
|
||||||
}, $attributeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $attributeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given class has any non-static property.
|
* Checks if the given class has any non-static property.
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
<?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\Annotation;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class GroupsTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testEmptyGroupsParameter()
|
||||||
|
{
|
||||||
|
new Groups(array('value' => array()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testNotAnArrayGroupsParameter()
|
||||||
|
{
|
||||||
|
new Groups(array('value' => 'coopTilleuls'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testInvalidGroupsParameter()
|
||||||
|
{
|
||||||
|
new Groups(array('value' => array('a', 1, new \stdClass())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGroupsParameters()
|
||||||
|
{
|
||||||
|
$validData = array('a', 'b');
|
||||||
|
|
||||||
|
$groups = new Groups(array('value' => $validData));
|
||||||
|
$this->assertEquals($validData, $groups->getGroups());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class GroupDummy extends GroupDummyParent implements GroupDummyInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Groups({"a"})
|
||||||
|
*/
|
||||||
|
private $foo;
|
||||||
|
/**
|
||||||
|
* @Groups({"b", "c"})
|
||||||
|
*/
|
||||||
|
protected $bar;
|
||||||
|
private $fooBar;
|
||||||
|
private $symfony;
|
||||||
|
|
||||||
|
public function setBar($bar)
|
||||||
|
{
|
||||||
|
$this->bar = $bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBar()
|
||||||
|
{
|
||||||
|
return $this->bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFoo($foo)
|
||||||
|
{
|
||||||
|
$this->foo = $foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFoo()
|
||||||
|
{
|
||||||
|
return $this->foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFooBar($fooBar)
|
||||||
|
{
|
||||||
|
$this->fooBar = $fooBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Groups({"a", "b"})
|
||||||
|
*/
|
||||||
|
public function getFooBar()
|
||||||
|
{
|
||||||
|
return $this->fooBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSymfony($symfony)
|
||||||
|
{
|
||||||
|
$this->symfony = $symfony;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSymfony()
|
||||||
|
{
|
||||||
|
return $this->symfony;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
interface GroupDummyInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Groups({"a"})
|
||||||
|
*/
|
||||||
|
public function getSymfony();
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class GroupDummyParent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Groups({"a"})
|
||||||
|
*/
|
||||||
|
private $kevin;
|
||||||
|
private $coopTilleuls;
|
||||||
|
|
||||||
|
public function setKevin($kevin)
|
||||||
|
{
|
||||||
|
$this->kevin = $kevin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKevin()
|
||||||
|
{
|
||||||
|
return $this->kevin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCoopTilleuls($coopTilleuls)
|
||||||
|
{
|
||||||
|
$this->coopTilleuls = $coopTilleuls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Groups({"a", "b"})
|
||||||
|
*/
|
||||||
|
public function getCoopTilleuls()
|
||||||
|
{
|
||||||
|
return $this->coopTilleuls;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
foo
|
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<serializer xmlns="http://symfony.com/schema/dic/serializer"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://symfony.com/schema/dic/serializer http://symfony.com/schema/dic/constraint-mapping/serializer-1.0.xsd">
|
||||||
|
|
||||||
|
<class name="Symfony\Component\Serializer\Tests\Fixtures\GroupDummy">
|
||||||
|
<attribute name="foo">
|
||||||
|
<group name="group1" />
|
||||||
|
<group name="group2" />
|
||||||
|
</attribute>
|
||||||
|
|
||||||
|
<attribute name="bar">
|
||||||
|
<group name="group2" />
|
||||||
|
</attribute>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</serializer>
|
@ -0,0 +1,6 @@
|
|||||||
|
Symfony\Component\Serializer\Tests\Fixtures\GroupDummy:
|
||||||
|
attributes:
|
||||||
|
foo:
|
||||||
|
groups: ['group1', 'group2']
|
||||||
|
bar:
|
||||||
|
groups: ['group2']
|
@ -0,0 +1,73 @@
|
|||||||
|
<?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\Mapping\Factory;
|
||||||
|
|
||||||
|
use Doctrine\Common\Annotations\AnnotationReader;
|
||||||
|
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
|
||||||
|
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
|
||||||
|
use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory;
|
||||||
|
|
||||||
|
require_once __DIR__.'/../../../Annotation/Groups.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class ClassMetadataFactoryTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testGetMetadataFor()
|
||||||
|
{
|
||||||
|
$factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
|
||||||
|
$metadata = $factory->getMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy');
|
||||||
|
|
||||||
|
$this->assertEquals(TestClassMetadataFactory::createClassMetadata(true, true), $metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasMetadataFor()
|
||||||
|
{
|
||||||
|
$factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
|
||||||
|
$this->assertTrue($factory->hasMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy'));
|
||||||
|
$this->assertTrue($factory->hasMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\GroupDummyParent'));
|
||||||
|
$this->assertTrue($factory->hasMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\GroupDummyInterface'));
|
||||||
|
$this->assertFalse($factory->hasMetadataFor('Dunglas\Entity'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCacheExists()
|
||||||
|
{
|
||||||
|
$cache = $this->getMock('Doctrine\Common\Cache\Cache');
|
||||||
|
$cache
|
||||||
|
->expects($this->once())
|
||||||
|
->method('fetch')
|
||||||
|
->will($this->returnValue('foo'))
|
||||||
|
;
|
||||||
|
|
||||||
|
$factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()), $cache);
|
||||||
|
$this->assertEquals('foo', $factory->getMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCacheNotExists()
|
||||||
|
{
|
||||||
|
$cache = $this->getMock('Doctrine\Common\Cache\Cache');
|
||||||
|
$cache
|
||||||
|
->method('fetch')
|
||||||
|
->will($this->returnValue(false))
|
||||||
|
;
|
||||||
|
|
||||||
|
$cache
|
||||||
|
->method('save')
|
||||||
|
;
|
||||||
|
|
||||||
|
$factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()), $cache);
|
||||||
|
$metadata = $factory->getMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy');
|
||||||
|
|
||||||
|
$this->assertEquals(TestClassMetadataFactory::createClassMetadata(true, true), $metadata);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
<?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\Mapping\Loader;
|
||||||
|
|
||||||
|
use Doctrine\Common\Annotations\AnnotationReader;
|
||||||
|
use Symfony\Component\Serializer\Mapping\ClassMetadata;
|
||||||
|
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
|
||||||
|
use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory;
|
||||||
|
|
||||||
|
require_once __DIR__.'/../../../Annotation/Groups.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class AnnotationLoaderTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testLoadClassMetadataReturnsTrueIfSuccessful()
|
||||||
|
{
|
||||||
|
$loader = new AnnotationLoader(new AnnotationReader());
|
||||||
|
$metadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy');
|
||||||
|
|
||||||
|
$this->assertTrue($loader->loadClassMetadata($metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadClassMetadata()
|
||||||
|
{
|
||||||
|
$loader = new AnnotationLoader(new AnnotationReader());
|
||||||
|
$metadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy');
|
||||||
|
|
||||||
|
$loader->loadClassMetadata($metadata);
|
||||||
|
|
||||||
|
$this->assertEquals(TestClassMetadataFactory::createClassMetadata(), $metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadClassMetadataAndMerge()
|
||||||
|
{
|
||||||
|
$loader = new AnnotationLoader(new AnnotationReader());
|
||||||
|
$metadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy');
|
||||||
|
$parentMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\GroupDummyParent');
|
||||||
|
|
||||||
|
$loader->loadClassMetadata($parentMetadata);
|
||||||
|
$metadata->mergeAttributesGroups($parentMetadata);
|
||||||
|
|
||||||
|
$loader->loadClassMetadata($metadata);
|
||||||
|
|
||||||
|
$this->assertEquals(TestClassMetadataFactory::createClassMetadata(true), $metadata);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
<?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\Mapping\Loader;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
|
||||||
|
use Symfony\Component\Serializer\Mapping\ClassMetadata;
|
||||||
|
use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
private $loader;
|
||||||
|
private $metadata;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->loader = new XmlFileLoader(__DIR__.'/../../Fixtures/serializer.xml');
|
||||||
|
$this->metadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadClassMetadataReturnsTrueIfSuccessful()
|
||||||
|
{
|
||||||
|
$this->assertTrue($this->loader->loadClassMetadata($this->metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadClassMetadata()
|
||||||
|
{
|
||||||
|
$this->loader->loadClassMetadata($this->metadata);
|
||||||
|
|
||||||
|
$this->assertEquals(TestClassMetadataFactory::createXmlCLassMetadata(), $this->metadata);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
<?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\Mapping\Loader;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
|
||||||
|
use Symfony\Component\Serializer\Mapping\ClassMetadata;
|
||||||
|
use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
private $loader;
|
||||||
|
private $metadata;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->loader = new YamlFileLoader(__DIR__.'/../../Fixtures/serializer.yml');
|
||||||
|
$this->metadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadClassMetadataReturnsTrueIfSuccessful()
|
||||||
|
{
|
||||||
|
$this->assertTrue($this->loader->loadClassMetadata($this->metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadClassMetadataReturnsFalseWhenEmpty()
|
||||||
|
{
|
||||||
|
$loader = new YamlFileLoader(__DIR__.'/../../Fixtures/empty-mapping.yml');
|
||||||
|
$this->assertFalse($loader->loadClassMetadata($this->metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Serializer\Exception\MappingException
|
||||||
|
*/
|
||||||
|
public function testLoadClassMetadataReturnsThrowsInvalidMapping()
|
||||||
|
{
|
||||||
|
$loader = new YamlFileLoader(__DIR__.'/../../Fixtures/invalid-mapping.yml');
|
||||||
|
$loader->loadClassMetadata($this->metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadClassMetadata()
|
||||||
|
{
|
||||||
|
$this->loader->loadClassMetadata($this->metadata);
|
||||||
|
|
||||||
|
$this->assertEquals(TestClassMetadataFactory::createXmlCLassMetadata(), $this->metadata);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
<?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\Mapping;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Mapping\ClassMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class TestClassMetadataFactory
|
||||||
|
{
|
||||||
|
public static function createClassMetadata($withParent = false, $withInterface = false)
|
||||||
|
{
|
||||||
|
$expected = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy');
|
||||||
|
|
||||||
|
if ($withParent) {
|
||||||
|
$expected->addAttributeGroup('kevin', 'a');
|
||||||
|
$expected->addAttributeGroup('coopTilleuls', 'a');
|
||||||
|
$expected->addAttributeGroup('coopTilleuls', 'b');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($withInterface) {
|
||||||
|
$expected->addAttributeGroup('symfony', 'a');
|
||||||
|
}
|
||||||
|
|
||||||
|
$expected->addAttributeGroup('foo', 'a');
|
||||||
|
$expected->addAttributeGroup('bar', 'b');
|
||||||
|
$expected->addAttributeGroup('bar', 'c');
|
||||||
|
$expected->addAttributeGroup('fooBar', 'a');
|
||||||
|
$expected->addAttributeGroup('fooBar', 'b');
|
||||||
|
|
||||||
|
// load reflection class so that the comparison passes
|
||||||
|
$expected->getReflectionClass();
|
||||||
|
|
||||||
|
return $expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createXmlCLassMetadata()
|
||||||
|
{
|
||||||
|
$expected = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy');
|
||||||
|
$expected->addAttributeGroup('foo', 'group1');
|
||||||
|
$expected->addAttributeGroup('foo', 'group2');
|
||||||
|
$expected->addAttributeGroup('bar', 'group2');
|
||||||
|
|
||||||
|
return $expected;
|
||||||
|
}
|
||||||
|
}
|
@ -11,12 +11,18 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Serializer\Tests\Normalizer;
|
namespace Symfony\Component\Serializer\Tests\Normalizer;
|
||||||
|
|
||||||
|
use Doctrine\Common\Annotations\AnnotationReader;
|
||||||
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
|
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
|
||||||
use Symfony\Component\Serializer\Serializer;
|
use Symfony\Component\Serializer\Serializer;
|
||||||
use Symfony\Component\Serializer\SerializerInterface;
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy;
|
use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy;
|
||||||
use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder;
|
use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder;
|
||||||
|
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
|
||||||
|
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
|
||||||
|
use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy;
|
||||||
|
|
||||||
|
require_once __DIR__.'/../../Annotation/Groups.php';
|
||||||
|
|
||||||
class GetSetMethodNormalizerTest extends \PHPUnit_Framework_TestCase
|
class GetSetMethodNormalizerTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
@ -24,6 +30,10 @@ class GetSetMethodNormalizerTest extends \PHPUnit_Framework_TestCase
|
|||||||
* @var GetSetMethodNormalizer
|
* @var GetSetMethodNormalizer
|
||||||
*/
|
*/
|
||||||
private $normalizer;
|
private $normalizer;
|
||||||
|
/**
|
||||||
|
* @var SerializerInterface
|
||||||
|
*/
|
||||||
|
private $serializer;
|
||||||
|
|
||||||
protected function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
@ -155,6 +165,64 @@ class GetSetMethodNormalizerTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals('bar', $obj->getBar());
|
$this->assertEquals('bar', $obj->getBar());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGroupsNormalize()
|
||||||
|
{
|
||||||
|
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
|
||||||
|
$this->normalizer = new GetSetMethodNormalizer($classMetadataFactory);
|
||||||
|
$this->normalizer->setSerializer($this->serializer);
|
||||||
|
|
||||||
|
$obj = new GroupDummy();
|
||||||
|
$obj->setFoo('foo');
|
||||||
|
$obj->setBar('bar');
|
||||||
|
$obj->setFooBar('fooBar');
|
||||||
|
$obj->setSymfony('symfony');
|
||||||
|
$obj->setKevin('kevin');
|
||||||
|
$obj->setCoopTilleuls('coopTilleuls');
|
||||||
|
|
||||||
|
$this->assertEquals(array(
|
||||||
|
'bar' => 'bar',
|
||||||
|
), $this->normalizer->normalize($obj, null, array('groups' => array('c'))));
|
||||||
|
|
||||||
|
$this->assertEquals(array(
|
||||||
|
'symfony' => 'symfony',
|
||||||
|
'foo' => 'foo',
|
||||||
|
'fooBar' => 'fooBar',
|
||||||
|
'bar' => 'bar',
|
||||||
|
'kevin' => 'kevin',
|
||||||
|
'coopTilleuls' => 'coopTilleuls',
|
||||||
|
), $this->normalizer->normalize($obj, null, array('groups' => array('a', 'c'))));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGroupsDenormalize()
|
||||||
|
{
|
||||||
|
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
|
||||||
|
$this->normalizer = new GetSetMethodNormalizer($classMetadataFactory);
|
||||||
|
$this->normalizer->setSerializer($this->serializer);
|
||||||
|
|
||||||
|
$obj = new GroupDummy();
|
||||||
|
$obj->setFoo('foo');
|
||||||
|
|
||||||
|
$toNormalize = array('foo' => 'foo', 'bar' => 'bar');
|
||||||
|
|
||||||
|
$normalized = $this->normalizer->denormalize(
|
||||||
|
$toNormalize,
|
||||||
|
'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy',
|
||||||
|
null,
|
||||||
|
array('groups' => array('a'))
|
||||||
|
);
|
||||||
|
$this->assertEquals($obj, $normalized);
|
||||||
|
|
||||||
|
$obj->setBar('bar');
|
||||||
|
|
||||||
|
$normalized = $this->normalizer->denormalize(
|
||||||
|
$toNormalize,
|
||||||
|
'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy',
|
||||||
|
null,
|
||||||
|
array('groups' => array('a', 'b'))
|
||||||
|
);
|
||||||
|
$this->assertEquals($obj, $normalized);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider provideCallbacks
|
* @dataProvider provideCallbacks
|
||||||
*/
|
*/
|
||||||
|
@ -11,7 +11,14 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Serializer\Tests\Normalizer;
|
namespace Symfony\Component\Serializer\Tests\Normalizer;
|
||||||
|
|
||||||
|
use Doctrine\Common\Annotations\AnnotationReader;
|
||||||
|
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
|
||||||
|
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
|
||||||
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
|
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy;
|
||||||
|
|
||||||
|
require_once __DIR__.'/../../Annotation/Groups.php';
|
||||||
|
|
||||||
class PropertyNormalizerTest extends \PHPUnit_Framework_TestCase
|
class PropertyNormalizerTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
@ -19,11 +26,16 @@ class PropertyNormalizerTest extends \PHPUnit_Framework_TestCase
|
|||||||
* @var PropertyNormalizer
|
* @var PropertyNormalizer
|
||||||
*/
|
*/
|
||||||
private $normalizer;
|
private $normalizer;
|
||||||
|
/**
|
||||||
|
* @var SerializerInterface
|
||||||
|
*/
|
||||||
|
private $serializer;
|
||||||
|
|
||||||
protected function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
|
$this->serializer = $this->getMock('Symfony\Component\Serializer\SerializerInterface');
|
||||||
$this->normalizer = new PropertyNormalizer();
|
$this->normalizer = new PropertyNormalizer();
|
||||||
$this->normalizer->setSerializer($this->getMock('Symfony\Component\Serializer\Serializer'));
|
$this->normalizer->setSerializer($this->serializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNormalize()
|
public function testNormalize()
|
||||||
@ -135,6 +147,63 @@ class PropertyNormalizerTest extends \PHPUnit_Framework_TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGroupsNormalize()
|
||||||
|
{
|
||||||
|
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
|
||||||
|
$this->normalizer = new PropertyNormalizer($classMetadataFactory);
|
||||||
|
$this->normalizer->setSerializer($this->serializer);
|
||||||
|
|
||||||
|
$obj = new GroupDummy();
|
||||||
|
$obj->setFoo('foo');
|
||||||
|
$obj->setBar('bar');
|
||||||
|
$obj->setFooBar('fooBar');
|
||||||
|
$obj->setSymfony('symfony');
|
||||||
|
$obj->setKevin('kevin');
|
||||||
|
$obj->setCoopTilleuls('coopTilleuls');
|
||||||
|
|
||||||
|
$this->assertEquals(array(
|
||||||
|
'bar' => 'bar',
|
||||||
|
), $this->normalizer->normalize($obj, null, array('groups' => array('c'))));
|
||||||
|
|
||||||
|
// The PropertyNormalizer is not able to hydrate properties from parent classes
|
||||||
|
$this->assertEquals(array(
|
||||||
|
'symfony' => 'symfony',
|
||||||
|
'foo' => 'foo',
|
||||||
|
'fooBar' => 'fooBar',
|
||||||
|
'bar' => 'bar',
|
||||||
|
), $this->normalizer->normalize($obj, null, array('groups' => array('a', 'c'))));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGroupsDenormalize()
|
||||||
|
{
|
||||||
|
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
|
||||||
|
$this->normalizer = new PropertyNormalizer($classMetadataFactory);
|
||||||
|
$this->normalizer->setSerializer($this->serializer);
|
||||||
|
|
||||||
|
$obj = new GroupDummy();
|
||||||
|
$obj->setFoo('foo');
|
||||||
|
|
||||||
|
$toNormalize = array('foo' => 'foo', 'bar' => 'bar');
|
||||||
|
|
||||||
|
$normalized = $this->normalizer->denormalize(
|
||||||
|
$toNormalize,
|
||||||
|
'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy',
|
||||||
|
null,
|
||||||
|
array('groups' => array('a'))
|
||||||
|
);
|
||||||
|
$this->assertEquals($obj, $normalized);
|
||||||
|
|
||||||
|
$obj->setBar('bar');
|
||||||
|
|
||||||
|
$normalized = $this->normalizer->denormalize(
|
||||||
|
$toNormalize,
|
||||||
|
'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy',
|
||||||
|
null,
|
||||||
|
array('groups' => array('a', 'b'))
|
||||||
|
);
|
||||||
|
$this->assertEquals($obj, $normalized);
|
||||||
|
}
|
||||||
|
|
||||||
public function provideCallbacks()
|
public function provideCallbacks()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
|
@ -18,6 +18,18 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.3.3"
|
"php": ">=5.3.3"
|
||||||
},
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/yaml": "~2.0",
|
||||||
|
"symfony/config": "~2.2",
|
||||||
|
"doctrine/annotations": "~1.0",
|
||||||
|
"doctrine/cache": "~1.0"
|
||||||
|
},
|
||||||
|
"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."
|
||||||
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-0": { "Symfony\\Component\\Serializer\\": "" }
|
"psr-0": { "Symfony\\Component\\Serializer\\": "" }
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user