2015-08-17 17:00:26 -07: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 ;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException ;
use Symfony\Component\PropertyAccess\PropertyAccess ;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface ;
use Symfony\Component\Serializer\Exception\CircularReferenceException ;
use Symfony\Component\Serializer\Exception\LogicException ;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface ;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface ;
/**
* Converts between objects and arrays using the PropertyAccess component .
*
* @ author Kévin Dunglas < dunglas @ gmail . com >
*/
class ObjectNormalizer extends AbstractNormalizer
{
2016-04-20 09:56:34 -07:00
private $attributesCache = array ();
2015-08-17 17:00:26 -07:00
/**
* @ var PropertyAccessorInterface
*/
protected $propertyAccessor ;
public function __construct ( ClassMetadataFactoryInterface $classMetadataFactory = null , NameConverterInterface $nameConverter = null , PropertyAccessorInterface $propertyAccessor = null )
{
parent :: __construct ( $classMetadataFactory , $nameConverter );
$this -> propertyAccessor = $propertyAccessor ? : PropertyAccess :: createPropertyAccessor ();
}
/**
* { @ inheritdoc }
*/
public function supportsNormalization ( $data , $format = null )
{
return is_object ( $data ) && ! $data instanceof \Traversable ;
}
/**
* { @ inheritdoc }
*
* @ throws CircularReferenceException
*/
public function normalize ( $object , $format = null , array $context = array ())
{
2016-04-20 09:56:34 -07:00
if ( ! isset ( $context [ 'cache_key' ])) {
$context [ 'cache_key' ] = $this -> getCacheKey ( $context );
}
2015-08-17 17:00:26 -07:00
if ( $this -> isCircularReference ( $object , $context )) {
return $this -> handleCircularReference ( $object );
}
$data = array ();
2016-04-20 09:56:34 -07:00
$attributes = $this -> getAttributes ( $object , $context );
2015-08-17 17:00:26 -07:00
foreach ( $attributes as $attribute ) {
if ( in_array ( $attribute , $this -> ignoredAttributes )) {
continue ;
}
$attributeValue = $this -> propertyAccessor -> getValue ( $object , $attribute );
if ( isset ( $this -> callbacks [ $attribute ])) {
$attributeValue = call_user_func ( $this -> callbacks [ $attribute ], $attributeValue );
}
if ( null !== $attributeValue && ! is_scalar ( $attributeValue )) {
if ( ! $this -> serializer instanceof NormalizerInterface ) {
throw new LogicException ( sprintf ( 'Cannot normalize attribute "%s" because injected serializer is not a normalizer' , $attribute ));
}
$attributeValue = $this -> serializer -> normalize ( $attributeValue , $format , $context );
}
if ( $this -> nameConverter ) {
$attribute = $this -> nameConverter -> normalize ( $attribute );
}
$data [ $attribute ] = $attributeValue ;
}
return $data ;
}
/**
* { @ inheritdoc }
*/
public function supportsDenormalization ( $data , $type , $format = null )
{
return class_exists ( $type );
}
/**
* { @ inheritdoc }
*/
public function denormalize ( $data , $class , $format = null , array $context = array ())
{
2016-04-20 09:56:34 -07:00
if ( ! isset ( $context [ 'cache_key' ])) {
$context [ 'cache_key' ] = $this -> getCacheKey ( $context );
}
2015-08-17 17:00:26 -07:00
$allowedAttributes = $this -> getAllowedAttributes ( $class , $context , true );
$normalizedData = $this -> prepareForDenormalization ( $data );
$reflectionClass = new \ReflectionClass ( $class );
$object = $this -> instantiateObject ( $normalizedData , $class , $context , $reflectionClass , $allowedAttributes );
foreach ( $normalizedData as $attribute => $value ) {
if ( $this -> nameConverter ) {
$attribute = $this -> nameConverter -> denormalize ( $attribute );
}
$allowed = $allowedAttributes === false || in_array ( $attribute , $allowedAttributes );
$ignored = in_array ( $attribute , $this -> ignoredAttributes );
if ( $allowed && ! $ignored ) {
try {
$this -> propertyAccessor -> setValue ( $object , $attribute , $value );
} catch ( NoSuchPropertyException $exception ) {
// Properties not found are ignored
}
}
}
return $object ;
}
2016-04-20 09:56:34 -07:00
private function getCacheKey ( array $context )
{
try {
return md5 ( serialize ( $context ));
} catch ( \Exception $exception ) {
// The context cannot be serialized, skip the cache
return false ;
}
}
/**
* Gets and caches attributes for this class and context .
*
* @ param object $object
* @ param array $context
*
* @ return string []
*/
private function getAttributes ( $object , array $context )
{
$class = get_class ( $object );
$key = $class . '-' . $context [ 'cache_key' ];
if ( isset ( $this -> attributesCache [ $key ])) {
return $this -> attributesCache [ $key ];
}
$allowedAttributes = $this -> getAllowedAttributes ( $object , $context , true );
if ( false !== $allowedAttributes ) {
if ( $context [ 'cache_key' ]) {
$this -> attributesCache [ $key ] = $allowedAttributes ;
}
return $allowedAttributes ;
}
if ( isset ( $this -> attributesCache [ $class ])) {
return $this -> attributesCache [ $class ];
}
return $this -> attributesCache [ $class ] = $this -> extractAttributes ( $object );
}
/**
* Extracts attributes for this class and context .
*
* @ param object $object
*
* @ return string []
*/
private function extractAttributes ( $object )
{
// If not using groups, detect manually
$attributes = array ();
// methods
$reflClass = new \ReflectionClass ( $object );
foreach ( $reflClass -> getMethods ( \ReflectionMethod :: IS_PUBLIC ) as $reflMethod ) {
if (
$reflMethod -> getNumberOfRequiredParameters () !== 0 ||
$reflMethod -> isStatic () ||
$reflMethod -> isConstructor () ||
$reflMethod -> isDestructor ()
) {
continue ;
}
$name = $reflMethod -> name ;
if ( 0 === strpos ( $name , 'get' ) || 0 === strpos ( $name , 'has' )) {
// getters and hassers
$attributes [ lcfirst ( substr ( $name , 3 ))] = true ;
} elseif ( strpos ( $name , 'is' ) === 0 ) {
// issers
$attributes [ lcfirst ( substr ( $name , 2 ))] = true ;
}
}
// properties
foreach ( $reflClass -> getProperties ( \ReflectionProperty :: IS_PUBLIC ) as $reflProperty ) {
if ( $reflProperty -> isStatic ()) {
continue ;
}
$attributes [ $reflProperty -> name ] = true ;
}
return array_keys ( $attributes );
}
2015-08-17 17:00:26 -07:00
}