1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection;
6:
7: use Attribute;
8: use LogicException;
9: use PhpParser\Node;
10: use PhpParser\Node\Expr;
11: use ReflectionClass as CoreReflectionClass;
12: use PHPStan\BetterReflection\NodeCompiler\CompileNodeToValue;
13: use PHPStan\BetterReflection\NodeCompiler\CompilerContext;
14: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionAttribute as ReflectionAttributeAdapter;
15: use PHPStan\BetterReflection\Reflection\StringCast\ReflectionAttributeStringCast;
16: use PHPStan\BetterReflection\Reflector\Reflector;
17:
18: use function array_map;
19:
20: /** @psalm-immutable */
21: class ReflectionAttribute
22: {
23: private Reflector $reflector;
24: /**
25: * @var \PHPStan\BetterReflection\Reflection\ReflectionClass|\PHPStan\BetterReflection\Reflection\ReflectionMethod|\PHPStan\BetterReflection\Reflection\ReflectionFunction|\PHPStan\BetterReflection\Reflection\ReflectionConstant|\PHPStan\BetterReflection\Reflection\ReflectionClassConstant|\PHPStan\BetterReflection\Reflection\ReflectionEnumCase|\PHPStan\BetterReflection\Reflection\ReflectionProperty|\PHPStan\BetterReflection\Reflection\ReflectionParameter
26: */
27: private $owner;
28: private bool $isRepeated;
29: /** @var class-string */
30: private string $name;
31:
32: /** @var array<int|string, Node\Expr> */
33: private array $arguments;
34:
35: /** @internal
36: * @param \PHPStan\BetterReflection\Reflection\ReflectionClass|\PHPStan\BetterReflection\Reflection\ReflectionMethod|\PHPStan\BetterReflection\Reflection\ReflectionFunction|\PHPStan\BetterReflection\Reflection\ReflectionConstant|\PHPStan\BetterReflection\Reflection\ReflectionClassConstant|\PHPStan\BetterReflection\Reflection\ReflectionEnumCase|\PHPStan\BetterReflection\Reflection\ReflectionProperty|\PHPStan\BetterReflection\Reflection\ReflectionParameter $owner */
37: public function __construct(Reflector $reflector, Node\Attribute $node, $owner, bool $isRepeated)
38: {
39: $this->reflector = $reflector;
40: $this->owner = $owner;
41: $this->isRepeated = $isRepeated;
42: /** @var class-string $name */
43: $name = $node->name->toString();
44: $this->name = $name;
45: $arguments = [];
46: foreach ($node->args as $argNo => $arg) {
47: $arguments[(($nullsafeVariable1 = $arg->name) ? $nullsafeVariable1->toString() : null) ?? $argNo] = $arg->value;
48: }
49: $this->arguments = $arguments;
50: }
51:
52: /**
53: * @return array<string, mixed>
54: */
55: public function exportToCache(): array
56: {
57: return [
58: 'name' => $this->name,
59: 'isRepeated' => $this->isRepeated,
60: 'arguments' => array_map(
61: static fn (Expr $expr) => ExprCacheHelper::export($expr),
62: $this->arguments,
63: ),
64: ];
65: }
66:
67: /**
68: * @param array<string, mixed> $data
69: * @param ReflectionClass|ReflectionMethod|ReflectionFunction|ReflectionConstant|ReflectionClassConstant|ReflectionEnumCase|ReflectionProperty|ReflectionParameter $owner
70: */
71: public static function importFromCache(Reflector $reflector, array $data, $owner): self
72: {
73: $reflection = new CoreReflectionClass(self::class);
74: /** @var self $ref */
75: $ref = $reflection->newInstanceWithoutConstructor();
76: $ref->reflector = $reflector;
77:
78: $ref->owner = $owner;
79: $ref->name = $data['name'];
80: $ref->isRepeated = $data['isRepeated'];
81:
82: $ref->arguments = array_map(
83: static fn (array $exprData) => ExprCacheHelper::import($exprData),
84: $data['arguments'],
85: );
86:
87: return $ref;
88: }
89:
90: /** @internal
91: * @param \PHPStan\BetterReflection\Reflection\ReflectionClass|\PHPStan\BetterReflection\Reflection\ReflectionMethod|\PHPStan\BetterReflection\Reflection\ReflectionFunction|\PHPStan\BetterReflection\Reflection\ReflectionConstant|\PHPStan\BetterReflection\Reflection\ReflectionClassConstant|\PHPStan\BetterReflection\Reflection\ReflectionEnumCase|\PHPStan\BetterReflection\Reflection\ReflectionProperty|\PHPStan\BetterReflection\Reflection\ReflectionParameter $owner */
92: public function withOwner($owner): self
93: {
94: $clone = clone $this;
95: $clone->owner = $owner;
96:
97: return $clone;
98: }
99:
100: /** @return class-string */
101: public function getName(): string
102: {
103: return $this->name;
104: }
105:
106: public function getClass(): ReflectionClass
107: {
108: return $this->reflector->reflectClass($this->getName());
109: }
110:
111: /** @return array<int|string, Node\Expr> */
112: public function getArgumentsExpressions(): array
113: {
114: return $this->arguments;
115: }
116:
117: /**
118: * @return array<int|string, mixed>
119: */
120: public function getArguments(): array
121: {
122: $compiler = new CompileNodeToValue();
123: $context = new CompilerContext($this->reflector, $this->owner);
124:
125: return array_map(static fn (Node\Expr $value) => $compiler->__invoke($value, $context)->value, $this->arguments);
126: }
127:
128: /** @return int-mask-of<Attribute::TARGET_*>|ReflectionAttributeAdapter::TARGET_CONSTANT_COMPATIBILITY */
129: public function getTarget(): int
130: {
131: switch (true) {
132: case $this->owner instanceof ReflectionClass:
133: return Attribute::TARGET_CLASS;
134: case $this->owner instanceof ReflectionFunction:
135: return Attribute::TARGET_FUNCTION;
136: case $this->owner instanceof ReflectionConstant:
137: return ReflectionAttributeAdapter::TARGET_CONSTANT_COMPATIBILITY;
138: case $this->owner instanceof ReflectionMethod:
139: return Attribute::TARGET_METHOD;
140: case $this->owner instanceof ReflectionProperty:
141: return Attribute::TARGET_PROPERTY;
142: case $this->owner instanceof ReflectionClassConstant:
143: return Attribute::TARGET_CLASS_CONSTANT;
144: case $this->owner instanceof ReflectionEnumCase:
145: return Attribute::TARGET_CLASS_CONSTANT;
146: case $this->owner instanceof ReflectionParameter:
147: return Attribute::TARGET_PARAMETER;
148: default:
149: throw new LogicException('unknown owner');
150: }
151: }
152:
153: public function isRepeated(): bool
154: {
155: return $this->isRepeated;
156: }
157:
158: /** @return non-empty-string */
159: public function __toString(): string
160: {
161: return ReflectionAttributeStringCast::toString($this);
162: }
163: }
164: