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

  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]
  63.             ?? ($this->isRepeatable($attributeName) ? new RepeatableAttributeCollection() : null);
  64.     }
  65.     /**
  66.      * @param class-string<T> $attributeName The name of the annotation.
  67.      *
  68.      * @return RepeatableAttributeCollection<T>
  69.      *
  70.      * @template T of Annotation
  71.      */
  72.     public function getPropertyAttributeCollection(
  73.         ReflectionProperty $property,
  74.         string $attributeName
  75.     ): RepeatableAttributeCollection {
  76.         if (! $this->isRepeatable($attributeName)) {
  77.             throw new LogicException(sprintf(
  78.                 'The attribute "%s" is not repeatable. Call getPropertyAttribute() instead.',
  79.                 $attributeName
  80.             ));
  81.         }
  82.         return $this->getPropertyAttributes($property)[$attributeName] ?? new RepeatableAttributeCollection();
  83.     }
  84.     /**
  85.      * @param array<ReflectionAttribute> $attributes
  86.      *
  87.      * @return class-string-map<T, T|RepeatableAttributeCollection<T>>
  88.      *
  89.      * @template T of Annotation
  90.      */
  91.     private function convertToAttributeInstances(array $attributes): array
  92.     {
  93.         $instances = [];
  94.         foreach ($attributes as $attribute) {
  95.             $attributeName $attribute->getName();
  96.             assert(is_string($attributeName));
  97.             // Make sure we only get Doctrine Attributes
  98.             if (! is_subclass_of($attributeNameAnnotation::class)) {
  99.                 continue;
  100.             }
  101.             $instance $attribute->newInstance();
  102.             assert($instance instanceof Annotation);
  103.             if ($this->isRepeatable($attributeName)) {
  104.                 if (! isset($instances[$attributeName])) {
  105.                     $instances[$attributeName] = new RepeatableAttributeCollection();
  106.                 }
  107.                 $collection $instances[$attributeName];
  108.                 assert($collection instanceof RepeatableAttributeCollection);
  109.                 $collection[] = $instance;
  110.             } else {
  111.                 $instances[$attributeName] = $instance;
  112.             }
  113.         }
  114.         return $instances;
  115.     }
  116.     /** @param class-string<Annotation> $attributeClassName */
  117.     private function isRepeatable(string $attributeClassName): bool
  118.     {
  119.         if (isset($this->isRepeatableAttribute[$attributeClassName])) {
  120.             return $this->isRepeatableAttribute[$attributeClassName];
  121.         }
  122.         $reflectionClass = new ReflectionClass($attributeClassName);
  123.         $attribute       $reflectionClass->getAttributes()[0]->newInstance();
  124.         return $this->isRepeatableAttribute[$attributeClassName] = ($attribute->flags Attribute::IS_REPEATABLE) > 0;
  125.     }
  126. }