vendor/doctrine/orm/src/Mapping/Driver/AttributeReader.php line 33

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