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 ;
2015-02-03 22:24:43 +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\CamelCaseToSnakeCaseNameConverter ;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface ;
2014-10-01 16:10:16 +01:00
/**
* Normalizer implementation .
*
* @ author Kévin Dunglas < dunglas @ gmail . com >
*/
abstract class AbstractNormalizer extends SerializerAwareNormalizer implements NormalizerInterface , DenormalizerInterface
{
2015-02-25 22:32:01 +00:00
/**
* @ var int
*/
2015-01-04 14:19:26 +00:00
protected $circularReferenceLimit = 1 ;
2015-02-25 22:32:01 +00:00
/**
* @ var callable
*/
2015-01-04 14:19:26 +00:00
protected $circularReferenceHandler ;
2015-02-25 22:32:01 +00:00
/**
* @ var ClassMetadataFactoryInterface | null
*/
2014-10-01 16:10:16 +01:00
protected $classMetadataFactory ;
2015-02-25 22:32:01 +00:00
/**
* @ var NameConverterInterface | null
*/
2014-12-25 23:45:46 +00:00
protected $nameConverter ;
2015-02-25 22:32:01 +00:00
/**
* @ var array
*/
2014-10-01 16:10:16 +01:00
protected $callbacks = array ();
2015-02-25 22:32:01 +00:00
/**
* @ var array
*/
2014-10-01 16:10:16 +01:00
protected $ignoredAttributes = array ();
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
*
* @ throws InvalidArgumentException
*/
public function setCircularReferenceHandler ( $circularReferenceHandler )
{
if ( ! is_callable ( $circularReferenceHandler )) {
throw new InvalidArgumentException ( 'The given circular reference handler is not callable.' );
}
$this -> circularReferenceHandler = $circularReferenceHandler ;
return $this ;
}
2014-10-01 16:10:16 +01:00
/**
* Set normalization callbacks .
*
* @ param array $callbacks help normalize the result
*
* @ 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 ;
}
/**
* Set attributes to be camelized on denormalize .
*
2014-12-25 23:45:46 +00:00
* @ deprecated Deprecated since version 2.7 , to be removed in 3.0 . Use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter instead .
*
2014-10-01 16:10:16 +01:00
* @ param array $camelizedAttributes
*
* @ return self
2015-02-03 22:24:43 +00:00
*
* @ throws LogicException
2014-10-01 16:10:16 +01:00
*/
public function setCamelizedAttributes ( array $camelizedAttributes )
{
2015-06-07 07:33:05 +01:00
@ trigger_error ( sprintf ( '%s is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter instead.' , __METHOD__ ), E_USER_DEPRECATED );
2014-12-25 23:45:46 +00:00
if ( $this -> nameConverter && ! $this -> nameConverter instanceof CamelCaseToSnakeCaseNameConverter ) {
2015-02-03 22:24:43 +00:00
throw new LogicException ( sprintf ( '%s cannot be called if a custom Name Converter is defined.' , __METHOD__ ));
2014-12-25 23:45:46 +00:00
}
$attributes = array ();
foreach ( $camelizedAttributes as $camelizedAttribute ) {
$attributes [] = lcfirst ( preg_replace_callback ( '/(^|_|\.)+(.)/' , function ( $match ) {
return ( '.' === $match [ 1 ] ? '_' : '' ) . strtoupper ( $match [ 2 ]);
}, $camelizedAttribute ));
}
$this -> nameConverter = new CamelCaseToSnakeCaseNameConverter ( $attributes );
2014-10-01 16:10:16 +01:00
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 );
if ( isset ( $context [ 'circular_reference_limit' ][ $objectHash ])) {
if ( $context [ 'circular_reference_limit' ][ $objectHash ] >= $this -> circularReferenceLimit ) {
unset ( $context [ 'circular_reference_limit' ][ $objectHash ]);
return true ;
}
$context [ 'circular_reference_limit' ][ $objectHash ] ++ ;
} else {
$context [ 'circular_reference_limit' ][ $objectHash ] = 1 ;
}
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
/**
* Format an attribute name , for example to convert a snake_case name to camelCase .
*
2014-12-25 23:45:46 +00:00
* @ deprecated Deprecated since version 2.7 , to be removed in 3.0 . Use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter instead .
*
2014-10-01 16:10:16 +01:00
* @ param string $attributeName
2014-12-25 23:45:46 +00:00
*
2014-10-01 16:10:16 +01:00
* @ return string
*/
protected function formatAttribute ( $attributeName )
{
2015-06-07 07:33:05 +01:00
@ trigger_error ( sprintf ( '%s is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter instead.' , __METHOD__ ), E_USER_DEPRECATED );
2014-10-01 16:10:16 +01:00
2014-12-25 23:45:46 +00:00
return $this -> nameConverter ? $this -> nameConverter -> normalize ( $attributeName ) : $attributeName ;
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
{
if ( ! $this -> classMetadataFactory || ! isset ( $context [ 'groups' ]) || ! is_array ( $context [ 'groups' ])) {
return false ;
}
$allowedAttributes = array ();
2015-02-25 22:32:01 +00:00
foreach ( $this -> classMetadataFactory -> getMetadataFor ( $classOrObject ) -> getAttributesMetadata () as $attributeMetadata ) {
if ( count ( array_intersect ( $attributeMetadata -> getGroups (), $context [ 'groups' ]))) {
$allowedAttributes [] = $attributesAsString ? $attributeMetadata -> getName () : $attributeMetadata ;
2014-10-01 16:10:16 +01:00
}
}
return array_unique ( $allowedAttributes );
}
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
}
/**
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
* it is present in the context with the object_to_populate key .
*
* @ param array $data
* @ param string $class
* @ param array $context
* @ param \ReflectionClass $reflectionClass
* @ param array | bool $allowedAttributes
*
* @ return object
*
* @ throws RuntimeException
*/
2015-07-22 20:42:44 +01:00
protected function instantiateObject ( array & $data , $class , array & $context , \ReflectionClass $reflectionClass , $allowedAttributes )
2015-01-04 20:11:42 +00:00
{
if (
isset ( $context [ 'object_to_populate' ]) &&
is_object ( $context [ 'object_to_populate' ]) &&
$class === get_class ( $context [ 'object_to_populate' ])
) {
return $context [ 'object_to_populate' ];
}
$constructor = $reflectionClass -> getConstructor ();
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 );
$ignored = in_array ( $paramName , $this -> ignoredAttributes );
2015-05-21 07:43:39 +01:00
if ( $allowed && ! $ignored && array_key_exists ( $key , $data )) {
2014-12-25 23:45:46 +00:00
$params [] = $data [ $key ];
2015-01-04 20:11:42 +00:00
// don't run set for a parameter passed to the constructor
2014-12-25 23:45:46 +00:00
unset ( $data [ $key ]);
2015-05-25 00:36:47 +01:00
} elseif ( $constructorParameter -> isDefaultValueAvailable ()) {
2015-01-04 20:11:42 +00:00
$params [] = $constructorParameter -> getDefaultValue ();
} 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
)
);
}
}
return $reflectionClass -> newInstanceArgs ( $params );
}
return new $class ();
}
2014-10-01 16:10:16 +01:00
}