Completly refactor the Serializer Options Pull Request to push context information directly and avoid state and dependencies between SerializerInterface and encoders/normalizers.

This commit is contained in:
Benjamin Eberlei 2013-01-18 14:08:59 +01:00
parent ef652e2d8f
commit b6bdb450e3
22 changed files with 119 additions and 160 deletions

View File

@ -383,3 +383,9 @@
framework:
trusted_proxies: ['127.0.0.1', '10.0.0.1'] # a list of proxy IPs you trust
```
#### Serializer
* All serializer interfaces (Serializer, Normalizer, Encoder) have been extended with an optional ``$context`` array.
This was necessary to allow for more complex use-cases that require context information during the (de)normalization
and en-/decoding steps.

View File

@ -34,9 +34,9 @@ class ChainDecoder implements DecoderInterface
/**
* {@inheritdoc}
*/
final public function decode($data, $format)
final public function decode($data, $format, array $context = array())
{
return $this->getDecoder($format)->decode($data, $format);
return $this->getDecoder($format)->decode($data, $format, $context);
}
/**

View File

@ -35,9 +35,9 @@ class ChainEncoder implements EncoderInterface
/**
* {@inheritdoc}
*/
final public function encode($data, $format)
final public function encode($data, $format, array $context = array())
{
return $this->getEncoder($format)->encode($data, $format);
return $this->getEncoder($format)->encode($data, $format, $context);
}
/**

View File

@ -23,10 +23,11 @@ interface DecoderInterface
*
* @param scalar $data Data to decode
* @param string $format Format name
* @param array $context options that decoders have access to.
*
* @return mixed
*/
public function decode($data, $format);
public function decode($data, $format, array $context = array());
/**
* Checks whether the serializer can decode from given format

View File

@ -23,10 +23,11 @@ interface EncoderInterface
*
* @param mixed $data Data to encode
* @param string $format Format name
* @param array $context options that normalizers/encoders have access to.
*
* @return scalar
*/
public function encode($data, $format);
public function encode($data, $format, array $context = array());
/**
* Checks whether the serializer can encode to given format

View File

@ -11,15 +11,12 @@
namespace Symfony\Component\Serializer\Encoder;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Serializer\SerializerAwareInterface;
/**
* Decodes JSON data
*
* @author Sander Coolen <sander@jibber.nl>
*/
class JsonDecode implements DecoderInterface, SerializerAwareInterface
class JsonDecode implements DecoderInterface
{
private $associative;
private $recursionDepth;
@ -52,13 +49,13 @@ class JsonDecode implements DecoderInterface, SerializerAwareInterface
*
* @return mixed
*/
public function decode($data, $format)
public function decode($data, $format, array $context = array())
{
$context = $this->getContext();
$context = $this->resolveContext($context);
$associative = $context['associative'];
$recursionDepth = $context['recursionDepth'];
$options = $context['options'];
$associative = $context['json_decode_associative'];
$recursionDepth = $context['json_decode_recursion_depth'];
$options = $context['json_decode_options'];
$decodedData = json_decode($data, $associative, $recursionDepth, $options);
$this->lastError = json_last_error();
@ -75,45 +72,19 @@ class JsonDecode implements DecoderInterface, SerializerAwareInterface
}
/**
* {@inheritdoc}
* Merge the default options of the Json Decoder with the passed context.
*
* @param array $context
* @return array
*/
public function setSerializer(SerializerInterface $serializer)
private function resolveContext(array $context)
{
$this->serializer = $serializer;
}
private function getContext()
{
$options = array(
'associative' => $this->associative,
'recursionDepth' => $this->recursionDepth,
'options' => 0
$defaultOptions = array(
'json_decode_associative' => $this->associative,
'json_decode_recursion_depth' => $this->recursionDepth,
'json_decode_options' => 0
);
if (!$this->serializer) {
return $options;
}
$options = array(
'associative' => false,
'recursionDepth' => 512,
'options' => 0
);
$context = $this->serializer->getContext();
if (isset($context['associative'])) {
$options['associative'] = $context['associative'];
}
if (isset($context['recursionDepth'])) {
$options['recursionDepth'] = $context['recursionDepth'];
}
if (isset($context['options'])) {
$options['options'] = $context['options'];
}
return $options;
return array_merge($defaultOptions, $context);
}
}

View File

@ -16,7 +16,7 @@ namespace Symfony\Component\Serializer\Encoder;
*
* @author Sander Coolen <sander@jibber.nl>
*/
class JsonEncode extends SerializerAwareEncoder implements EncoderInterface
class JsonEncode implements EncoderInterface
{
private $options ;
private $lastError = JSON_ERROR_NONE;
@ -41,16 +41,13 @@ class JsonEncode extends SerializerAwareEncoder implements EncoderInterface
/**
* Encodes PHP data to a JSON string
*
* @param mixed $data
* @param string $format
*
* @return string
* {@inheritdoc}
*/
public function encode($data, $format)
public function encode($data, $format, array $context = array())
{
$options = $this->getContext();
$context = $this->resolveContext($context);
$encodedJson = json_encode($data, $options);
$encodedJson = json_encode($data, $context['json_encode_options']);
$this->lastError = json_last_error();
return $encodedJson;
@ -64,22 +61,14 @@ class JsonEncode extends SerializerAwareEncoder implements EncoderInterface
return JsonEncoder::FORMAT === $format;
}
private function getContext()
/**
* Merge default json encode options with context.
*
* @param array $context
* @return array
*/
private function resolveContext(array $context = array())
{
if (!$this->serializer) {
return 0;
}
$context = $this->serializer->getContext();
if (empty($context)) {
$context = array(0);
}
if (!is_array($context)) {
$context = array($context);
}
return array_sum($context);
return array_merge(array('json_encode_options' => $this->options), $context);
}
}

View File

@ -16,7 +16,7 @@ namespace Symfony\Component\Serializer\Encoder;
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class JsonEncoder extends SerializerAwareEncoder implements EncoderInterface, DecoderInterface
class JsonEncoder implements EncoderInterface, DecoderInterface
{
const FORMAT = 'json';
@ -59,21 +59,17 @@ class JsonEncoder extends SerializerAwareEncoder implements EncoderInterface, De
/**
* {@inheritdoc}
*/
public function encode($data, $format)
public function encode($data, $format, array $context = array())
{
$this->encodingImpl->setSerializer($this->serializer);
return $this->encodingImpl->encode($data, self::FORMAT);
return $this->encodingImpl->encode($data, self::FORMAT, $context);
}
/**
* {@inheritdoc}
*/
public function decode($data, $format)
public function decode($data, $format, array $context = array())
{
$this->decodingImpl->setSerializer($this->serializer);
return $this->decodingImpl->decode($data, self::FORMAT);
return $this->decodingImpl->decode($data, self::FORMAT, $context);
}
/**

View File

@ -26,24 +26,36 @@ class XmlEncoder extends SerializerAwareEncoder implements EncoderInterface, Dec
private $format;
private $rootNodeName = 'response';
/**
* Construct new XmlEncoder and allow to change the root node element name.
*
* @param string $rootNodeName
*/
public function __construct($rootNodeName = 'response')
{
$this->rootNodeName = $rootNodeName;
}
/**
* {@inheritdoc}
*/
public function encode($data, $format)
public function encode($data, $format, array $context = array())
{
if ($data instanceof \DOMDocument) {
return $data->saveXML();
}
$xmlRootNodeName = $this->resolveXmlRootName($context);
$this->dom = new \DOMDocument();
$this->format = $format;
if (null !== $data && !is_scalar($data)) {
$root = $this->dom->createElement($this->getRealRootNodeName());
$root = $this->dom->createElement($xmlRootNodeName);
$this->dom->appendChild($root);
$this->buildXml($root, $data);
$this->buildXml($root, $data, $xmlRootNodeName);
} else {
$this->appendNode($this->dom, $data, $this->getRealRootNodeName());
$this->appendNode($this->dom, $data, $xmlRootNodeName);
}
return $this->dom->saveXML();
@ -52,7 +64,7 @@ class XmlEncoder extends SerializerAwareEncoder implements EncoderInterface, Dec
/**
* {@inheritdoc}
*/
public function decode($data, $format)
public function decode($data, $format, array $context = array())
{
$internalErrors = libxml_use_internal_errors(true);
$disableEntities = libxml_disable_entity_loader(true);
@ -269,12 +281,13 @@ class XmlEncoder extends SerializerAwareEncoder implements EncoderInterface, Dec
*
* @param DOMNode $parentNode
* @param array|object $data data
* @param string $xmlRootNodeName
*
* @return Boolean
*
* @throws UnexpectedValueException
*/
private function buildXml($parentNode, $data)
private function buildXml($parentNode, $data, $xmlRootNodeName)
{
$append = true;
@ -310,21 +323,24 @@ class XmlEncoder extends SerializerAwareEncoder implements EncoderInterface, Dec
return $append;
}
if (is_object($data)) {
$data = $this->serializer->normalize($data, $this->format);
if (null !== $data && !is_scalar($data)) {
return $this->buildXml($parentNode, $data);
return $this->buildXml($parentNode, $data, $xmlRootNodeName);
}
// top level data object was normalized into a scalar
if (!$parentNode->parentNode->parentNode) {
$root = $parentNode->parentNode;
$root->removeChild($parentNode);
return $this->appendNode($root, $data, $this->getRealRootNodeName());
return $this->appendNode($root, $data, $xmlRootNodeName);
}
return $this->appendNode($parentNode, $data, 'data');
}
throw new UnexpectedValueException('An unexpected value could not be serialized: '.var_export($data, true));
}
@ -376,7 +392,7 @@ class XmlEncoder extends SerializerAwareEncoder implements EncoderInterface, Dec
private function selectNodeType($node, $val)
{
if (is_array($val)) {
return $this->buildXml($node, $val);
return $this->buildXml($node, $val, null);
} elseif ($val instanceof \SimpleXMLElement) {
$child = $this->dom->importNode(dom_import_simplexml($val), true);
$node->appendChild($child);
@ -403,14 +419,8 @@ class XmlEncoder extends SerializerAwareEncoder implements EncoderInterface, Dec
/**
* Get real XML root node name, taking serializer options into account.
*/
private function getRealRootNodeName()
private function resolveXmlRootName(array $context = array())
{
if (!$this->serializer) {
return $this->rootNodeName;
}
$context = $this->serializer->getContext();
return isset($context['xml_root_node_name'])
? $context['xml_root_node_name']
: $this->rootNodeName;

View File

@ -19,18 +19,18 @@ class CustomNormalizer extends SerializerAwareNormalizer implements NormalizerIn
/**
* {@inheritdoc}
*/
public function normalize($object, $format = null)
public function normalize($object, $format = null, array $context = array())
{
return $object->normalize($this->serializer, $format);
return $object->normalize($this->serializer, $format, $context);
}
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = null)
public function denormalize($data, $class, $format = null, array $context = array())
{
$object = new $class;
$object->denormalize($this->serializer, $data, $format);
$object->denormalize($this->serializer, $data, $format, $context);
return $object;
}

View File

@ -32,6 +32,7 @@ interface DenormalizableInterface
* @param array|scalar $data The data from which to re-create the object.
* @param string|null $format The format is optionally given to be able to denormalize differently
* based on different input formats.
* @param array $context options for denormalizing
*/
public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null);
public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null, array $context = array());
}

View File

@ -24,10 +24,11 @@ interface DenormalizerInterface
* @param mixed $data data to restore
* @param string $class the expected class to instantiate
* @param string $format format the given data was extracted from
* @param array $context options available to the denormalizer
*
* @return object
*/
public function denormalize($data, $class, $format = null);
public function denormalize($data, $class, $format = null, array $context = array());
/**
* Checks whether the given class is supported for denormalization by this normalizer

View File

@ -69,7 +69,7 @@ class GetSetMethodNormalizer extends SerializerAwareNormalizer implements Normal
/**
* {@inheritdoc}
*/
public function normalize($object, $format = null)
public function normalize($object, $format = null, array $context = array())
{
$reflectionObject = new \ReflectionObject($object);
$reflectionMethods = $reflectionObject->getMethods(\ReflectionMethod::IS_PUBLIC);
@ -101,7 +101,7 @@ class GetSetMethodNormalizer extends SerializerAwareNormalizer implements Normal
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = null)
public function denormalize($data, $class, $format = null, array $context = array())
{
$reflectionClass = new \ReflectionClass($class);
$constructor = $reflectionClass->getConstructor();

View File

@ -31,8 +31,9 @@ interface NormalizableInterface
* can use it to normalize objects contained within this object.
* @param string|null $format The format is optionally given to be able to normalize differently
* based on different output formats.
* @param array $context Options for normalizing this object
*
* @return array|scalar
*/
public function normalize(NormalizerInterface $normalizer, $format = null);
public function normalize(NormalizerInterface $normalizer, $format = null, array $context = array());
}

View File

@ -23,10 +23,11 @@ interface NormalizerInterface
*
* @param object $object object to normalize
* @param string $format format the normalization result will be encoded as
* @param array $context Context options for the normalizer
*
* @return array|scalar
*/
public function normalize($object, $format = null);
public function normalize($object, $format = null, array $context = array());
/**
* Checks whether the given class is supported for normalization by this normalizer

View File

@ -42,7 +42,6 @@ class Serializer implements SerializerInterface, NormalizerInterface, Denormaliz
protected $normalizers = array();
protected $normalizerCache = array();
protected $denormalizerCache = array();
protected $context;
public function __construct(array $normalizers = array(), array $encoders = array())
{
@ -79,13 +78,11 @@ class Serializer implements SerializerInterface, NormalizerInterface, Denormaliz
throw new UnexpectedValueException('Serialization for the format '.$format.' is not supported');
}
$this->context = $context;
if ($this->encoder->needsNormalization($format)) {
$data = $this->normalize($data, $format);
$data = $this->normalize($data, $format, $context);
}
return $this->encode($data, $format);
return $this->encode($data, $format, $context);
}
/**
@ -97,38 +94,36 @@ class Serializer implements SerializerInterface, NormalizerInterface, Denormaliz
throw new UnexpectedValueException('Deserialization for the format '.$format.' is not supported');
}
$this->context = $context;
$data = $this->decode($data, $format, $context);
$data = $this->decode($data, $format);
return $this->denormalize($data, $type, $format);
return $this->denormalize($data, $type, $format, $context);
}
/**
* {@inheritdoc}
*/
public function normalize($data, $format = null)
public function normalize($data, $format = null, array $context = array())
{
if (null === $data || is_scalar($data)) {
return $data;
}
if (is_object($data) && $this->supportsNormalization($data, $format)) {
return $this->normalizeObject($data, $format);
return $this->normalizeObject($data, $format, $context);
}
if ($data instanceof \Traversable) {
$normalized = array();
foreach ($data as $key => $val) {
$normalized[$key] = $this->normalize($val, $format);
$normalized[$key] = $this->normalize($val, $format, $context);
}
return $normalized;
}
if (is_object($data)) {
return $this->normalizeObject($data, $format);
return $this->normalizeObject($data, $format, $context);
}
if (is_array($data)) {
foreach ($data as $key => $val) {
$data[$key] = $this->normalize($val, $format);
$data[$key] = $this->normalize($val, $format, $context);
}
return $data;
@ -139,9 +134,9 @@ class Serializer implements SerializerInterface, NormalizerInterface, Denormaliz
/**
* {@inheritdoc}
*/
public function denormalize($data, $type, $format = null)
public function denormalize($data, $type, $format = null, array $context = array())
{
return $this->denormalizeObject($data, $type, $format);
return $this->denormalizeObject($data, $type, $format, $context);
}
/**
@ -207,17 +202,17 @@ class Serializer implements SerializerInterface, NormalizerInterface, Denormaliz
/**
* {@inheritdoc}
*/
final public function encode($data, $format)
final public function encode($data, $format, array $context = array())
{
return $this->encoder->encode($data, $format);
return $this->encoder->encode($data, $format, $context);
}
/**
* {@inheritdoc}
*/
final public function decode($data, $format)
final public function decode($data, $format, array $context = array())
{
return $this->decoder->decode($data, $format);
return $this->decoder->decode($data, $format, $context);
}
/**
@ -225,13 +220,14 @@ class Serializer implements SerializerInterface, NormalizerInterface, Denormaliz
*
* @param object $object object to normalize
* @param string $format format name, present to give the option to normalizers to act differently based on formats
* @param array $context The context data for this particular normalization
*
* @return array|scalar
*
* @throws LogicException
* @throws UnexpectedValueException
*/
private function normalizeObject($object, $format = null)
private function normalizeObject($object, $format = null, array $context = array())
{
if (!$this->normalizers) {
throw new LogicException('You must register at least one normalizer to be able to normalize objects.');
@ -239,14 +235,14 @@ class Serializer implements SerializerInterface, NormalizerInterface, Denormaliz
$class = get_class($object);
if (isset($this->normalizerCache[$class][$format])) {
return $this->normalizerCache[$class][$format]->normalize($object, $format);
return $this->normalizerCache[$class][$format]->normalize($object, $format, $context);
}
foreach ($this->normalizers as $normalizer) {
if ($normalizer->supportsNormalization($object, $format)) {
$this->normalizerCache[$class][$format] = $normalizer;
return $normalizer->normalize($object, $format);
return $normalizer->normalize($object, $format, $context);
}
}
@ -259,27 +255,28 @@ class Serializer implements SerializerInterface, NormalizerInterface, Denormaliz
* @param mixed $data data to restore
* @param string $class the expected class to instantiate
* @param string $format format name, present to give the option to normalizers to act differently based on formats
* @param array $context The context data for this particular denormalization
*
* @return object
*
* @throws LogicException
* @throws UnexpectedValueException
*/
private function denormalizeObject($data, $class, $format = null)
private function denormalizeObject($data, $class, $format = null, array $context = array())
{
if (!$this->normalizers) {
throw new LogicException('You must register at least one normalizer to be able to denormalize objects.');
}
if (isset($this->denormalizerCache[$class][$format])) {
return $this->denormalizerCache[$class][$format]->denormalize($data, $class, $format);
return $this->denormalizerCache[$class][$format]->denormalize($data, $class, $format, $context);
}
foreach ($this->normalizers as $normalizer) {
if ($normalizer->supportsDenormalization($data, $class, $format)) {
$this->denormalizerCache[$class][$format] = $normalizer;
return $normalizer->denormalize($data, $class, $format);
return $normalizer->denormalize($data, $class, $format, $context);
}
}
@ -301,12 +298,4 @@ class Serializer implements SerializerInterface, NormalizerInterface, Denormaliz
{
return $this->decoder->supportsDecoding($format);
}
/**
* {@inheritdoc}
*/
public function getContext()
{
return $this->context;
}
}

View File

@ -40,11 +40,4 @@ interface SerializerInterface
* @return object
*/
public function deserialize($data, $type, $format, array $context = array());
/**
* Get current options of the serializer
*
* @return array
*/
public function getContext();
}

View File

@ -21,7 +21,6 @@ class JsonEncoderTest extends \PHPUnit_Framework_TestCase
{
$this->encoder = new JsonEncoder;
$this->serializer = new Serializer(array(new CustomNormalizer()), array('json' => new JsonEncoder()));
$this->encoder->setSerializer($this->serializer);
}
public function testEncodeScalar()
@ -45,7 +44,7 @@ class JsonEncoderTest extends \PHPUnit_Framework_TestCase
public function testOptions()
{
$context = array(JSON_NUMERIC_CHECK);
$context = array('json_encode_options' => JSON_NUMERIC_CHECK);
$arr = array();
$arr['foo'] = "3";

View File

@ -17,7 +17,7 @@ use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
class DenormalizableDummy implements DenormalizableInterface
{
public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null)
public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null, array $context = array())
{
}

View File

@ -23,7 +23,7 @@ class Dummy implements NormalizableInterface, DenormalizableInterface
public $baz;
public $qux;
public function normalize(NormalizerInterface $normalizer, $format = null)
public function normalize(NormalizerInterface $normalizer, $format = null, array $context = array())
{
return array(
'foo' => $this->foo,
@ -33,7 +33,7 @@ class Dummy implements NormalizableInterface, DenormalizableInterface
);
}
public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null)
public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null, array $context = array())
{
$this->foo = $data['foo'];
$this->bar = $data['bar'];

View File

@ -18,7 +18,7 @@ use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
class NormalizableTraversableDummy extends TraversableDummy implements NormalizableInterface, DenormalizableInterface
{
public function normalize(NormalizerInterface $normalizer, $format = null)
public function normalize(NormalizerInterface $normalizer, $format = null, array $context = array())
{
return array(
'foo' => 'normalizedFoo',
@ -26,7 +26,7 @@ class NormalizableTraversableDummy extends TraversableDummy implements Normaliza
);
}
public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null)
public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null, array $context = array())
{
return array(
'foo' => 'denormalizedFoo',

View File

@ -21,12 +21,12 @@ class ScalarDummy implements NormalizableInterface, DenormalizableInterface
public $foo;
public $xmlFoo;
public function normalize(NormalizerInterface $normalizer, $format = null)
public function normalize(NormalizerInterface $normalizer, $format = null, array $context = array())
{
return $format === 'xml' ? $this->xmlFoo : $this->foo;
}
public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null)
public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null, array $context = array())
{
if ($format === 'xml') {
$this->xmlFoo = $data;