Skip to content

[Serializer] Properties extractor implementations #30980

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
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
Add circular reference normalizer
  • Loading branch information
joelwurtz committed Apr 9, 2019
commit a3c388a2a3c90f2bda9c27ce09ca6f21b6d4a9fa
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?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\CircularReferenceException;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerInterface;

/**
* Handle circular references.
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
final class CheckCircularReferenceNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface, NormalizerAwareInterface, DenormalizerAwareInterface, SerializerAwareInterface
{
public const CIRCULAR_REFERENCE_LIMIT = 'circular_reference_limit';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure to duplicate const.
I think, for the moment. you can referrer to original const as :
public const CIRCULAR_REFERENCE_LIMIT = AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT
When the deprecated on AbstractNormalizer will be add, we will can revert the relation with
public const CIRCULAR_REFERENCE_LIMIT = CheckCircularReferenceNormalizer::CIRCULAR_REFERENCE_LIMIT on AbstractNormalizer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jewome62 The const value can't be changed for BC, so it's alright.

public const CIRCULAR_REFERENCE_HANDLER = 'circular_reference_handler';
private const CIRCULAR_REFERENCE_LIMIT_COUNTERS = 'circular_reference_limit_counters';

private $normalizer;

public function __construct(NormalizerInterface $normalizer)
{
$this->normalizer = $normalizer;
}

/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = null, array $context = [])
{
if (!$this->normalizer instanceof DenormalizerInterface) {
return null;
}

return $this->normalizer->denormalize($data, $class, $format, $context);
}

/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null)
{
return ($this->normalizer instanceof DenormalizerInterface && $this->normalizer->supportsDenormalization($data, $type, $format));
}

/**
* {@inheritdoc}
*/
public function normalize($object, $format = null, array $context = [])
{
if ($this->isCircularReference($object, $context)) {
return $this->handleCircularReference($object, $format, $context);
}

return $this->normalizer->normalize($object, $format, $context);
}

/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return $this->normalizer->supportsNormalization($data, $format);
}

/**
* {@inheritdoc}
*/
public function hasCacheableSupportsMethod(): bool
{
return $this->normalizer instanceof CacheableSupportsMethodInterface && $this->normalizer->hasCacheableSupportsMethod();
}

/**
* {@inheritdoc}
*/
public function setDenormalizer(DenormalizerInterface $denormalizer)
{
if ($this->normalizer instanceof DenormalizerAwareInterface) {
$this->normalizer->setDenormalizer($denormalizer);
}
}

/**
* {@inheritdoc}
*/
public function setNormalizer(NormalizerInterface $normalizer)
{
if ($this->normalizer instanceof NormalizerAwareInterface) {
$this->normalizer->setNormalizer($normalizer);
}
}

/**
* {@inheritdoc}
*/
public function setSerializer(SerializerInterface $serializer)
{
if ($this->normalizer instanceof SerializerAwareInterface) {
$this->normalizer->setSerializer($serializer);
}
}

private function isCircularReference($object, &$context)
{
$objectHash = spl_object_hash($object);

$circularReferenceLimit = $context[self::CIRCULAR_REFERENCE_LIMIT] ?? 1;

if (isset($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash])) {
if ($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash] >= $circularReferenceLimit) {
unset($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash]);

return true;
}

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

return false;
}

protected function handleCircularReference($object, string $format = null, array $context = [])
{
$circularReferenceHandler = $context[self::CIRCULAR_REFERENCE_HANDLER] ?? null;

if ($circularReferenceHandler) {
return $circularReferenceHandler($object, $format, $context);
}

$circularReferenceLimit = $context[self::CIRCULAR_REFERENCE_LIMIT] ?? 1;

throw new CircularReferenceException(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d)', \get_class($object), $circularReferenceLimit));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
<?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\Normalizer;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Exception\CircularReferenceException;
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
use Symfony\Component\Serializer\Normalizer\CheckCircularReferenceNormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Serializer;

/**
* @author Jérôme Desjardins <jewome62@gmail.com>
*/
class CheckCircularReferenceNormalizerTest extends TestCase
{
public function testNormalize()
{
$subNormalizer = $this
->getMockBuilder(NormalizerInterface::class)
->getMock()
;

$subNormalizer->method('normalize')->willReturn(['foo' => 'foo']);
$normalizer = new CheckCircularReferenceNormalizer($subNormalizer);

$dummy = new Dummy();

$data = $normalizer->normalize($dummy, 'json');
$this->assertSame(['foo' => 'foo'], $data);

$data = $normalizer->normalize($dummy, 'json');
$this->assertSame(['foo' => 'foo'], $data);
}

public function testSupportNormalization()
{
$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, DenormalizerInterface::class])
->getMock()
;

$subNormalizer->method('supportsNormalization')->willReturn(true);
$normalizer = new CheckCircularReferenceNormalizer($subNormalizer);

$this->assertTrue($normalizer->supportsNormalization([], 'json'));

$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, DenormalizerInterface::class])
->getMock()
;

$normalizer = new CheckCircularReferenceNormalizer($subNormalizer);
$subNormalizer->method('supportsNormalization')->willReturn(false);
$this->assertFalse($normalizer->supportsNormalization([], 'json'));
}

public function testDenormalize()
{
$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, DenormalizerInterface::class])
->getMock()
;

$dummy = new DummyCircular();
$subNormalizer->method('denormalize')->willReturn($dummy);
$normalizer = new CheckCircularReferenceNormalizer($subNormalizer);

$data = $normalizer->denormalize([], 'type', 'json');

$this->assertSame($dummy, $data);
}

public function testSupportDenormalization()
{
$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, DenormalizerInterface::class])
->getMock()
;

$subNormalizer->method('supportsDenormalization')->willReturn(true);
$normalizer = new CheckCircularReferenceNormalizer($subNormalizer);

$this->assertTrue($normalizer->supportsDenormalization([], 'type', 'json'));

$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, DenormalizerInterface::class])
->getMock()
;

$normalizer = new CheckCircularReferenceNormalizer($subNormalizer);
$subNormalizer->method('supportsDenormalization')->willReturn(false);
$this->assertFalse($normalizer->supportsDenormalization([], 'type', 'json'));
}

public function testHasCacheableSupportMethod()
{
$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, CacheableSupportsMethodInterface::class])
->getMock()
;

$subNormalizer->method('hasCacheableSupportsMethod')->willReturn(true);
$normalizer = new CheckCircularReferenceNormalizer($subNormalizer);

$this->assertTrue($normalizer->hasCacheableSupportsMethod());

$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, CacheableSupportsMethodInterface::class])
->getMock()
;

$normalizer = new CheckCircularReferenceNormalizer($subNormalizer);
$subNormalizer->method('hasCacheableSupportsMethod')->willReturn(false);
$this->assertFalse($normalizer->hasCacheableSupportsMethod());
}


/**
* @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException
*/
public function testThrowException()
{
$dummyCircular = new DummyCircular();
$dummyNormalizer = new DummyNormalizer();
$normalizer = new CheckCircularReferenceNormalizer($dummyNormalizer);
$serializer = new Serializer([$normalizer]);

$normalizer->normalize($dummyCircular, 'json');
}

public function testLimitCounter()
{
$dummyCircular = new DummyCircular();
$dummyNormalizer = new DummyNormalizer();
$normalizer = new CheckCircularReferenceNormalizer($dummyNormalizer);
$serializer = new Serializer([$normalizer]);

try {
$normalizer->normalize($dummyCircular, 'json', [
CheckCircularReferenceNormalizer::CIRCULAR_REFERENCE_LIMIT => 3
]);
} catch (CircularReferenceException $exception) {
$this->assertSame(3, $dummyCircular->counter);

return;
}

$this->assertFalse(true);
}

public function testHandler()
{
$dummyCircular = new DummyCircular();
$dummyNormalizer = new DummyNormalizer();
$normalizer = new CheckCircularReferenceNormalizer($dummyNormalizer);
$serializer = new Serializer([$normalizer]);

$data = $normalizer->normalize($dummyCircular, 'format', [
'context_key' => 'context_value',
CheckCircularReferenceNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) use ($dummyCircular) {
$this->assertSame($dummyCircular, $object);
$this->assertSame(1, $object->counter);
$this->assertSame('format', $format);
$this->assertInternalType('array', $context);
$this->assertArrayHasKey('context_key', $context);
$this->assertSame($context['context_key'], 'context_value');

return 'dummy';
}
]);

$this->assertSame('dummy', $data);
}
}

class DummyCircular
{
public $counter = 0;
}

class DummyNormalizer implements NormalizerInterface, NormalizerAwareInterface
{
use NormalizerAwareTrait;

public $object;

public function normalize($object, $format = null, array $context = [])
{
$object->counter++;

return $this->normalizer->normalize($object, $format, $context);
}

public function supportsNormalization($data, $format = null)
{
return true;
}
}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy