vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/Driver/AttributeReader.php line 106

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM\Mapping\Driver;
  4. use Attribute;
  5. use Doctrine\ORM\Mapping\Annotation;
  6. use LogicException;
  7. use ReflectionAttribute;
  8. use ReflectionClass;
  9. use ReflectionMethod;
  10. use ReflectionProperty;
  11. use function assert;
  12. use function is_string;
  13. use function is_subclass_of;
  14. use function sprintf;
  15. /**
  16.  * @internal
  17.  */
  18. final class AttributeReader
  19. {
  20.     /** @var array<class-string<Annotation>,bool> */
  21.     private array $isRepeatableAttribute = [];
  22.     /**
  23.      * @psalm-return class-string-map<T, T|RepeatableAttributeCollection<T>>
  24.      *
  25.      * @template T of Annotation
  26.      */
  27.     public function getClassAnnotations(ReflectionClass $class): array
  28.     {
  29.         return $this->convertToAttributeInstances($class->getAttributes());
  30.     }
  31.     /**
  32.      * @return class-string-map<T, T|RepeatableAttributeCollection<T>>
  33.      *
  34.      * @template T of Annotation
  35.      */
  36.     public function getMethodAnnotations(ReflectionMethod $method): array
  37.     {
  38.         return $this->convertToAttributeInstances($method->getAttributes());
  39.     }
  40.     /**
  41.      * @return class-string-map<T, T|RepeatableAttributeCollection<T>>
  42.      *
  43.      * @template T of Annotation
  44.      */
  45.     public function getPropertyAnnotations(ReflectionProperty $property): array
  46.     {
  47.         return $this->convertToAttributeInstances($property->getAttributes());
  48.     }
  49.     /**
  50.      * @param class-string<T> $annotationName The name of the annotation.
  51.      *
  52.      * @return T|null
  53.      *
  54.      * @template T of Annotation
  55.      */
  56.     public function getPropertyAnnotation(ReflectionProperty $property$annotationName)
  57.     {
  58.         if ($this->isRepeatable($annotationName)) {
  59.             throw new LogicException(sprintf(
  60.                 'The attribute "%s" is repeatable. Call getPropertyAnnotationCollection() instead.',
  61.                 $annotationName
  62.             ));
  63.         }
  64.         return $this->getPropertyAnnotations($property)[$annotationName]
  65.             ?? ($this->isRepeatable($annotationName) ? new RepeatableAttributeCollection() : null);
  66.     }
  67.     /**
  68.      * @param class-string<T> $annotationName The name of the annotation.
  69.      *
  70.      * @return RepeatableAttributeCollection<T>
  71.      *
  72.      * @template T of Annotation
  73.      */
  74.     public function getPropertyAnnotationCollection(
  75.         ReflectionProperty $property,
  76.         string $annotationName
  77.     ): RepeatableAttributeCollection {
  78.         if (! $this->isRepeatable($annotationName)) {
  79.             throw new LogicException(sprintf(
  80.                 'The attribute "%s" is not repeatable. Call getPropertyAnnotation() instead.',
  81.                 $annotationName
  82.             ));
  83.         }
  84.         return $this->getPropertyAnnotations($property)[$annotationName] ?? new RepeatableAttributeCollection();
  85.     }
  86.     /**
  87.      * @param array<ReflectionAttribute> $attributes
  88.      *
  89.      * @return class-string-map<T, T|RepeatableAttributeCollection<T>>
  90.      *
  91.      * @template T of Annotation
  92.      */
  93.     private function convertToAttributeInstances(array $attributes): array
  94.     {
  95.         $instances = [];
  96.         foreach ($attributes as $attribute) {
  97.             $attributeName $attribute->getName();
  98.             assert(is_string($attributeName));
  99.             // Make sure we only get Doctrine Annotations
  100.             if (! is_subclass_of($attributeNameAnnotation::class)) {
  101.                 continue;
  102.             }
  103.             $instance $attribute->newInstance();
  104.             assert($instance instanceof Annotation);
  105.             if ($this->isRepeatable($attributeName)) {
  106.                 if (! isset($instances[$attributeName])) {
  107.                     $instances[$attributeName] = new RepeatableAttributeCollection();
  108.                 }
  109.                 $collection $instances[$attributeName];
  110.                 assert($collection instanceof RepeatableAttributeCollection);
  111.                 $collection[] = $instance;
  112.             } else {
  113.                 $instances[$attributeName] = $instance;
  114.             }
  115.         }
  116.         return $instances;
  117.     }
  118.     /**
  119.      * @param class-string<Annotation> $attributeClassName
  120.      */
  121.     private function isRepeatable(string $attributeClassName): bool
  122.     {
  123.         if (isset($this->isRepeatableAttribute[$attributeClassName])) {
  124.             return $this->isRepeatableAttribute[$attributeClassName];
  125.         }
  126.         $reflectionClass = new ReflectionClass($attributeClassName);
  127.         $attribute       $reflectionClass->getAttributes()[0]->newInstance();
  128.         return $this->isRepeatableAttribute[$attributeClassName] = ($attribute->flags Attribute::IS_REPEATABLE) > 0;
  129.     }
  130. }