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