Add a normalizer that support JsonSerializable objects

Handles circular references
This commit is contained in:
Fred Cox 2016-01-29 17:26:46 +02:00 committed by Fred Cox
parent 1b85799602
commit a6788813fa
7 changed files with 216 additions and 0 deletions

View File

@ -25,6 +25,7 @@ use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Serializer\Normalizer\DataUriNormalizer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
use Symfony\Component\Validator\Validation;
/**
@ -914,6 +915,13 @@ class FrameworkExtension extends Extension
$definition->addTag('serializer.normalizer', array('priority' => -910));
}
if (class_exists('Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer')) {
// Run before serializer.normalizer.object
$definition = $container->register('serializer.normalizer.json_serializable', JsonSerializableNormalizer::class);
$definition->setPublic(false);
$definition->addTag('serializer.normalizer', array('priority' => -900));
}
$loader->load('serializer.xml');
$chainLoader = $container->getDefinition('serializer.mapping.chain_loader');

View File

@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Serializer\Normalizer\DataUriNormalizer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
abstract class FrameworkExtensionTest extends TestCase
{
@ -485,6 +486,21 @@ abstract class FrameworkExtensionTest extends TestCase
$this->assertEquals(-910, $tag[0]['priority']);
}
public function testJsonNormalizerRegistered()
{
if (!class_exists('Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer')) {
$this->markTestSkipped('The JsonSerializableNormalizer has been introduced in the Serializer Component version 3.1.');
}
$container = $this->createContainerFromFile('full');
$definition = $container->getDefinition('serializer.normalizer.json');
$tag = $definition->getTag('serializer.normalizer');
$this->assertEquals(JsonSerializableNormalizer::class, $definition->getClass());
$this->assertEquals(-900, $tag[0]['priority']);
}
public function testAssetHelperWhenAssetsAreEnabled()
{
$container = $this->createContainerFromFile('full');

View File

@ -46,6 +46,7 @@
"symfony/form": "~2.8|~3.0",
"symfony/expression-language": "~2.8|~3.0",
"symfony/process": "~2.8|~3.0",
"symfony/serializer": "~2.8|^3.0",
"symfony/validator": "~2.8|~3.0",
"symfony/yaml": "~2.8|~3.0",
"symfony/property-info": "~2.8|~3.0",

View File

@ -1,6 +1,11 @@
CHANGELOG
=========
3.1.0
-----
* added support for serializing objects that implement `JsonSerializable`
2.7.0
-----

View File

@ -0,0 +1,67 @@
<?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\Exception\LogicException;
/**
* A normalizer that uses an objects own JsonSerializable implementation.
*
* @author Fred Cox <mcfedr@gmail.com>
*/
class JsonSerializableNormalizer extends AbstractNormalizer
{
/**
* {@inheritdoc}
*/
public function normalize($object, $format = null, array $context = array())
{
if ($this->isCircularReference($object, $context)) {
return $this->handleCircularReference($object);
}
if (!$object instanceof \JsonSerializable) {
throw new InvalidArgumentException(sprintf('The object must implement "%s".', \JsonSerializable::class));
}
if (!$this->serializer instanceof NormalizerInterface) {
throw new LogicException('Cannot normalize object because injected serializer is not a normalizer');
}
return $this->serializer->normalize($object->jsonSerialize(), $format, $context);
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return $data instanceof \JsonSerializable;
}
/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null)
{
return false;
}
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = null, array $context = array())
{
throw new LogicException(sprintf('Cannot denormalize with "%s".', \JsonSerializable::class));
}
}

View File

@ -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;
class JsonSerializableDummy implements \JsonSerializable
{
public function jsonSerialize()
{
return array(
'foo' => 'a',
'bar' => 'b',
'baz' => 'c',
'qux' => $this,
);
}
}

View File

@ -0,0 +1,94 @@
<?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 Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Serializer\Tests\Fixtures\JsonSerializableDummy;
/**
* @author Fred Cox <mcfedr@gmail.com>
*/
class JsonSerializableNormalizerTest extends \PHPUnit_Framework_TestCase
{
/**
* @var JsonSerializableNormalizer
*/
private $normalizer;
/**
* @var \PHPUnit_Framework_MockObject_MockObject|SerializerInterface
*/
private $serializer;
protected function setUp()
{
$this->serializer = $this->getMock(JsonSerializerNormalizer::class);
$this->normalizer = new JsonSerializableNormalizer();
$this->normalizer->setSerializer($this->serializer);
}
public function testSupportNormalization()
{
$this->assertTrue($this->normalizer->supportsNormalization(new JsonSerializableDummy()));
$this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
}
public function testNormalize()
{
$this->serializer
->expects($this->once())
->method('normalize')
->will($this->returnCallback(function($data) {
$this->assertArraySubset(array('foo' => 'a', 'bar' => 'b', 'baz' => 'c'), $data);
return 'string_object';
}))
;
$this->assertEquals('string_object', $this->normalizer->normalize(new JsonSerializableDummy()));
}
/**
* @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException
*/
public function testCircularNormalize()
{
$this->normalizer->setCircularReferenceLimit(1);
$this->serializer
->expects($this->once())
->method('normalize')
->will($this->returnCallback(function($data, $format, $context) {
$this->normalizer->normalize($data['qux'], $format, $context);
return 'string_object';
}))
;
$this->assertEquals('string_object', $this->normalizer->normalize(new JsonSerializableDummy()));
}
/**
* @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException
* @expectedExceptionMessage The object must implement "JsonSerializable".
*/
public function testInvalidDataThrowException()
{
$this->normalizer->normalize(new \stdClass());
}
}
abstract class JsonSerializerNormalizer implements SerializerInterface, NormalizerInterface
{
}