vendor/symfony/serializer/Normalizer/ObjectNormalizer.php line 149

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Serializer\Normalizer;
  11. use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
  12. use Symfony\Component\PropertyAccess\PropertyAccess;
  13. use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
  14. use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
  15. use Symfony\Component\Serializer\Exception\LogicException;
  16. use Symfony\Component\Serializer\Mapping\AttributeMetadata;
  17. use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
  18. use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
  19. use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
  20. /**
  21.  * Converts between objects and arrays using the PropertyAccess component.
  22.  *
  23.  * @author Kévin Dunglas <dunglas@gmail.com>
  24.  */
  25. class ObjectNormalizer extends AbstractObjectNormalizer
  26. {
  27.     protected $propertyAccessor;
  28.     private $discriminatorCache = [];
  29.     private $objectClassResolver;
  30.     public function __construct(ClassMetadataFactoryInterface $classMetadataFactory nullNameConverterInterface $nameConverter nullPropertyAccessorInterface $propertyAccessor nullPropertyTypeExtractorInterface $propertyTypeExtractor nullClassDiscriminatorResolverInterface $classDiscriminatorResolver null, callable $objectClassResolver null, array $defaultContext = [])
  31.     {
  32.         if (!class_exists(PropertyAccess::class)) {
  33.             throw new LogicException('The ObjectNormalizer class requires the "PropertyAccess" component. Install "symfony/property-access" to use it.');
  34.         }
  35.         parent::__construct($classMetadataFactory$nameConverter$propertyTypeExtractor$classDiscriminatorResolver$objectClassResolver$defaultContext);
  36.         $this->propertyAccessor $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
  37.         $this->objectClassResolver $objectClassResolver ?? function ($class) {
  38.             return \is_object($class) ? \get_class($class) : $class;
  39.         };
  40.     }
  41.     /**
  42.      * {@inheritdoc}
  43.      */
  44.     public function hasCacheableSupportsMethod(): bool
  45.     {
  46.         return __CLASS__ === static::class;
  47.     }
  48.     /**
  49.      * {@inheritdoc}
  50.      */
  51.     protected function extractAttributes(object $objectstring $format null, array $context = []): array
  52.     {
  53.         if (\stdClass::class === \get_class($object)) {
  54.             return array_keys((array) $object);
  55.         }
  56.         // If not using groups, detect manually
  57.         $attributes = [];
  58.         // methods
  59.         $class = ($this->objectClassResolver)($object);
  60.         $reflClass = new \ReflectionClass($class);
  61.         foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) {
  62.             if (
  63.                 !== $reflMethod->getNumberOfRequiredParameters() ||
  64.                 $reflMethod->isStatic() ||
  65.                 $reflMethod->isConstructor() ||
  66.                 $reflMethod->isDestructor()
  67.             ) {
  68.                 continue;
  69.             }
  70.             $name $reflMethod->name;
  71.             $attributeName null;
  72.             if (str_starts_with($name'get') || str_starts_with($name'has') || str_starts_with($name'can')) {
  73.                 // getters, hassers and canners
  74.                 $attributeName substr($name3);
  75.                 if (!$reflClass->hasProperty($attributeName)) {
  76.                     $attributeName lcfirst($attributeName);
  77.                 }
  78.             } elseif (str_starts_with($name'is')) {
  79.                 // issers
  80.                 $attributeName substr($name2);
  81.                 if (!$reflClass->hasProperty($attributeName)) {
  82.                     $attributeName lcfirst($attributeName);
  83.                 }
  84.             }
  85.             if (null !== $attributeName && $this->isAllowedAttribute($object$attributeName$format$context)) {
  86.                 $attributes[$attributeName] = true;
  87.             }
  88.         }
  89.         // properties
  90.         foreach ($reflClass->getProperties() as $reflProperty) {
  91.             if (!$reflProperty->isPublic()) {
  92.                 continue;
  93.             }
  94.             if ($reflProperty->isStatic() || !$this->isAllowedAttribute($object$reflProperty->name$format$context)) {
  95.                 continue;
  96.             }
  97.             $attributes[$reflProperty->name] = true;
  98.         }
  99.         return array_keys($attributes);
  100.     }
  101.     /**
  102.      * {@inheritdoc}
  103.      */
  104.     protected function getAttributeValue(object $objectstring $attributestring $format null, array $context = []): mixed
  105.     {
  106.         $cacheKey \get_class($object);
  107.         if (!\array_key_exists($cacheKey$this->discriminatorCache)) {
  108.             $this->discriminatorCache[$cacheKey] = null;
  109.             if (null !== $this->classDiscriminatorResolver) {
  110.                 $mapping $this->classDiscriminatorResolver->getMappingForMappedObject($object);
  111.                 $this->discriminatorCache[$cacheKey] = $mapping?->getTypeProperty();
  112.             }
  113.         }
  114.         return $attribute === $this->discriminatorCache[$cacheKey] ? $this->classDiscriminatorResolver->getTypeForMappedObject($object) : $this->propertyAccessor->getValue($object$attribute);
  115.     }
  116.     /**
  117.      * {@inheritdoc}
  118.      */
  119.     protected function setAttributeValue(object $objectstring $attributemixed $valuestring $format null, array $context = [])
  120.     {
  121.         try {
  122.             $this->propertyAccessor->setValue($object$attribute$value);
  123.         } catch (NoSuchPropertyException) {
  124.             // Properties not found are ignored
  125.         }
  126.     }
  127.     /**
  128.      * {@inheritdoc}
  129.      */
  130.     protected function getAllowedAttributes(string|object $classOrObject, array $contextbool $attributesAsString false): array|bool
  131.     {
  132.         if (false === $allowedAttributes parent::getAllowedAttributes($classOrObject$context$attributesAsString)) {
  133.             return false;
  134.         }
  135.         if (null !== $this->classDiscriminatorResolver) {
  136.             $class \is_object($classOrObject) ? \get_class($classOrObject) : $classOrObject;
  137.             if (null !== $discriminatorMapping $this->classDiscriminatorResolver->getMappingForMappedObject($classOrObject)) {
  138.                 $allowedAttributes[] = $attributesAsString $discriminatorMapping->getTypeProperty() : new AttributeMetadata($discriminatorMapping->getTypeProperty());
  139.             }
  140.             if (null !== $discriminatorMapping $this->classDiscriminatorResolver->getMappingForClass($class)) {
  141.                 $attributes = [];
  142.                 foreach ($discriminatorMapping->getTypesMapping() as $mappedClass) {
  143.                     $attributes[] = parent::getAllowedAttributes($mappedClass$context$attributesAsString);
  144.                 }
  145.                 $allowedAttributes array_merge($allowedAttributes, ...$attributes);
  146.             }
  147.         }
  148.         return $allowedAttributes;
  149.     }
  150. }