2014-10-01 16:10:16 +01:00
< ? 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 ;
2015-01-04 14:19:26 +00:00
use Symfony\Component\Serializer\Exception\CircularReferenceException ;
2014-10-01 16:10:16 +01:00
use Symfony\Component\Serializer\Exception\InvalidArgumentException ;
2016-11-29 19:46:44 +00:00
use Symfony\Component\Serializer\Exception\LogicException ;
2015-01-20 21:49:28 +00:00
use Symfony\Component\Serializer\Exception\RuntimeException ;
2015-02-25 22:32:01 +00:00
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface ;
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface ;
2014-12-25 23:45:46 +00:00
use Symfony\Component\Serializer\NameConverter\NameConverterInterface ;
2016-01-26 12:57:16 +00:00
use Symfony\Component\Serializer\SerializerAwareInterface ;
2014-10-01 16:10:16 +01:00
/**
* Normalizer implementation .
*
* @ author Kévin Dunglas < dunglas @ gmail . com >
*/
2016-04-19 12:00:54 +01:00
abstract class AbstractNormalizer extends SerializerAwareNormalizer implements NormalizerInterface , DenormalizerInterface , SerializerAwareInterface
2014-10-01 16:10:16 +01:00
{
2016-01-18 16:42:55 +00:00
const CIRCULAR_REFERENCE_LIMIT = 'circular_reference_limit' ;
const OBJECT_TO_POPULATE = 'object_to_populate' ;
const GROUPS = 'groups' ;
2017-04-27 16:42:57 +01:00
const ATTRIBUTES = 'attributes' ;
2016-01-18 16:42:55 +00:00
2015-02-25 22:32:01 +00:00
/**
* @ var int
*/
2015-01-04 14:19:26 +00:00
protected $circularReferenceLimit = 1 ;
2016-01-18 21:15:26 +00:00
2015-02-25 22:32:01 +00:00
/**
* @ var callable
*/
2015-01-04 14:19:26 +00:00
protected $circularReferenceHandler ;
2016-01-18 21:15:26 +00:00
2015-02-25 22:32:01 +00:00
/**
* @ var ClassMetadataFactoryInterface | null
*/
2014-10-01 16:10:16 +01:00
protected $classMetadataFactory ;
2016-01-18 21:15:26 +00:00
2015-02-25 22:32:01 +00:00
/**
* @ var NameConverterInterface | null
*/
2014-12-25 23:45:46 +00:00
protected $nameConverter ;
2016-01-18 21:15:26 +00:00
2015-02-25 22:32:01 +00:00
/**
* @ var array
*/
2014-10-01 16:10:16 +01:00
protected $callbacks = array ();
2016-01-18 21:15:26 +00:00
2015-02-25 22:32:01 +00:00
/**
* @ var array
*/
2014-10-01 16:10:16 +01:00
protected $ignoredAttributes = array ();
2016-01-18 21:15:26 +00:00
2015-02-25 22:32:01 +00:00
/**
* @ var array
*/
2014-10-01 16:10:16 +01:00
protected $camelizedAttributes = array ();
/**
2015-02-25 22:32:01 +00:00
* Sets the { @ link ClassMetadataFactoryInterface } to use .
2014-10-01 16:10:16 +01:00
*
2015-05-21 07:43:39 +01:00
* @ param ClassMetadataFactoryInterface | null $classMetadataFactory
* @ param NameConverterInterface | null $nameConverter
2014-10-01 16:10:16 +01:00
*/
2015-02-25 22:32:01 +00:00
public function __construct ( ClassMetadataFactoryInterface $classMetadataFactory = null , NameConverterInterface $nameConverter = null )
2014-10-01 16:10:16 +01:00
{
$this -> classMetadataFactory = $classMetadataFactory ;
2014-12-25 23:45:46 +00:00
$this -> nameConverter = $nameConverter ;
2014-10-01 16:10:16 +01:00
}
2015-01-04 14:19:26 +00:00
/**
* Set circular reference limit .
*
2015-01-18 19:33:52 +00:00
* @ param int $circularReferenceLimit limit of iterations for the same object
2015-01-04 14:19:26 +00:00
*
* @ return self
*/
public function setCircularReferenceLimit ( $circularReferenceLimit )
{
$this -> circularReferenceLimit = $circularReferenceLimit ;
return $this ;
}
/**
* Set circular reference handler .
*
* @ param callable $circularReferenceHandler
*
* @ return self
*/
2015-04-12 20:00:20 +01:00
public function setCircularReferenceHandler ( callable $circularReferenceHandler )
2015-01-04 14:19:26 +00:00
{
$this -> circularReferenceHandler = $circularReferenceHandler ;
return $this ;
}
2014-10-01 16:10:16 +01:00
/**
* Set normalization callbacks .
*
2015-10-05 17:52:37 +01:00
* @ param callable [] $callbacks help normalize the result
2014-10-01 16:10:16 +01:00
*
* @ return self
*
* @ throws InvalidArgumentException if a non - callable callback is set
*/
public function setCallbacks ( array $callbacks )
{
foreach ( $callbacks as $attribute => $callback ) {
if ( ! is_callable ( $callback )) {
throw new InvalidArgumentException ( sprintf (
'The given callback for attribute "%s" is not callable.' ,
$attribute
));
}
}
$this -> callbacks = $callbacks ;
return $this ;
}
/**
* Set ignored attributes for normalization and denormalization .
*
* @ param array $ignoredAttributes
*
* @ return self
*/
public function setIgnoredAttributes ( array $ignoredAttributes )
{
$this -> ignoredAttributes = $ignoredAttributes ;
return $this ;
}
2015-01-04 14:19:26 +00:00
/**
* Detects if the configured circular reference limit is reached .
*
* @ param object $object
* @ param array $context
*
* @ return bool
*
* @ throws CircularReferenceException
*/
protected function isCircularReference ( $object , & $context )
{
$objectHash = spl_object_hash ( $object );
2016-01-18 16:42:55 +00:00
if ( isset ( $context [ static :: CIRCULAR_REFERENCE_LIMIT ][ $objectHash ])) {
if ( $context [ static :: CIRCULAR_REFERENCE_LIMIT ][ $objectHash ] >= $this -> circularReferenceLimit ) {
unset ( $context [ static :: CIRCULAR_REFERENCE_LIMIT ][ $objectHash ]);
2015-01-04 14:19:26 +00:00
return true ;
}
2016-01-18 16:42:55 +00:00
++ $context [ static :: CIRCULAR_REFERENCE_LIMIT ][ $objectHash ];
2015-01-04 14:19:26 +00:00
} else {
2016-01-18 16:42:55 +00:00
$context [ static :: CIRCULAR_REFERENCE_LIMIT ][ $objectHash ] = 1 ;
2015-01-04 14:19:26 +00:00
}
return false ;
}
/**
* Handles a circular reference .
*
* If a circular reference handler is set , it will be called . Otherwise , a
* { @ class CircularReferenceException } will be thrown .
*
* @ param object $object
*
* @ return mixed
*
* @ throws CircularReferenceException
*/
protected function handleCircularReference ( $object )
{
if ( $this -> circularReferenceHandler ) {
return call_user_func ( $this -> circularReferenceHandler , $object );
}
throw new CircularReferenceException ( sprintf ( 'A circular reference has been detected (configured limit: %d).' , $this -> circularReferenceLimit ));
}
2014-10-01 16:10:16 +01:00
/**
* Gets attributes to normalize using groups .
*
* @ param string | object $classOrObject
2015-02-25 22:32:01 +00:00
* @ param array $context
* @ param bool $attributesAsString If false , return an array of { @ link AttributeMetadataInterface }
*
* @ return string [] | AttributeMetadataInterface [] | bool
2014-10-01 16:10:16 +01:00
*/
2015-02-25 22:32:01 +00:00
protected function getAllowedAttributes ( $classOrObject , array $context , $attributesAsString = false )
2014-10-01 16:10:16 +01:00
{
2016-01-18 16:42:55 +00:00
if ( ! $this -> classMetadataFactory || ! isset ( $context [ static :: GROUPS ]) || ! is_array ( $context [ static :: GROUPS ])) {
2014-10-01 16:10:16 +01:00
return false ;
}
$allowedAttributes = array ();
2015-02-25 22:32:01 +00:00
foreach ( $this -> classMetadataFactory -> getMetadataFor ( $classOrObject ) -> getAttributesMetadata () as $attributeMetadata ) {
2015-12-30 23:01:59 +00:00
$name = $attributeMetadata -> getName ();
if (
2016-01-20 12:19:44 +00:00
count ( array_intersect ( $attributeMetadata -> getGroups (), $context [ static :: GROUPS ])) &&
2015-12-30 23:01:59 +00:00
$this -> isAllowedAttribute ( $classOrObject , $name , null , $context )
) {
$allowedAttributes [] = $attributesAsString ? $name : $attributeMetadata ;
2014-10-01 16:10:16 +01:00
}
}
2015-11-03 20:41:27 +00:00
return $allowedAttributes ;
2014-10-01 16:10:16 +01:00
}
2015-01-04 20:11:42 +00:00
2015-12-30 23:01:59 +00:00
/**
* Is this attribute allowed ?
*
* @ param object | string $classOrObject
* @ param string $attribute
* @ param string | null $format
* @ param array $context
*
* @ return bool
*/
protected function isAllowedAttribute ( $classOrObject , $attribute , $format = null , array $context = array ())
{
2016-05-22 08:54:16 +01:00
if ( in_array ( $attribute , $this -> ignoredAttributes )) {
return false ;
}
2017-04-27 16:42:57 +01:00
if ( isset ( $context [ self :: ATTRIBUTES ][ $attribute ])) {
2016-05-22 08:54:16 +01:00
// Nested attributes
return true ;
}
2017-04-27 16:42:57 +01:00
if ( isset ( $context [ self :: ATTRIBUTES ]) && is_array ( $context [ self :: ATTRIBUTES ])) {
return in_array ( $attribute , $context [ self :: ATTRIBUTES ], true );
2016-05-22 08:54:16 +01:00
}
return true ;
2015-12-30 23:01:59 +00:00
}
2015-01-04 20:11:42 +00:00
/**
* Normalizes the given data to an array . It ' s particularly useful during
* the denormalization process .
*
* @ param object | array $data
*
* @ return array
*/
protected function prepareForDenormalization ( $data )
{
2015-07-19 13:40:25 +01:00
return ( array ) $data ;
2015-01-04 20:11:42 +00:00
}
2016-06-21 23:22:25 +01:00
/**
* Returns the method to use to construct an object . This method must be either
* the object constructor or static .
*
* @ param array $data
* @ param string $class
* @ param array $context
* @ param \ReflectionClass $reflectionClass
* @ param array | bool $allowedAttributes
*
* @ return \ReflectionMethod | null
*/
protected function getConstructor ( array & $data , $class , array & $context , \ReflectionClass $reflectionClass , $allowedAttributes )
{
return $reflectionClass -> getConstructor ();
}
2015-01-04 20:11:42 +00:00
/**
2015-06-18 16:07:16 +01:00
* Instantiates an object using constructor parameters when needed .
2015-01-04 20:11:42 +00:00
*
* This method also allows to denormalize data into an existing object if
2016-01-06 06:50:20 +00:00
* it is present in the context with the object_to_populate . This object
* is removed from the context before being returned to avoid side effects
* when recursively normalizing an object graph .
2015-01-04 20:11:42 +00:00
*
* @ param array $data
* @ param string $class
* @ param array $context
* @ param \ReflectionClass $reflectionClass
* @ param array | bool $allowedAttributes
2016-07-03 12:09:45 +01:00
* @ param string | null $format
2015-01-04 20:11:42 +00:00
*
* @ return object
*
* @ throws RuntimeException
*/
2016-07-10 17:21:22 +01:00
protected function instantiateObject ( array & $data , $class , array & $context , \ReflectionClass $reflectionClass , $allowedAttributes /*, $format = null*/ )
2015-01-04 20:11:42 +00:00
{
2016-11-04 13:53:25 +00:00
if ( func_num_args () >= 6 ) {
$format = func_get_arg ( 5 );
} else {
if ( __CLASS__ !== get_class ( $this )) {
$r = new \ReflectionMethod ( $this , __FUNCTION__ );
if ( __CLASS__ !== $r -> getDeclaringClass () -> getName ()) {
2017-02-28 14:23:14 +00:00
@ trigger_error ( sprintf ( 'Method %s::%s() will have a 6th `$format = null` argument in version 4.0. Not defining it is deprecated since 3.2.' , get_class ( $this ), __FUNCTION__ ), E_USER_DEPRECATED );
2016-11-04 13:53:25 +00:00
}
}
$format = null ;
}
2016-07-10 17:21:22 +01:00
2015-01-04 20:11:42 +00:00
if (
2016-01-18 16:42:55 +00:00
isset ( $context [ static :: OBJECT_TO_POPULATE ]) &&
is_object ( $context [ static :: OBJECT_TO_POPULATE ]) &&
$context [ static :: OBJECT_TO_POPULATE ] instanceof $class
2015-01-04 20:11:42 +00:00
) {
2016-01-18 16:42:55 +00:00
$object = $context [ static :: OBJECT_TO_POPULATE ];
unset ( $context [ static :: OBJECT_TO_POPULATE ]);
2016-01-06 06:50:20 +00:00
return $object ;
2015-01-04 20:11:42 +00:00
}
2016-06-21 23:22:25 +01:00
$constructor = $this -> getConstructor ( $data , $class , $context , $reflectionClass , $allowedAttributes );
2015-01-04 20:11:42 +00:00
if ( $constructor ) {
$constructorParameters = $constructor -> getParameters ();
$params = array ();
foreach ( $constructorParameters as $constructorParameter ) {
2014-12-25 23:45:46 +00:00
$paramName = $constructorParameter -> name ;
$key = $this -> nameConverter ? $this -> nameConverter -> normalize ( $paramName ) : $paramName ;
2015-01-04 20:11:42 +00:00
$allowed = $allowedAttributes === false || in_array ( $paramName , $allowedAttributes );
2016-05-22 08:54:16 +01:00
$ignored = ! $this -> isAllowedAttribute ( $class , $paramName , $format , $context );
2015-08-11 08:58:34 +01:00
if ( method_exists ( $constructorParameter , 'isVariadic' ) && $constructorParameter -> isVariadic ()) {
if ( $allowed && ! $ignored && ( isset ( $data [ $key ]) || array_key_exists ( $key , $data ))) {
if ( ! is_array ( $data [ $paramName ])) {
throw new RuntimeException ( sprintf ( 'Cannot create an instance of %s from serialized data because the variadic parameter %s can only accept an array.' , $class , $constructorParameter -> name ));
}
$params = array_merge ( $params , $data [ $paramName ]);
}
2016-07-06 17:37:20 +01:00
} elseif ( $allowed && ! $ignored && ( isset ( $data [ $key ]) || array_key_exists ( $key , $data ))) {
2016-07-03 11:11:08 +01:00
$parameterData = $data [ $key ];
2016-07-10 18:16:12 +01:00
try {
if ( null !== $constructorParameter -> getClass ()) {
2016-11-29 19:46:44 +00:00
if ( ! $this -> serializer instanceof DenormalizerInterface ) {
throw new LogicException ( sprintf ( 'Cannot create an instance of %s from serialized data because the serializer inject in "%s" is not a denormalizer' , $constructorParameter -> getClass (), static :: class ));
}
2016-07-06 17:52:20 +01:00
$parameterClass = $constructorParameter -> getClass () -> getName ();
2016-05-22 08:54:16 +01:00
$parameterData = $this -> serializer -> denormalize ( $parameterData , $parameterClass , $format , $this -> createChildContext ( $context , $paramName ));
2016-07-06 17:52:20 +01:00
}
2016-07-10 18:16:12 +01:00
} catch ( \ReflectionException $e ) {
throw new RuntimeException ( sprintf ( 'Could not determine the class of the parameter "%s".' , $key ), 0 , $e );
2016-07-03 11:11:08 +01:00
}
// Don't run set for a parameter passed to the constructor
$params [] = $parameterData ;
2016-07-03 12:09:45 +01:00
unset ( $data [ $key ]);
2016-07-06 17:37:20 +01:00
} elseif ( $constructorParameter -> isDefaultValueAvailable ()) {
2015-01-04 20:11:42 +00:00
$params [] = $constructorParameter -> getDefaultValue ();
2016-07-06 17:37:20 +01:00
} else {
throw new RuntimeException (
sprintf (
'Cannot create an instance of %s from serialized data because its constructor requires parameter "%s" to be present.' ,
$class ,
$constructorParameter -> name
)
);
2015-01-04 20:11:42 +00:00
}
}
2016-06-21 23:22:25 +01:00
if ( $constructor -> isConstructor ()) {
return $reflectionClass -> newInstanceArgs ( $params );
2016-07-06 17:37:20 +01:00
} else {
return $constructor -> invokeArgs ( null , $params );
2016-06-21 23:22:25 +01:00
}
2015-01-04 20:11:42 +00:00
}
return new $class ();
}
2016-05-22 08:54:16 +01:00
/**
* @ param array $parentContext
* @ param string $attribute
*
* @ return array
*
* @ internal
*/
protected function createChildContext ( array $parentContext , $attribute )
{
2017-04-27 16:42:57 +01:00
if ( isset ( $parentContext [ self :: ATTRIBUTES ][ $attribute ])) {
$parentContext [ self :: ATTRIBUTES ] = $parentContext [ self :: ATTRIBUTES ][ $attribute ];
2016-05-22 08:54:16 +01:00
}
return $parentContext ;
}
2014-10-01 16:10:16 +01:00
}