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

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. /** @internal */
  16. final class AttributeReader
  17. {
  18. /** @var array<class-string<Annotation>,bool> */
  19. private array $isRepeatableAttribute = [];
  20. /**
  21. * @phpstan-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($attributeName, Annotation::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. }