[Serializer] Handle circular references
This commit is contained in:
parent
d318e09eef
commit
48491c4441
@ -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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CircularReferenceException
|
||||||
|
*
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class CircularReferenceException extends RuntimeException
|
||||||
|
{
|
||||||
|
}
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Serializer\Normalizer;
|
namespace Symfony\Component\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Exception\CircularReferenceException;
|
||||||
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
|
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
|
||||||
use Symfony\Component\Serializer\Exception\RuntimeException;
|
use Symfony\Component\Serializer\Exception\RuntimeException;
|
||||||
|
|
||||||
@ -33,13 +34,50 @@ use Symfony\Component\Serializer\Exception\RuntimeException;
|
|||||||
* takes place.
|
* takes place.
|
||||||
*
|
*
|
||||||
* @author Nils Adermann <naderman@naderman.de>
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
*/
|
*/
|
||||||
class GetSetMethodNormalizer extends SerializerAwareNormalizer implements NormalizerInterface, DenormalizerInterface
|
class GetSetMethodNormalizer extends SerializerAwareNormalizer implements NormalizerInterface, DenormalizerInterface
|
||||||
{
|
{
|
||||||
|
protected $circularReferenceLimit = 1;
|
||||||
|
protected $circularReferenceHandler;
|
||||||
protected $callbacks = array();
|
protected $callbacks = array();
|
||||||
protected $ignoredAttributes = array();
|
protected $ignoredAttributes = array();
|
||||||
protected $camelizedAttributes = array();
|
protected $camelizedAttributes = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set circular reference limit.
|
||||||
|
*
|
||||||
|
* @param $circularReferenceLimit limit of iterations for the same object
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setCircularReferenceLimit($circularReferenceLimit)
|
||||||
|
{
|
||||||
|
$this->circularReferenceLimit = $circularReferenceLimit;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set circular reference handler.
|
||||||
|
*
|
||||||
|
* @param callable $circularReferenceHandler
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function setCircularReferenceHandler($circularReferenceHandler)
|
||||||
|
{
|
||||||
|
if (!is_callable($circularReferenceHandler)) {
|
||||||
|
throw new InvalidArgumentException('The given circular reference handler is not callable.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->circularReferenceHandler = $circularReferenceHandler;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set normalization callbacks.
|
* Set normalization callbacks.
|
||||||
*
|
*
|
||||||
@ -94,6 +132,24 @@ class GetSetMethodNormalizer extends SerializerAwareNormalizer implements Normal
|
|||||||
*/
|
*/
|
||||||
public function normalize($object, $format = null, array $context = array())
|
public function normalize($object, $format = null, array $context = array())
|
||||||
{
|
{
|
||||||
|
$objectHash = spl_object_hash($object);
|
||||||
|
|
||||||
|
if (isset($context['circular_reference_limit'][$objectHash])) {
|
||||||
|
if ($context['circular_reference_limit'][$objectHash] >= $this->circularReferenceLimit) {
|
||||||
|
unset($context['circular_reference_limit'][$objectHash]);
|
||||||
|
|
||||||
|
if ($this->circularReferenceHandler) {
|
||||||
|
return call_user_func($this->circularReferenceHandler, $object);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new CircularReferenceException(sprintf('A circular reference has been detected (configured limit: %d).', $this->circularReferenceLimit));
|
||||||
|
}
|
||||||
|
|
||||||
|
$context['circular_reference_limit'][$objectHash]++;
|
||||||
|
} else {
|
||||||
|
$context['circular_reference_limit'][$objectHash] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
$reflectionObject = new \ReflectionObject($object);
|
$reflectionObject = new \ReflectionObject($object);
|
||||||
$reflectionMethods = $reflectionObject->getMethods(\ReflectionMethod::IS_PUBLIC);
|
$reflectionMethods = $reflectionObject->getMethods(\ReflectionMethod::IS_PUBLIC);
|
||||||
|
|
||||||
@ -114,7 +170,8 @@ class GetSetMethodNormalizer extends SerializerAwareNormalizer implements Normal
|
|||||||
if (!$this->serializer instanceof NormalizerInterface) {
|
if (!$this->serializer instanceof NormalizerInterface) {
|
||||||
throw new \LogicException(sprintf('Cannot normalize attribute "%s" because injected serializer is not a normalizer', $attributeName));
|
throw new \LogicException(sprintf('Cannot normalize attribute "%s" because injected serializer is not a normalizer', $attributeName));
|
||||||
}
|
}
|
||||||
$attributeValue = $this->serializer->normalize($attributeValue, $format);
|
|
||||||
|
$attributeValue = $this->serializer->normalize($attributeValue, $format, $context);
|
||||||
}
|
}
|
||||||
|
|
||||||
$attributes[$attributeName] = $attributeValue;
|
$attributes[$attributeName] = $attributeValue;
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
<?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 CircularReferenceDummy
|
||||||
|
{
|
||||||
|
public function getMe()
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -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\Fixtures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class SiblingHolder
|
||||||
|
{
|
||||||
|
private $sibling0;
|
||||||
|
private $sibling1;
|
||||||
|
private $sibling2;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$sibling = new Sibling();
|
||||||
|
$this->sibling0 = $sibling;
|
||||||
|
$this->sibling1 = $sibling;
|
||||||
|
$this->sibling2 = $sibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSibling0()
|
||||||
|
{
|
||||||
|
return $this->sibling0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSibling1()
|
||||||
|
{
|
||||||
|
return $this->sibling1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSibling2()
|
||||||
|
{
|
||||||
|
return $this->sibling2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class Sibling
|
||||||
|
{
|
||||||
|
public function getCoopTilleuls()
|
||||||
|
{
|
||||||
|
return 'Les-Tilleuls.coop';
|
||||||
|
}
|
||||||
|
}
|
@ -12,8 +12,11 @@
|
|||||||
namespace Symfony\Component\Serializer\Tests\Normalizer;
|
namespace Symfony\Component\Serializer\Tests\Normalizer;
|
||||||
|
|
||||||
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
|
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
|
||||||
|
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\SiblingHolder;
|
||||||
|
|
||||||
class GetSetMethodNormalizerTest extends \PHPUnit_Framework_TestCase
|
class GetSetMethodNormalizerTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
@ -271,6 +274,49 @@ class GetSetMethodNormalizerTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
$this->normalizer->normalize($obj, 'any');
|
$this->normalizer->normalize($obj, 'any');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException
|
||||||
|
*/
|
||||||
|
public function testUnableToNormalizeCircularReference()
|
||||||
|
{
|
||||||
|
$serializer = new Serializer(array($this->normalizer));
|
||||||
|
$this->normalizer->setSerializer($serializer);
|
||||||
|
$this->normalizer->setCircularReferenceLimit(2);
|
||||||
|
|
||||||
|
$obj = new CircularReferenceDummy();
|
||||||
|
|
||||||
|
$this->normalizer->normalize($obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSiblingReference()
|
||||||
|
{
|
||||||
|
$serializer = new Serializer(array($this->normalizer));
|
||||||
|
$this->normalizer->setSerializer($serializer);
|
||||||
|
|
||||||
|
$siblingHolder = new SiblingHolder();
|
||||||
|
|
||||||
|
$expected = array(
|
||||||
|
'sibling0' => array('coopTilleuls' => 'Les-Tilleuls.coop'),
|
||||||
|
'sibling1' => array('coopTilleuls' => 'Les-Tilleuls.coop'),
|
||||||
|
'sibling2' => array('coopTilleuls' => 'Les-Tilleuls.coop'),
|
||||||
|
);
|
||||||
|
$this->assertEquals($expected, $this->normalizer->normalize($siblingHolder));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCircularReferenceHandler()
|
||||||
|
{
|
||||||
|
$serializer = new Serializer(array($this->normalizer));
|
||||||
|
$this->normalizer->setSerializer($serializer);
|
||||||
|
$this->normalizer->setCircularReferenceHandler(function ($obj) {
|
||||||
|
return get_class($obj);
|
||||||
|
});
|
||||||
|
|
||||||
|
$obj = new CircularReferenceDummy();
|
||||||
|
|
||||||
|
$expected = array('me' => 'Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy');
|
||||||
|
$this->assertEquals($expected, $this->normalizer->normalize($obj));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GetSetDummy
|
class GetSetDummy
|
||||||
|
Reference in New Issue
Block a user