Merge remote branch 'Seldaek/serializer'

* Seldaek/serializer:
  [Serializer] CS fixes
  [Serializer] Split supports in supportsNormalization and supportsDenormalization
  [Serializer] Add support for Traversable objects
  Fixed docs typo
  [Serializer] updated SerializerInterface
This commit is contained in:
Fabien Potencier 2011-05-06 22:13:18 +02:00
commit c98bf4ff94
9 changed files with 135 additions and 105 deletions

View File

@ -124,6 +124,9 @@ beta1 to beta2
'allow_delete' => true, 'allow_delete' => true,
)); ));
* Serializer: The NormalizerInterface's `supports()` method has been split in
two methods: `supportsNormalization` and `supportsDenormalization`.
PR12 to beta1 PR12 to beta1
------------- -------------
@ -210,11 +213,11 @@ PR11 to PR12
<app:engine>twig</app:engine> <app:engine>twig</app:engine>
<twig:extension>twig.extension.debug</twig:extension> <twig:extension>twig.extension.debug</twig:extension>
* Fixes a critical security issue which allowed all users to switch to * Fixes a critical security issue which allowed all users to switch to
arbitrary accounts when the SwitchUserListener was activated. Configurations arbitrary accounts when the SwitchUserListener was activated. Configurations
which do not use the SwitchUserListener are not affected. which do not use the SwitchUserListener are not affected.
* The Dependency Injection Container now strongly validates the references of * The Dependency Injection Container now strongly validates the references of
all your services at the end of its compilation process. If you have invalid all your services at the end of its compilation process. If you have invalid
references this will result in a compile-time exception instead of a run-time references this will result in a compile-time exception instead of a run-time
exception (the previous behavior). exception (the previous behavior).

View File

@ -224,14 +224,14 @@ class XmlEncoder extends AbstractEncoder implements DecoderInterface
$append = $this->selectNodeType($parentNode, $data); $append = $this->selectNodeType($parentNode, $data);
} elseif (is_array($data) && false === is_numeric($key)) { } elseif (is_array($data) && false === is_numeric($key)) {
/** /**
* Is this array fully numeric keys? * Is this array fully numeric keys?
*/ */
if (ctype_digit(implode('', array_keys($data)))) { if (ctype_digit(implode('', array_keys($data)))) {
/** /**
* Create nodes to append to $parentNode based on the $key of this array * Create nodes to append to $parentNode based on the $key of this array
* Produces <xml><item>0</item><item>1</item></xml> * Produces <xml><item>0</item><item>1</item></xml>
* From array("item" => array(0,1)); * From array("item" => array(0,1));
*/ */
foreach ($data as $subData) { foreach ($data as $subData) {
$append = $this->appendNode($parentNode, $subData, $key); $append = $this->appendNode($parentNode, $subData, $key);
} }

View File

@ -39,13 +39,27 @@ class CustomNormalizer extends AbstractNormalizer
/** /**
* Checks if the given class implements the NormalizableInterface. * Checks if the given class implements the NormalizableInterface.
* *
* @param ReflectionClass $class A ReflectionClass instance of the class * @param mixed $data Data to normalize.
* to serialize into or from. * @param string $format The format being (de-)serialized from or into.
* @param string $format The format being (de-)serialized from or into.
* @return Boolean * @return Boolean
*/ */
public function supports(\ReflectionClass $class, $format = null) public function supportsNormalization($data, $format = null)
{ {
return $class->implementsInterface('Symfony\Component\Serializer\Normalizer\NormalizableInterface'); return $data instanceof NormalizableInterface;
}
/**
* Checks if the given class implements the NormalizableInterface.
*
* @param mixed $data Data to denormalize from.
* @param string $type The class to which the data should be denormalized.
* @param string $format The format being deserialized from.
* @return Boolean
*/
public function supportsDenormalization($data, $type, $format = null)
{
$class = new \ReflectionClass($type);
return $class->isSubclassOf('Symfony\Component\Serializer\Normalizer\NormalizableInterface');
} }
} }

View File

@ -107,23 +107,37 @@ class GetSetMethodNormalizer extends AbstractNormalizer
return $object; return $object;
} }
/**
* {@inheritDoc}
*/
public function supportsNormalization($data, $format = null)
{
return $this->supports(get_class($data));
}
/**
* {@inheritDoc}
*/
public function supportsDenormalization($data, $type, $format = null)
{
return $this->supports($type);
}
/** /**
* Checks if the given class has any get{Property} method. * Checks if the given class has any get{Property} method.
* *
* @param ReflectionClass $class A ReflectionClass instance of the class * @param string $class
* to serialize into or from. * @return Boolean
* @param string $format The format being (de-)serialized from or into.
* @return Boolean Whether the class has any getters.
*/ */
public function supports(\ReflectionClass $class, $format = null) private function supports($class)
{ {
$class = new \ReflectionClass($type);
$methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC); $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) { foreach ($methods as $method) {
if ($this->isGetMethod($method)) { if ($this->isGetMethod($method)) {
return true; return true;
} }
} }
return false; return false;
} }

View File

@ -14,7 +14,7 @@ use Symfony\Component\Serializer\SerializerInterface;
*/ */
/** /**
* Defines the interface of serializers * Defines the interface of normalizers.
* *
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
*/ */
@ -43,16 +43,25 @@ interface NormalizerInterface
function denormalize($data, $class, $format = null); function denormalize($data, $class, $format = null);
/** /**
* Checks whether the given class is supported by this normalizer * Checks whether the given class is supported for normalization by this normalizer
*
* @param ReflectionClass $class
* @param string $format format the given data was extracted from
* *
* @param mixed $data Data to normalize.
* @param string $format The format being (de-)serialized from or into.
* @return Boolean * @return Boolean
*
* @api * @api
*/ */
function supports(\ReflectionClass $class, $format = null); function supportsNormalization($data, $format = null);
/**
* Checks whether the given class is supported for denormalization by this normalizer
*
* @param mixed $data Data to denormalize from.
* @param string $type The class to which the data should be denormalized.
* @param string $format The format being deserialized from.
* @return Boolean
* @api
*/
function supportsDenormalization($data, $type, $format = null);
/** /**
* Sets the owning Serializer object * Sets the owning Serializer object

View File

@ -31,6 +31,7 @@ class Serializer implements SerializerInterface
private $normalizers = array(); private $normalizers = array();
private $encoders = array(); private $encoders = array();
protected $normalizerCache = array(); protected $normalizerCache = array();
protected $denormalizerCache = array();
/** /**
* @param mixed $value value to test * @param mixed $value value to test
@ -46,7 +47,14 @@ class Serializer implements SerializerInterface
*/ */
public function serialize($data, $format) public function serialize($data, $format)
{ {
return $this->encode($data, $format); return $this->encode($this->normalize($data, $format), $format);
}
/**
* {@inheritDoc}
*/
public function deserialize($data, $type, $format) {
return $this->denormalize($this->decode($data, $format), $type, $format);
} }
/** /**
@ -61,9 +69,8 @@ class Serializer implements SerializerInterface
if (isset($this->normalizerCache[$class][$format])) { if (isset($this->normalizerCache[$class][$format])) {
return $this->normalizerCache[$class][$format]->normalize($object, $format, $properties); return $this->normalizerCache[$class][$format]->normalize($object, $format, $properties);
} }
$reflClass = new \ReflectionClass($class);
foreach ($this->normalizers as $normalizer) { foreach ($this->normalizers as $normalizer) {
if ($normalizer->supports($reflClass, $format)) { if ($normalizer->supportsNormalization($object, $class, $format)) {
$this->normalizerCache[$class][$format] = $normalizer; $this->normalizerCache[$class][$format] = $normalizer;
return $normalizer->normalize($object, $format, $properties); return $normalizer->normalize($object, $format, $properties);
} }
@ -79,13 +86,12 @@ class Serializer implements SerializerInterface
if (!$this->normalizers) { if (!$this->normalizers) {
throw new \LogicException('You must register at least one normalizer to be able to denormalize objects.'); throw new \LogicException('You must register at least one normalizer to be able to denormalize objects.');
} }
if (isset($this->normalizerCache[$class][$format])) { if (isset($this->denormalizerCache[$class][$format])) {
return $this->normalizerCache[$class][$format]->denormalize($data, $class, $format); return $this->denormalizerCache[$class][$format]->denormalize($data, $class, $format);
} }
$reflClass = new \ReflectionClass($class);
foreach ($this->normalizers as $normalizer) { foreach ($this->normalizers as $normalizer) {
if ($normalizer->supports($reflClass, $format)) { if ($normalizer->supportsDenormalization($class, $format)) {
$this->normalizerCache[$class][$format] = $normalizer; $this->denormalizerCache[$class][$format] = $normalizer;
return $normalizer->denormalize($data, $class, $format); return $normalizer->denormalize($data, $class, $format);
} }
} }
@ -95,20 +101,38 @@ class Serializer implements SerializerInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function normalize($data, $format) public function normalize($data, $format = null)
{ {
if (is_array($data)) { if (!$this->isStructuredType($data)) {
foreach ($data as $key => $val) {
$data[$key] = $this->isStructuredType($val) ? $this->normalize($val, $format) : $val;
}
return $data; return $data;
} }
if ($data instanceof Traversable) {
$normalized = array();
foreach ($data as $key => $val) {
$normalized[$key] = $this->normalize($val, $format);
}
return $normalized;
}
if (is_object($data)) { if (is_object($data)) {
return $this->normalizeObject($data, $format); return $this->normalizeObject($data, $format);
} }
if (is_array($data)) {
foreach ($data as $key => $val) {
$data[$key] = $this->normalize($val, $format);
}
return $data;
}
throw new \UnexpectedValueException('An unexpected value could not be normalized: '.var_export($data, true)); throw new \UnexpectedValueException('An unexpected value could not be normalized: '.var_export($data, true));
} }
/**
* {@inheritDoc}
*/
public function denormalize($data, $type, $format = null)
{
return $this->denormalizeObject($data, $type, $format);
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */

View File

@ -31,6 +31,15 @@ interface SerializerInterface
*/ */
function serialize($data, $format); function serialize($data, $format);
/**
* Deserializes data into the given type.
*
* @param mixed $data
* @param string $type
* @param string $format
*/
function deserialize($data, $type, $format);
/** /**
* Normalizes any data into a set of arrays/scalars * Normalizes any data into a set of arrays/scalars
* *
@ -39,27 +48,18 @@ interface SerializerInterface
* @return array|scalar * @return array|scalar
* @api * @api
*/ */
function normalize($data, $format); function normalize($data, $format = null);
/** /**
* Normalizes an object into a set of arrays/scalars * Denormalizes data into the given type.
* *
* @param object $object object to normalize * @param mixed $data
* @param string $format format name, present to give the option to normalizers to act differently based on formats * @param string $type
* @param array $properties a list of properties to extract, if null all properties are returned * @param string $format
* @return array|scalar
*/
function normalizeObject($object, $format, $properties = null);
/**
* Denormalizes data back into an object of the given class
* *
* @param mixed $data data to restore * @return mixed
* @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
* @return object
*/ */
function denormalizeObject($data, $class, $format = null); function denormalize($data, $type, $format = null);
/** /**
* Encodes data into the given format * Encodes data into the given format
@ -80,48 +80,4 @@ interface SerializerInterface
* @api * @api
*/ */
function decode($data, $format); function decode($data, $format);
/**
* @param NormalizerInterface $normalizer
*/
function addNormalizer(NormalizerInterface $normalizer);
/**
* @return array[]NormalizerInterface
*/
function getNormalizers();
/**
* @param NormalizerInterface $normalizer
*/
function removeNormalizer(NormalizerInterface $normalizer);
/**
* @param string $format format name
* @param EncoderInterface $encoder
*/
function setEncoder($format, EncoderInterface $encoder);
/**
* @return EncoderInterface
*/
function getEncoders();
/**
* @return array[]EncoderInterface
*/
function getEncoder($format);
/**
* Checks whether the serializer has an encoder registered for the given format
*
* @param string $format format name
* @return Boolean
*/
function hasEncoder($format);
/**
* @param string $format format name
*/
function removeEncoder($format);
} }

View File

@ -44,9 +44,15 @@ class CustomNormalizerTest extends \PHPUnit_Framework_TestCase
$this->assertNull($obj->xmlFoo); $this->assertNull($obj->xmlFoo);
} }
public function testSupports() public function testSupportsNormalization()
{ {
$this->assertTrue($this->normalizer->supports(new \ReflectionClass(get_class(new ScalarDummy)))); $this->assertTrue($this->normalizer->supportsNormalization(new ScalarDummy));
$this->assertFalse($this->normalizer->supports(new \ReflectionClass('stdClass'))); $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass));
}
public function testSupportsDenormalization()
{
$this->assertTrue($this->normalizer->supportsDenormalization(array(), 'Symfony\Tests\Component\Serializer\Fixtures\ScalarDummy'));
$this->assertFalse($this->normalizer->supportsDenormalization(array(), 'stdClass'));
} }
} }

View File

@ -29,7 +29,8 @@ class GetSetMethodNormalizerTest extends \PHPUnit_Framework_TestCase
$obj->setBar('bar'); $obj->setBar('bar');
$this->assertEquals( $this->assertEquals(
array('foo' => 'foo', 'bar' => 'bar', 'foobar' => 'foobar'), array('foo' => 'foo', 'bar' => 'bar', 'foobar' => 'foobar'),
$this->normalizer->normalize($obj, 'any')); $this->normalizer->normalize($obj, 'any')
);
} }
public function testNormalizeRestricted() public function testNormalizeRestricted()
@ -39,14 +40,17 @@ class GetSetMethodNormalizerTest extends \PHPUnit_Framework_TestCase
$obj->setBar('bar'); $obj->setBar('bar');
$this->assertEquals( $this->assertEquals(
array('foo' => 'foo'), array('foo' => 'foo'),
$this->normalizer->normalize($obj, 'any', array('foo'))); $this->normalizer->normalize($obj, 'any', array('foo'))
);
} }
public function testDenormalize() public function testDenormalize()
{ {
$obj = $this->normalizer->denormalize( $obj = $this->normalizer->denormalize(
array('foo' => 'foo', 'bar' => 'bar', 'foobar' => 'foobar'), array('foo' => 'foo', 'bar' => 'bar', 'foobar' => 'foobar'),
__NAMESPACE__.'\GetSetDummy', 'any'); __NAMESPACE__.'\GetSetDummy',
'any'
);
$this->assertEquals('foo', $obj->getFoo()); $this->assertEquals('foo', $obj->getFoo());
$this->assertEquals('bar', $obj->getBar()); $this->assertEquals('bar', $obj->getBar());
} }