8000 [Serializer] Add a NormalizerDumper by GuilhemN · Pull Request #22051 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Serializer] Add a NormalizerDumper #22051

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 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Merge branch 'master' into SERIALIZERAST
  • Loading branch information
GuilhemN committed Nov 3, 2018
commit 47adb721e96bdc052d360e9753296df46a436245
7 changes: 4 additions & 3 deletions src/Symfony/Bundle/FrameworkBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"symfony/process": "~3.4|~4.0",
"symfony/security-core": "~3.4|~4.0",
"symfony/security-csrf": "~3.4|~4.0",
"symfony/serializer": "~4.1",
"symfony/serializer": "^4.2",
"symfony/stopwatch": "~3.4|~4.0",
"symfony/translation": "~4.2",
"symfony/templating": "~3.4|~4.0",
Expand All @@ -66,8 +66,9 @@
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
"symfony/asset": "<3.4",
"symfony/console": "<3.4",
"symfony/form": "<3.4",
"symfony/property-info": "<4.1",
"symfony/form": "<4.2",
"symfony/messenger": "<4.2",
"symfony/property-info": "<3.4",
"symfony/serializer": "<4.1",
"symfony/stopwatch": "<3.4",
"symfony/translation": "<4.2",
Expand Down
15 changes: 13 additions & 2 deletions src/Symfony/Component/Serializer/Dumper/NormalizerDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,17 @@ public function dump(string $class, array $context = array())
*/
class {$context['class']} implements NormalizerInterface, NormalizerAwareInterface
{
protected \$defaultContext = array(
ObjectNormalizer::CIRCULAR_REFERENCE_LIMIT => 1,
);

use CircularReferenceTrait, NormalizerAwareTrait;

public function __construct(array \$defaultContext)
{
\$this->defaultContext = array_merge(\$this->defaultContext, \$defaultContext);
}

{$this->generateNormalizeMethod($reflectionClass)}

{$this->generateSupportsNormalizationMethod($reflectionClass)}
Expand Down Expand Up @@ -104,9 +113,10 @@ private function generateNormalizeMethodInner(\ReflectionClass $reflectionClass)
}

if ($maxDepthCode) {
$maxDepthKey = ObjectNormalizer::ENABLE_MAX_DEPTH;
$code .= <<<EOL

if (isset(\$context[ObjectNormalizer::ENABLE_MAX_DEPTH])) {{$maxDepthCode}
if (\$context[$maxDepthKey] ?? \$this->defaultContext[$maxDepthKey]) {{$maxDepthCode}
}

EOL;
Expand All @@ -115,6 +125,7 @@ private function generateNormalizeMethodInner(\ReflectionClass $reflectionClass)
foreach ($attributesMetadata as $attributeMetadata) {
$code .= <<<EOL

\$attributes = \$context['attributes'] ?? $this->defaultContext['attributes'] ?? null
if ((null === \$groups
EOL;

Expand All @@ -123,7 +134,7 @@ private function generateNormalizeMethodInner(\ReflectionClass $reflectionClass)
}
$code .= ')';

$code .= " && (!isset(\$context['attributes']) || isset(\$context['attributes']['{$attributeMetadata->name}']) || (is_array(\$context['attributes']) && in_array('{$attributeMetadata->name}', \$context['attributes'], true)))";
$code .= " && (isset(\$attributes['{$attributeMetadata->name}']) || (is_array(\$attributes) && in_array('{$attributeMetadata->name}', \$attributes, true)))";

if (null !== $maxDepth = $attributeMetadata->getMaxDepth()) {
$key = sprintf(ObjectNormalizer::DEPTH_KEY_PATTERN, $reflectionClass->name, $attributeMetadata->name);
Expand Down
66 changes: 59 additions & 7 deletions src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Symfony\Component\Serializer\Normalizer;

use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
use Symfony\Component\Serializer\Exception\CircularReferenceException;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
Expand Down Expand Up @@ -43,6 +43,17 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
const CIRCULAR_REFERENCE_HANDLER = 'circular_reference_handler';
const IGNORED_ATTRIBUTES = 'ignored_attributes';

/**
* @internal
*/
const CIRCULAR_REFERENCE_LIMIT_COUNTERS = 'circular_reference_limit_counters';

protected $defaultContext = array(
self::ALLOW_EXTRA_ATTRIBUTES => true,
self::CIRCULAR_REFERENCE_LIMIT => 1,
self::IGNORED_ATTRIBUTES => array(),
);

/**
* @var ClassMetadataFactoryInterface|null
*/
Expand Down Expand Up @@ -87,7 +98,45 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory
}

/**
* Set normalization callbacks.
* Sets circular reference limit.
*
* @deprecated since Symfony 4.2
*
* @param int $circularReferenceLimit Limit of iterations for the same object
*
* @return self
*/
public function setCircularReferenceLimit($circularReferenceLimit)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "circular_reference_limit" key of the context instead.', __METHOD__), E_USER_DEPRECATED);

$this->defaultContext[self::CIRCULAR_REFERENCE_LIMIT] = $this->circularReferenceLimit = $circularReferenceLimit;

return $this;
}

/**
* Sets circular reference handler.
*
* @deprecated since Symfony 4.2
*
* @param callable $circularReferenceHandler
*
* @return self
*/
public function setCircularReferenceHandler(callable $circularReferenceHandler)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "circular_reference_handler" key of the context instead.', __METHOD__), E_USER_DEPRECATED);

$this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER] = $this->circularReferenceHandler = $circularReferenceHandler;

return $this;
}

/**
* Sets normalization callbacks.
*
* @deprecated since Symfony 4.2
*
* @param callable[] $callbacks Help normalize the result
*
Expand Down Expand Up @@ -125,6 +174,14 @@ public function setIgnoredAttributes(array $ignoredAttributes)
return $this;
}

/**
* {@inheritdoc}
*/
public function hasCacheableSupportsMethod(): bool
{
return false;
}

/**
* Gets attributes to normalize using groups.
*
Expand Down Expand Up @@ -340,9 +397,4 @@ protected function createChildContext(array $parentContext, $attribute)

return $parentContext;
}

private function getCircularReferenceLimitField()
{
return static::CIRCULAR_REFERENCE_LIMIT;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,42 +23,16 @@
trait CircularReferenceTrait
{
/**
* @var int
* @deprecated since Symfony 4.2
*/
protected $circularReferenceLimit = 1;

/**
* @var callable
*/
protected $circularReferenceHandler;

/**
* Set circular reference limit.
*
* @param int $circularReferenceLimit Limit of iterations for the same object
*
* @return self
*/
public function setCircularReferenceLimit($circularReferenceLimit)
{
$this->circularReferenceLimit = $circularReferenceLimit;

return $this;
}

/**
F438 * Set circular reference handler.
* @deprecated since Symfony 4.2
*
* @param callable $circularReferenceHandler
*
* @return self
* @var callable|null
*/
public function setCircularReferenceHandler(callable $circularReferenceHandler)
{
$this->circularReferenceHandler = $circularReferenceHandler;

return $this;
}
protected $circularReferenceHandler;

/**
* Detects if the configured circular reference limit is reached.
Expand All @@ -74,17 +48,17 @@ protected function isCircularReference($object, &$context)
{
$objectHash = spl_object_hash($object);

$circularReferenceLimitField = $this->getCircularReferenceLimitField();
if (isset($context[$circularReferenceLimitField][$objectHash])) {
if ($context[$circularReferenceLimitField][$objectHash] >= $this->circularReferenceLimit) {
unset($context[$circularReferenceLimitField][$objectHash]);
$circularReferenceLimit = $context[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT] ?? $this->defaultContext[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT] ?? $this->circularReferenceLimit;
if (isset($context[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash])) {
if ($context[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash] >= $circularReferenceLimit) {
unset($context[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash]);

return true;
}

++$context[$circularReferenceLimitField][$objectHash];
++$context[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash];
} else {
$context[$circularReferenceLimitField][$objectHash] = 1;
$context[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash] = 1;
}

return false;
Expand All @@ -96,23 +70,29 @@ protected function isCircularReference($object, &$context)
* If a circular reference handler is set, it will be called. Otherwise, a
* {@class CircularReferenceException} will be thrown.
*
* @param object $object
* @final since Symfony 4.2
*
* @param object $object
* @param string|null $format
* @param array $context
*
* @return mixed
*
* @throws CircularReferenceException
*/
protected function handleCircularReference($object)
protected function handleCircularReference($object/*, string $format = null, array $context = array()*/)
{
if ($this->circularReferenceHandler) {
return \call_user_func($this->circularReferenceHandler, $object);
if (\func_num_args() < 2 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface) {
@trigger_error(sprintf('The "%s()" method will have two new "string $format = null" and "array $context = array()" arguments in version 5.0, not defining it is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED);
}
$format = \func_num_args() > 1 ? func_get_arg(1) : null;
$context = \func_num_args() > 2 ? func_get_arg(2) : array();

throw new CircularReferenceException(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d)', \get_class($object), $this->circularReferenceLimit));
}
$circularReferenceHandler = $context[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER] ?? $this->defaultContext[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER] ?? $this->circularReferenceHandler;
if ($circularReferenceHandler) {
return $circularReferenceHandler($object, $format, $context);
}

private function getCircularReferenceLimitField()
{
return ObjectNormalizer::CIRCULAR_REFERENCE_LIMIT;
throw new CircularReferenceException(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d)', \get_class($object), $this->circularReferenceLimit));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,10 @@
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy;
use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy;
use Symfony\Component\Serializer\Tests\Fixtures\Sibling;
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;
use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy;
use Symfony\Component\Serializer\Tests\Fixtures\NotSerializedConstructorArgumentDummy;
use Symfony\Component\Serializer\Tests\Fixtures\Sibling;
use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder;

/**
Expand Down Expand Up @@ -598,15 +594,22 @@ public function testUnableToNormalizeObjectAttribute()
*/
public function testUnableToNormalizeCircularReference()
{
$normalizer = $this->getNormalizerFor(CircularReferenceDummy::class);
$this->doTestUnableToNormalizeCircularReference();
}

$serializer = new Serializer(array($normalizer));
if ($normalizer instanceof ObjectNormalizer) {
$normalizer->setSerializer($serializer);
} else {
$normalizer->setNormalizer($serializer);
}
$normalizer->setCircularReferenceLimit(2);
/**
* @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException
*/
public function testLegacyUnableToNormalizeCircularReference()
{
$this->doTestUnableToNormalizeCircularReference(true);
}

private function doTestUnableToNormalizeCircularReference(bool $legacy = false)
{
$legacy ? $this->normalizer->setCircularReferenceLimit(2) : $this->createNormalizer(array(ObjectNormalizer::CIRCULAR_REFERENCE_LIMIT => 2));
$serializer = new Serializer(array($this->normalizer));
$this->normalizer->setSerializer($serializer);

$obj = new CircularReferenceDummy();

Expand All @@ -632,21 +635,39 @@ public function testSiblingReference()

public function testCircularReferenceHandler()
{
$normalizer = $this->getNormalizerFor(CircularReferenceDummy::class);
$serializer = new Serializer(array($normalizer));
$this->doTestCircularReferenceHandler();
}

if ($normalizer instanceof ObjectNormalizer) {
$normalizer->setSerializer($serializer);
} else {
$normalizer->setNormalizer($serializer);
}
$normalizer->setCircularReferenceHandler(function ($obj) {
return get_class($obj);
});
public function testLegacyCircularReferenceHandler()
{
$this->doTestCircularReferenceHandler(true);
}

private function doTestCircularReferenceHandler(bool $legacy = false)
{
$this->createNormalizerWithCircularReferenceHandler(function ($obj) {
return \get_class($obj);
}, $legacy);

$obj = new CircularReferenceDummy();
$expected = array('me' => 'Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy');
$this->assertEquals($expected, $normalizer->normalize($obj));
$this->assertEquals($expected, $this->normalizer->normalize($obj));

$this->createNormalizerWithCircularReferenceHandler(function ($obj, string $format, array $context) {
$this->assertInstanceOf(CircularReferenceDummy::class, $obj);
$this->assertSame('test', $format);
$this->arrayHasKey('foo', $context);

return \get_class($obj);
}, $legacy);
$this->assertEquals($expected, $this->normalizer->normalize($obj, 'test', array('foo' => 'bar')));
}

private function createNormalizerWithCircularReferenceHandler(callable $handler, bool $legacy)
{
$legacy ? $this->normalizer->setCircularReferenceHandler($handler) : $this->createNormalizer(array(ObjectNormalizer::CIRCULAR_REFERENCE_HANDLER => $handler));
$this->serializer = new Serializer(array($this->normalizer));
$this->normalizer->setSerializer($this->serializer);
}

public function testDenormalizeNonExistingAttribute()
Expand Down Expand Up @@ -691,8 +712,8 @@ public function testNormalizeNotSerializableContext()

public function testMaxDepth()
{
$normalizer = $this->getNormalizerFor(MaxDepthDummy::class);
$serializer = new Serializer(array($normalizer));
$this->doTestMaxDepth();
}

public function testLegacyMaxDepth()
{
Expand Down Expand Up @@ -744,11 +765,7 @@ private function doTestMaxDepth(bool $legacy = false)
),
);

$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$this->normalizer = new ObjectNormalizer($classMetadataFactory);
$serializer = new Serializer(array($this->normalizer));
$this->normalizer->setSerializer($serializer);
$this->normalizer->setMaxDepthHandler(function ($obj) {
$this->createNormalizerWithMaxDepthHandler(function () {
return 'handler';
}, $legacy);
$result = $this->serializer->normalize($level1, null, array(ObjectNormalizer::ENABLE_MAX_DEPTH => true));
Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.
0