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

  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) ? $class::class : $class;
  39.         };
  40.     }
  41.     public function hasCacheableSupportsMethod(): bool
  42.     {
  43.         return __CLASS__ === static::class;
  44.     }
  45.     protected function extractAttributes(object $objectstring $format null, array $context = []): array
  46.     {
  47.         if (\stdClass::class === $object::class) {
  48.             return array_keys((array) $object);
  49.         }
  50.         // If not using groups, detect manually
  51.         $attributes = [];
  52.         // methods
  53.         $class = ($this->objectClassResolver)($object);
  54.         $reflClass = new \ReflectionClass($class);
  55.         foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) {
  56.             if (
  57.                 !== $reflMethod->getNumberOfRequiredParameters() ||
  58.                 $reflMethod->isStatic() ||
  59.                 $reflMethod->isConstructor() ||
  60.                 $reflMethod->isDestructor()
  61.             ) {
  62.                 continue;
  63.             }
  64.             $name $reflMethod->name;
  65.             $attributeName null;
  66.             if (str_starts_with($name'get') || str_starts_with($name'has') || str_starts_with($name'can')) {
  67.                 // getters, hassers and canners
  68.                 $attributeName substr($name3);
  69.                 if (!$reflClass->hasProperty($attributeName)) {
  70.                     $attributeName lcfirst($attributeName);
  71.                 }
  72.             } elseif (str_starts_with($name'is')) {
  73.                 // issers
  74.                 $attributeName substr($name2);
  75.                 if (!$reflClass->hasProperty($attributeName)) {
  76.                     $attributeName lcfirst($attributeName);
  77.                 }
  78.             }
  79.             if (null !== $attributeName && $this->isAllowedAttribute($object$attributeName$format$context)) {
  80.                 $attributes[$attributeName] = true;
  81.             }
  82.         }
  83.         // properties
  84.         foreach ($reflClass->getProperties() as $reflProperty) {
  85.             if (!$reflProperty->isPublic()) {
  86.                 continue;
  87.             }
  88.             if ($reflProperty->isStatic() || !$this->isAllowedAttribute($object$reflProperty->name$format$context)) {
  89.                 continue;
  90.             }
  91.             $attributes[$reflProperty->name] = true;
  92.         }
  93.         return array_keys($attributes);
  94.     }
  95.     protected function getAttributeValue(object $objectstring $attributestring $format null, array $context = []): mixed
  96.     {
  97.         $cacheKey $object::class;
  98.         if (!\array_key_exists($cacheKey$this->discriminatorCache)) {
  99.             $this->discriminatorCache[$cacheKey] = null;
  100.             if (null !== $this->classDiscriminatorResolver) {
  101.                 $mapping $this->classDiscriminatorResolver->getMappingForMappedObject($object);
  102.                 $this->discriminatorCache[$cacheKey] = $mapping?->getTypeProperty();
  103.             }
  104.         }
  105.         return $attribute === $this->discriminatorCache[$cacheKey] ? $this->classDiscriminatorResolver->getTypeForMappedObject($object) : $this->propertyAccessor->getValue($object$attribute);
  106.     }
  107.     protected function setAttributeValue(object $objectstring $attributemixed $valuestring $format null, array $context = [])
  108.     {
  109.         try {
  110.             $this->propertyAccessor->setValue($object$attribute$value);
  111.         } catch (NoSuchPropertyException) {
  112.             // Properties not found are ignored
  113.         }
  114.     }
  115.     protected function getAllowedAttributes(string|object $classOrObject, array $contextbool $attributesAsString false): array|bool
  116.     {
  117.         if (false === $allowedAttributes parent::getAllowedAttributes($classOrObject$context$attributesAsString)) {
  118.             return false;
  119.         }
  120.         if (null !== $this->classDiscriminatorResolver) {
  121.             $class \is_object($classOrObject) ? $classOrObject::class : $classOrObject;
  122.             if (null !== $discriminatorMapping $this->classDiscriminatorResolver->getMappingForMappedObject($classOrObject)) {
  123.                 $allowedAttributes[] = $attributesAsString $discriminatorMapping->getTypeProperty() : new AttributeMetadata($discriminatorMapping->getTypeProperty());
  124.             }
  125.             if (null !== $discriminatorMapping $this->classDiscriminatorResolver->getMappingForClass($class)) {
  126.                 $attributes = [];
  127.                 foreach ($discriminatorMapping->getTypesMapping() as $mappedClass) {
  128.                     $attributes[] = parent::getAllowedAttributes($mappedClass$context$attributesAsString);
  129.                 }
  130.                 $allowedAttributes array_merge($allowedAttributes, ...$attributes);
  131.             }
  132.         }
  133.         return $allowedAttributes;
  134.     }
  135. }