* * 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\InvalidArgumentException; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Exception\ExtraAttributesException; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; /** * Base class for a normalizer dealing with objects. * * @author Kévin Dunglas */ abstract class AbstractObjectNormalizer extends AbstractNormalizer { const ENABLE_MAX_DEPTH = 'enable_max_depth'; const DEPTH_KEY_PATTERN = 'depth_%s::%s'; const DISABLE_TYPE_ENFORCEMENT = 'disable_type_enforcement'; private $propertyTypeExtractor; private $attributesCache = array(); private $cache = array(); public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null) { parent::__construct($classMetadataFactory, $nameConverter); $this->propertyTypeExtractor = $propertyTypeExtractor; } /** * {@inheritdoc} */ public function supportsNormalization($data, $format = null) { return \is_object($data) && !$data instanceof \Traversable; } /** * {@inheritdoc} */ public function normalize($object, $format = null, array $context = array()) { if (!isset($context['cache_key'])) { $context['cache_key'] = $this->getCacheKey($format, $context); } if ($this->isCircularReference($object, $context)) { return $this->handleCircularReference($object); } $data = array(); $stack = array(); $attributes = $this->getAttributes($object, $format, $context); $class = get_class($object); $attributesMetadata = $this->classMetadataFactory ? $this->classMetadataFactory->getMetadataFor($class)->getAttributesMetadata() : null; foreach ($attributes as $attribute) { if (null !== $attributesMetadata && $this->isMaxDepthReached($attributesMetadata, $class, $attribute, $context)) { continue; } $attributeValue = $this->getAttributeValue($object, $attribute, $format, $context); if (isset($this->callbacks[$attribute])) { $attributeValue = call_user_func($this->callbacks[$attribute], $attributeValue); } if (null !== $attributeValue && !is_scalar($attributeValue)) { $stack[$attribute] = $attributeValue; } $data = $this->updateData($data, $attribute, $attributeValue); } foreach ($stack as $attribute => $attributeValue) { if (!$this->serializer instanceof NormalizerInterface) { throw new LogicException(sprintf('Cannot normalize attribute "%s" because the injected serializer is not a normalizer', $attribute)); } $data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute))); } return $data; } /** * Gets and caches attributes for the given object, format and context. * * @param object $object * @param string|null $format * @param array $context * * @return string[] */ protected function getAttributes($object, $format = null, 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($context['attributes'])) { return $this->extractAttributes($object, $format, $context); } if (isset($this->attributesCache[$class])) { return $this->attributesCache[$class]; } return $this->attributesCache[$class] = $this->extractAttributes($object, $format, $context); } /** * Extracts attributes to normalize from the class of the given object, format and context. * * @param object $object * @param string|null $format * @param array $context * * @return string[] */ abstract protected function extractAttributes($object, $format = null, array $context = array()); /** * Gets the attribute value. * * @param object $object * @param string $attribute * @param string|null $format * @param array $context * * @return mixed */ abstract protected function getAttributeValue($object, $attribute, $format = null, array $context = array()); /** * {@inheritdoc} */ public function supportsDenormalization($data, $type, $format = null) { return isset($this->cache[$type]) ? $this->cache[$type] : $this->cache[$type] = class_exists($type); } /** * {@inheritdoc} */ public function denormalize($data, $class, $format = null, array $context = array()) { if (!isset($context['cache_key'])) { $context['cache_key'] = $this->getCacheKey($format, $context); } $allowedAttributes = $this->getAllowedAttributes($class, $context, true); $normalizedData = $this->prepareForDenormalization($data); $extraAttributes = array(); $reflectionClass = new \ReflectionClass($class); $object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes, $format); foreach ($normalizedData as $attribute => $value) { if ($this->nameConverter) { $attribute = $this->nameConverter->denormalize($attribute); } if ((false !== $allowedAttributes && !in_array($attribute, $allowedAttributes)) || !$this->isAllowedAttribute($class, $attribute, $format, $context)) { if (isset($context[self::ALLOW_EXTRA_ATTRIBUTES]) && !$context[self::ALLOW_EXTRA_ATTRIBUTES]) { $extraAttributes[] = $attribute; } continue; } $value = $this->validateAndDenormalize($class, $attribute, $value, $format, $context); try { $this->setAttributeValue($object, $attribute, $value, $format, $context); } catch (InvalidArgumentException $e) { throw new NotNormalizableValueException($e->getMessage(), $e->getCode(), $e); } } if (!empty($extraAttributes)) { throw new ExtraAttributesException($extraAttributes); } return $object; } /** * Sets attribute value. * * @param object $object * @param string $attribute * @param mixed $value * @param string|null $format * @param array $context */ abstract protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = array()); /** * Validates the submitted data and denormalizes it. * * @param string $currentClass * @param string $attribute * @param mixed $data * @param string|null $format * @param array $context * * @return mixed * * @throws NotNormalizableValueException * @throws LogicException */ private function validateAndDenormalize($currentClass, $attribute, $data, $format, array $context) { if (null === $this->propertyTypeExtractor || null === $types = $this->propertyTypeExtractor->getTypes($currentClass, $attribute)) { return $data; } $expectedTypes = array(); foreach ($types as $type) { if (null === $data && $type->isNullable()) { return; } if ($type->isCollection() && null !== ($collectionValueType = $type->getCollectionValueType()) && Type::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) { $builtinType = Type::BUILTIN_TYPE_OBJECT; $class = $collectionValueType->getClassName().'[]'; if (null !== $collectionKeyType = $type->getCollectionKeyType()) { $context['key_type'] = $collectionKeyType; } } else { $builtinType = $type->getBuiltinType(); $class = $type->getClassName(); } $expectedTypes[Type::BUILTIN_TYPE_OBJECT === $builtinType && $class ? $class : $builtinType] = true; if (Type::BUILTIN_TYPE_OBJECT === $builtinType) { if (!$this->serializer instanceof DenormalizerInterface) { throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer', $attribute, $class)); } $childContext = $this->createChildContext($context, $attribute); if ($this->serializer->supportsDenormalization($data, $class, $format, $childContext)) { return $this->serializer->denormalize($data, $class, $format, $childContext); } } // JSON only has a Number type corresponding to both int and float PHP types. // PHP's json_encode, JavaScript's JSON.stringify, Go's json.Marshal as well as most other JSON encoders convert // floating-point numbers like 12.0 to 12 (the decimal part is dropped when possible). // PHP's json_decode automatically converts Numbers without a decimal part to integers. // To circumvent this behavior, integers are converted to floats when denormalizing JSON based formats and when // a float is expected. if (Type::BUILTIN_TYPE_FLOAT === $builtinType && is_int($data) && false !== strpos($format, JsonEncoder::FORMAT)) { return (float) $data; } if (call_user_func('is_'.$builtinType, $data)) { return $data; } } if (!empty($context[self::DISABLE_TYPE_ENFORCEMENT])) { return $data; } throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), gettype($data))); } /** * Sets an attribute and apply the name converter if necessary. * * @param string $attribute * @param mixed $attributeValue * * @return array */ private function updateData(array $data, $attribute, $attributeValue) { if ($this->nameConverter) { $attribute = $this->nameConverter->normalize($attribute); } $data[$attribute] = $attributeValue; return $data; } /** * Is the max depth reached for the given attribute? * * @param AttributeMetadataInterface[] $attributesMetadata * @param string $class * @param string $attribute * @param array $context * * @return bool */ private function isMaxDepthReached(array $attributesMetadata, $class, $attribute, array &$context) { if ( !isset($context[static::ENABLE_MAX_DEPTH]) || !isset($attributesMetadata[$attribute]) || null === $maxDepth = $attributesMetadata[$attribute]->getMaxDepth() ) { return false; } $key = sprintf(static::DEPTH_KEY_PATTERN, $class, $attribute); if (!isset($context[$key])) { $context[$key] = 1; return false; } if ($context[$key] === $maxDepth) { return true; } ++$context[$key]; return false; } /** * Gets the cache key to use. * * @param string|null $format * @param array $context * * @return bool|string */ private function getCacheKey($format, array $context) { try { return md5($format.serialize($context)); } catch (\Exception $exception) { // The context cannot be serialized, skip the cache return false; } } } __halt_compiler();----SIGNATURE:----sY/eyy77ZvPPdzlf94oXqLpL2v+MPVDvK7RYKjDsWkXUxlDFCUGfmyhGaDD9ghPjkY+mQhE7/rE2AmztRV7q1POHEdlSKPebuTeSC911A4PmbRGFT6Kj9pP/unefSM6u8oxN5cfpV3ADvN2uVXg5j4mR00WIHgkoSBi2ZCEAIb1wPMeB1q/uMA7RmVHLW1/RYXyTf8ADopl+p0IBvVQHVSKCPbp72zfHacvYjhpTT8Ik8AzniUy+aeEFe8R9xYvChE1eI5YV8L+ktGCsnvnJ5JhEeQ+Jw4PIBaG6TTLGwAdd4vVHEnzVpB0Gedyv1FbP2MCWNjIjJWYRIYiqCPh2/yBs4H7fv7kI+s9gYK01m3iry/By7XJiJ104pEA9+ZenvtPsgafLbqRLWcJae0lNYk86OKO6DtcYk2hx5J9eQDIpYBd37fSjeUDc9igfho3qPgm4DOpwqOXNMbg/g5DFji54ab2NCiaSihK5dN8qe1sKY+51Zcm/u0zT701+4IFcgLblqgyRiNqVsQM/p3eVXErpEUnw4rtSgeVF7/E4eq8UwS4D3BzjptJ6994tvVuUNcoFbrr4gUUIJrJw2fHGwU+onqFBzJEDLcFlAxw0hsLXv0Wwg4s4LDXIXrHwtTnFwrmrkR1ZUZWXbF1RY3i2wY4DxQCmbgaT3F6De9VX1Z4=----ATTACHMENT:----MTAwMDM2NjU0NTgzMjgzOSAxNTg2NjgyODIwMzU5ODI4IDc3Njc0Mzc0NjQ5MTQyMTM=