vendor/symfony/serializer/Mapping/Loader/AnnotationLoader.php line 72

  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\Annotation\Context;
  13. use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
  14. use Symfony\Component\Serializer\Annotation\Groups;
  15. use Symfony\Component\Serializer\Annotation\Ignore;
  16. use Symfony\Component\Serializer\Annotation\MaxDepth;
  17. use Symfony\Component\Serializer\Annotation\SerializedName;
  18. use Symfony\Component\Serializer\Annotation\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 Doctrine annotations and PHP 8 attributes.
  26.  *
  27.  * @author Kévin Dunglas <dunglas@gmail.com>
  28.  * @author Alexander M. Turek <me@derrabus.de>
  29.  */
  30. class AnnotationLoader implements LoaderInterface
  31. {
  32.     private const KNOWN_ANNOTATIONS = [
  33.         DiscriminatorMap::class,
  34.         Groups::class,
  35.         Ignore::class,
  36.         MaxDepth::class,
  37.         SerializedName::class,
  38.         SerializedPath::class,
  39.         Context::class,
  40.     ];
  41.     private $reader;
  42.     public function __construct(Reader $reader null)
  43.     {
  44.         $this->reader $reader;
  45.     }
  46.     public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
  47.     {
  48.         $reflectionClass $classMetadata->getReflectionClass();
  49.         $className $reflectionClass->name;
  50.         $loaded false;
  51.         $attributesMetadata $classMetadata->getAttributesMetadata();
  52.         foreach ($this->loadAnnotations($reflectionClass) as $annotation) {
  53.             if ($annotation instanceof DiscriminatorMap) {
  54.                 $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping(
  55.                     $annotation->getTypeProperty(),
  56.                     $annotation->getMapping()
  57.                 ));
  58.             }
  59.         }
  60.         foreach ($reflectionClass->getProperties() as $property) {
  61.             if (!isset($attributesMetadata[$property->name])) {
  62.                 $attributesMetadata[$property->name] = new AttributeMetadata($property->name);
  63.                 $classMetadata->addAttributeMetadata($attributesMetadata[$property->name]);
  64.             }
  65.             if ($property->getDeclaringClass()->name === $className) {
  66.                 foreach ($this->loadAnnotations($property) as $annotation) {
  67.                     if ($annotation instanceof Groups) {
  68.                         foreach ($annotation->getGroups() as $group) {
  69.                             $attributesMetadata[$property->name]->addGroup($group);
  70.                         }
  71.                     } elseif ($annotation instanceof MaxDepth) {
  72.                         $attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth());
  73.                     } elseif ($annotation instanceof SerializedName) {
  74.                         $attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName());
  75.                     } elseif ($annotation instanceof SerializedPath) {
  76.                         $attributesMetadata[$property->name]->setSerializedPath($annotation->getSerializedPath());
  77.                     } elseif ($annotation instanceof Ignore) {
  78.                         $attributesMetadata[$property->name]->setIgnore(true);
  79.                     } elseif ($annotation instanceof Context) {
  80.                         $this->setAttributeContextsForGroups($annotation$attributesMetadata[$property->name]);
  81.                     }
  82.                     $loaded true;
  83.                 }
  84.             }
  85.         }
  86.         foreach ($reflectionClass->getMethods() as $method) {
  87.             if ($method->getDeclaringClass()->name !== $className) {
  88.                 continue;
  89.             }
  90.             if (=== stripos($method->name'get') && $method->getNumberOfRequiredParameters()) {
  91.                 continue; /*  matches the BC behavior in `Symfony\Component\Serializer\Normalizer\ObjectNormalizer::extractAttributes` */
  92.             }
  93.             $accessorOrMutator preg_match('/^(get|is|has|set)(.+)$/i'$method->name$matches);
  94.             if ($accessorOrMutator) {
  95.                 $attributeName lcfirst($matches[2]);
  96.                 if (isset($attributesMetadata[$attributeName])) {
  97.                     $attributeMetadata $attributesMetadata[$attributeName];
  98.                 } else {
  99.                     $attributesMetadata[$attributeName] = $attributeMetadata = new AttributeMetadata($attributeName);
  100.                     $classMetadata->addAttributeMetadata($attributeMetadata);
  101.                 }
  102.             }
  103.             foreach ($this->loadAnnotations($method) as $annotation) {
  104.                 if ($annotation instanceof Groups) {
  105.                     if (!$accessorOrMutator) {
  106.                         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));
  107.                     }
  108.                     foreach ($annotation->getGroups() as $group) {
  109.                         $attributeMetadata->addGroup($group);
  110.                     }
  111.                 } elseif ($annotation instanceof MaxDepth) {
  112.                     if (!$accessorOrMutator) {
  113.                         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));
  114.                     }
  115.                     $attributeMetadata->setMaxDepth($annotation->getMaxDepth());
  116.                 } elseif ($annotation instanceof SerializedName) {
  117.                     if (!$accessorOrMutator) {
  118.                         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));
  119.                     }
  120.                     $attributeMetadata->setSerializedName($annotation->getSerializedName());
  121.                 } elseif ($annotation instanceof SerializedPath) {
  122.                     if (!$accessorOrMutator) {
  123.                         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));
  124.                     }
  125.                     $attributeMetadata->setSerializedPath($annotation->getSerializedPath());
  126.                 } elseif ($annotation instanceof Ignore) {
  127.                     if (!$accessorOrMutator) {
  128.                         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));
  129.                     }
  130.                     $attributeMetadata->setIgnore(true);
  131.                 } elseif ($annotation instanceof Context) {
  132.                     if (!$accessorOrMutator) {
  133.                         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));
  134.                     }
  135.                     $this->setAttributeContextsForGroups($annotation$attributeMetadata);
  136.                 }
  137.                 $loaded true;
  138.             }
  139.         }
  140.         return $loaded;
  141.     }
  142.     /**
  143.      * @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflector
  144.      */
  145.     public function loadAnnotations(object $reflector): iterable
  146.     {
  147.         foreach ($reflector->getAttributes() as $attribute) {
  148.             if ($this->isKnownAttribute($attribute->getName())) {
  149.                 try {
  150.                     yield $attribute->newInstance();
  151.                 } catch (\Error $e) {
  152.                     if (\Error::class !== $e::class) {
  153.                         throw $e;
  154.                     }
  155.                     $on = match (true) {
  156.                         $reflector instanceof \ReflectionClass => ' on class '.$reflector->name,
  157.                         $reflector instanceof \ReflectionMethod => sprintf(' on "%s::%s()"'$reflector->getDeclaringClass()->name$reflector->name),
  158.                         $reflector instanceof \ReflectionProperty => sprintf(' on "%s::$%s"'$reflector->getDeclaringClass()->name$reflector->name),
  159.                         default => '',
  160.                     };
  161.                     throw new MappingException(sprintf('Could not instantiate attribute "%s"%s.'$attribute->getName(), $on), 0$e);
  162.                 }
  163.             }
  164.         }
  165.         if (null === $this->reader) {
  166.             return;
  167.         }
  168.         if ($reflector instanceof \ReflectionClass) {
  169.             yield from $this->reader->getClassAnnotations($reflector);
  170.         }
  171.         if ($reflector instanceof \ReflectionMethod) {
  172.             yield from $this->reader->getMethodAnnotations($reflector);
  173.         }
  174.         if ($reflector instanceof \ReflectionProperty) {
  175.             yield from $this->reader->getPropertyAnnotations($reflector);
  176.         }
  177.     }
  178.     private function setAttributeContextsForGroups(Context $annotationAttributeMetadataInterface $attributeMetadata): void
  179.     {
  180.         if ($annotation->getContext()) {
  181.             $attributeMetadata->setNormalizationContextForGroups($annotation->getContext(), $annotation->getGroups());
  182.             $attributeMetadata->setDenormalizationContextForGroups($annotation->getContext(), $annotation->getGroups());
  183.         }
  184.         if ($annotation->getNormalizationContext()) {
  185.             $attributeMetadata->setNormalizationContextForGroups($annotation->getNormalizationContext(), $annotation->getGroups());
  186.         }
  187.         if ($annotation->getDenormalizationContext()) {
  188.             $attributeMetadata->setDenormalizationContextForGroups($annotation->getDenormalizationContext(), $annotation->getGroups());
  189.         }
  190.     }
  191.     private function isKnownAttribute(string $attributeName): bool
  192.     {
  193.         foreach (self::KNOWN_ANNOTATIONS as $knownAnnotation) {
  194.             if (is_a($attributeName$knownAnnotationtrue)) {
  195.                 return true;
  196.             }
  197.         }
  198.         return false;
  199.     }
  200. }