vendor/symfony/serializer/Mapping/Loader/AttributeLoader.php line 47

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\Mapping\Loader;
  11. use Doctrine\Common\Annotations\Reader;
  12. use Symfony\Component\Serializer\Attribute\Context;
  13. use Symfony\Component\Serializer\Attribute\DiscriminatorMap;
  14. use Symfony\Component\Serializer\Attribute\Groups;
  15. use Symfony\Component\Serializer\Attribute\Ignore;
  16. use Symfony\Component\Serializer\Attribute\MaxDepth;
  17. use Symfony\Component\Serializer\Attribute\SerializedName;
  18. use Symfony\Component\Serializer\Attribute\SerializedPath;
  19. use Symfony\Component\Serializer\Exception\MappingException;
  20. use Symfony\Component\Serializer\Mapping\AttributeMetadata;
  21. use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
  22. use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
  23. use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
  24. /**
  25.  * Loader for PHP attributes.
  26.  *
  27.  * @author Kévin Dunglas <dunglas@gmail.com>
  28.  * @author Alexander M. Turek <me@derrabus.de>
  29.  * @author Alexandre Daubois <alex.daubois@gmail.com>
  30.  */
  31. class AttributeLoader implements LoaderInterface
  32. {
  33.     private const KNOWN_ATTRIBUTES = [
  34.         DiscriminatorMap::class,
  35.         Groups::class,
  36.         Ignore::class,
  37.         MaxDepth::class,
  38.         SerializedName::class,
  39.         SerializedPath::class,
  40.         Context::class,
  41.     ];
  42.     public function __construct(
  43.         private readonly ?Reader $reader null,
  44.     ) {
  45.         if ($reader) {
  46.             trigger_deprecation('symfony/serializer''6.4''Passing a "%s" instance as argument 1 to "%s()" is deprecated, pass null or omit the parameter instead.'get_debug_type($reader), __METHOD__);
  47.         }
  48.     }
  49.     public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
  50.     {
  51.         $reflectionClass $classMetadata->getReflectionClass();
  52.         $className $reflectionClass->name;
  53.         $loaded false;
  54.         $classGroups = [];
  55.         $classContextAnnotation null;
  56.         $attributesMetadata $classMetadata->getAttributesMetadata();
  57.         foreach ($this->loadAttributes($reflectionClass) as $annotation) {
  58.             if ($annotation instanceof DiscriminatorMap) {
  59.                 $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping(
  60.                     $annotation->getTypeProperty(),
  61.                     $annotation->getMapping()
  62.                 ));
  63.                 continue;
  64.             }
  65.             if ($annotation instanceof Groups) {
  66.                 $classGroups $annotation->getGroups();
  67.                 continue;
  68.             }
  69.             if ($annotation instanceof Context) {
  70.                 $classContextAnnotation $annotation;
  71.             }
  72.         }
  73.         foreach ($reflectionClass->getProperties() as $property) {
  74.             if (!isset($attributesMetadata[$property->name])) {
  75.                 $attributesMetadata[$property->name] = new AttributeMetadata($property->name);
  76.                 $classMetadata->addAttributeMetadata($attributesMetadata[$property->name]);
  77.             }
  78.             if ($property->getDeclaringClass()->name === $className) {
  79.                 if ($classContextAnnotation) {
  80.                     $this->setAttributeContextsForGroups($classContextAnnotation$attributesMetadata[$property->name]);
  81.                 }
  82.                 foreach ($classGroups as $group) {
  83.                     $attributesMetadata[$property->name]->addGroup($group);
  84.                 }
  85.                 foreach ($this->loadAttributes($property) as $annotation) {
  86.                     if ($annotation instanceof Groups) {
  87.                         foreach ($annotation->getGroups() as $group) {
  88.                             $attributesMetadata[$property->name]->addGroup($group);
  89.                         }
  90.                     } elseif ($annotation instanceof MaxDepth) {
  91.                         $attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth());
  92.                     } elseif ($annotation instanceof SerializedName) {
  93.                         $attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName());
  94.                     } elseif ($annotation instanceof SerializedPath) {
  95.                         $attributesMetadata[$property->name]->setSerializedPath($annotation->getSerializedPath());
  96.                     } elseif ($annotation instanceof Ignore) {
  97.                         $attributesMetadata[$property->name]->setIgnore(true);
  98.                     } elseif ($annotation instanceof Context) {
  99.                         $this->setAttributeContextsForGroups($annotation$attributesMetadata[$property->name]);
  100.                     }
  101.                     $loaded true;
  102.                 }
  103.             }
  104.         }
  105.         foreach ($reflectionClass->getMethods() as $method) {
  106.             if ($method->getDeclaringClass()->name !== $className) {
  107.                 continue;
  108.             }
  109.             if (=== stripos($method->name'get') && $method->getNumberOfRequiredParameters()) {
  110.                 continue; /*  matches the BC behavior in `Symfony\Component\Serializer\Normalizer\ObjectNormalizer::extractAttributes` */
  111.             }
  112.             $accessorOrMutator preg_match('/^(get|is|has|set)(.+)$/i'$method->name$matches);
  113.             if ($accessorOrMutator) {
  114.                 $attributeName $reflectionClass->hasProperty($method->name) ? $method->name lcfirst($matches[2]);
  115.                 if (isset($attributesMetadata[$attributeName])) {
  116.                     $attributeMetadata $attributesMetadata[$attributeName];
  117.                 } else {
  118.                     $attributesMetadata[$attributeName] = $attributeMetadata = new AttributeMetadata($attributeName);
  119.                     $classMetadata->addAttributeMetadata($attributeMetadata);
  120.                 }
  121.             }
  122.             foreach ($this->loadAttributes($method) as $annotation) {
  123.                 if ($annotation instanceof Groups) {
  124.                     if (!$accessorOrMutator) {
  125.                         throw new MappingException(sprintf('Groups on "%s::%s()" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".'$className$method->name));
  126.                     }
  127.                     foreach ($annotation->getGroups() as $group) {
  128.                         $attributeMetadata->addGroup($group);
  129.                     }
  130.                 } elseif ($annotation instanceof MaxDepth) {
  131.                     if (!$accessorOrMutator) {
  132.                         throw new MappingException(sprintf('MaxDepth on "%s::%s()" cannot be added. MaxDepth can only be added on methods beginning with "get", "is", "has" or "set".'$className$method->name));
  133.                     }
  134.                     $attributeMetadata->setMaxDepth($annotation->getMaxDepth());
  135.                 } elseif ($annotation instanceof SerializedName) {
  136.                     if (!$accessorOrMutator) {
  137.                         throw new MappingException(sprintf('SerializedName on "%s::%s()" cannot be added. SerializedName can only be added on methods beginning with "get", "is", "has" or "set".'$className$method->name));
  138.                     }
  139.                     $attributeMetadata->setSerializedName($annotation->getSerializedName());
  140.                 } elseif ($annotation instanceof SerializedPath) {
  141.                     if (!$accessorOrMutator) {
  142.                         throw new MappingException(sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has" or "set".'$className$method->name));
  143.                     }
  144.                     $attributeMetadata->setSerializedPath($annotation->getSerializedPath());
  145.                 } elseif ($annotation instanceof Ignore) {
  146.                     if (!$accessorOrMutator) {
  147.                         throw new MappingException(sprintf('Ignore on "%s::%s()" cannot be added. Ignore can only be added on methods beginning with "get", "is", "has" or "set".'$className$method->name));
  148.                     }
  149.                     $attributeMetadata->setIgnore(true);
  150.                 } elseif ($annotation instanceof Context) {
  151.                     if (!$accessorOrMutator) {
  152.                         throw new MappingException(sprintf('Context on "%s::%s()" cannot be added. Context can only be added on methods beginning with "get", "is", "has" or "set".'$className$method->name));
  153.                     }
  154.                     $this->setAttributeContextsForGroups($annotation$attributeMetadata);
  155.                 }
  156.                 $loaded true;
  157.             }
  158.         }
  159.         return $loaded;
  160.     }
  161.     private function loadAttributes(\ReflectionMethod|\ReflectionClass|\ReflectionProperty $reflector): iterable
  162.     {
  163.         foreach ($reflector->getAttributes() as $attribute) {
  164.             if ($this->isKnownAttribute($attribute->getName())) {
  165.                 try {
  166.                     yield $attribute->newInstance();
  167.                 } catch (\Error $e) {
  168.                     if (\Error::class !== $e::class) {
  169.                         throw $e;
  170.                     }
  171.                     $on = match (true) {
  172.                         $reflector instanceof \ReflectionClass => ' on class '.$reflector->name,
  173.                         $reflector instanceof \ReflectionMethod => sprintf(' on "%s::%s()"'$reflector->getDeclaringClass()->name$reflector->name),
  174.                         $reflector instanceof \ReflectionProperty => sprintf(' on "%s::$%s"'$reflector->getDeclaringClass()->name$reflector->name),
  175.                         default => '',
  176.                     };
  177.                     throw new MappingException(sprintf('Could not instantiate attribute "%s"%s.'$attribute->getName(), $on), 0$e);
  178.                 }
  179.             }
  180.         }
  181.         if (null === $this->reader) {
  182.             return;
  183.         }
  184.         if ($reflector instanceof \ReflectionClass) {
  185.             yield from $this->getClassAnnotations($reflector);
  186.         }
  187.         if ($reflector instanceof \ReflectionMethod) {
  188.             yield from $this->getMethodAnnotations($reflector);
  189.         }
  190.         if ($reflector instanceof \ReflectionProperty) {
  191.             yield from $this->getPropertyAnnotations($reflector);
  192.         }
  193.     }
  194.     /**
  195.      * @deprecated since Symfony 6.4 without replacement
  196.      */
  197.     public function loadAnnotations(\ReflectionMethod|\ReflectionClass|\ReflectionProperty $reflector): iterable
  198.     {
  199.         trigger_deprecation('symfony/serializer''6.4''Method "%s()" is deprecated without replacement.'__METHOD__);
  200.         return $this->loadAttributes($reflector);
  201.     }
  202.     private function setAttributeContextsForGroups(Context $annotationAttributeMetadataInterface $attributeMetadata): void
  203.     {
  204.         if ($annotation->getContext()) {
  205.             $attributeMetadata->setNormalizationContextForGroups($annotation->getContext(), $annotation->getGroups());
  206.             $attributeMetadata->setDenormalizationContextForGroups($annotation->getContext(), $annotation->getGroups());
  207.         }
  208.         if ($annotation->getNormalizationContext()) {
  209.             $attributeMetadata->setNormalizationContextForGroups($annotation->getNormalizationContext(), $annotation->getGroups());
  210.         }
  211.         if ($annotation->getDenormalizationContext()) {
  212.             $attributeMetadata->setDenormalizationContextForGroups($annotation->getDenormalizationContext(), $annotation->getGroups());
  213.         }
  214.     }
  215.     private function isKnownAttribute(string $attributeName): bool
  216.     {
  217.         foreach (self::KNOWN_ATTRIBUTES as $knownAttribute) {
  218.             if (is_a($attributeName$knownAttributetrue)) {
  219.                 return true;
  220.             }
  221.         }
  222.         return false;
  223.     }
  224.     /**
  225.      * @return object[]
  226.      */
  227.     private function getClassAnnotations(\ReflectionClass $reflector): array
  228.     {
  229.         if ($annotations array_filter(
  230.             $this->reader->getClassAnnotations($reflector),
  231.             fn (object $annotation): bool => $this->isKnownAttribute($annotation::class),
  232.         )) {
  233.             trigger_deprecation('symfony/serializer''6.4''Class "%s" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'$reflector->getName());
  234.         }
  235.         return $annotations;
  236.     }
  237.     /**
  238.      * @return object[]
  239.      */
  240.     private function getMethodAnnotations(\ReflectionMethod $reflector): array
  241.     {
  242.         if ($annotations array_filter(
  243.             $this->reader->getMethodAnnotations($reflector),
  244.             fn (object $annotation): bool => $this->isKnownAttribute($annotation::class),
  245.         )) {
  246.             trigger_deprecation('symfony/serializer''6.4''Method "%s::%s()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'$reflector->getDeclaringClass()->getName(), $reflector->getName());
  247.         }
  248.         return $annotations;
  249.     }
  250.     /**
  251.      * @return object[]
  252.      */
  253.     private function getPropertyAnnotations(\ReflectionProperty $reflector): array
  254.     {
  255.         if ($annotations array_filter(
  256.             $this->reader->getPropertyAnnotations($reflector),
  257.             fn (object $annotation): bool => $this->isKnownAttribute($annotation::class),
  258.         )) {
  259.             trigger_deprecation('symfony/serializer''6.4''Property "%s::$%s" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'$reflector->getDeclaringClass()->getName(), $reflector->getName());
  260.         }
  261.         return $annotations;
  262.     }
  263. }
  264. if (!class_exists(AnnotationLoader::class, false)) {
  265.     class_alias(AttributeLoader::class, AnnotationLoader::class);
  266. }