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: use function is_array;
20:
21: /** @psalm-immutable */
22: class ReflectionAttribute
23: {
24: private Reflector $reflector;
25: /**
26: * @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
27: */
28: private $owner;
29: private bool $isRepeated;
30: /** @var class-string */
31: private string $name;
32:
33: /**
34: * Argument expressions, each either a Node\Expr or its exported cache form (parsed
35: * into an Expr only when the arguments are actually asked for).
36: *
37: * @var array<int|string, Node\Expr|array<string, mixed>>
38: */
39: private array $arguments;
40:
41: /** @return array<int|string, Node\Expr> */
42: private function argumentExpressions(): array
43: {
44: foreach ($this->arguments as $key => $argument) {
45: if (! is_array($argument)) {
46: continue;
47: }
48:
49: $this->arguments[$key] = ExprCacheHelper::import($argument);
50: }
51:
52: return $this->arguments;
53: }
54:
55: /** @internal
56: * @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 */
57: public function __construct(
58: Reflector $reflector,
59: Node\Attribute $node,
60: $owner,
61: bool $isRepeated
62: ) {
63: $this->reflector = $reflector;
64: $this->owner = $owner;
65: $this->isRepeated = $isRepeated;
66: /** @var class-string $name */
67: $name = $node->name->toString();
68:
69: $this->name = $name;
70:
71: $arguments = [];
72: foreach ($node->args as $argNo => $arg) {
73: $arguments[(($nullsafeVariable1 = $arg->name) ? $nullsafeVariable1->toString() : null) ?? $argNo] = $arg->value;
74: }
75:
76: $this->arguments = $arguments;
77: }
78:
79: /**
80: * @return array<string, mixed>
81: */
82: public function exportToCache(): array
83: {
84: return [
85: 'name' => $this->name,
86: 'isRepeated' => $this->isRepeated,
87: 'arguments' => array_map(
88: static fn ($expr) => is_array($expr) ? $expr : ExprCacheHelper::export($expr),
89: $this->arguments,
90: ),
91: ];
92: }
93:
94: /**
95: * @param array<string, mixed> $data
96: * @param ReflectionClass|ReflectionMethod|ReflectionFunction|ReflectionConstant|ReflectionClassConstant|ReflectionEnumCase|ReflectionProperty|ReflectionParameter $owner
97: */
98: public static function importFromCache(Reflector $reflector, array $data, $owner): self
99: {
100: $reflection = new CoreReflectionClass(self::class);
101: /** @var self $ref */
102: $ref = $reflection->newInstanceWithoutConstructor();
103: $ref->reflector = $reflector;
104:
105: $ref->owner = $owner;
106: $ref->name = $data['name'];
107: $ref->isRepeated = $data['isRepeated'];
108:
109: $ref->arguments = $data['arguments'];
110:
111: return $ref;
112: }
113:
114: /** @internal
115: * @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 */
116: public function withOwner($owner): self
117: {
118: $clone = clone $this;
119: $clone->owner = $owner;
120:
121: return $clone;
122: }
123:
124: /** @return class-string */
125: public function getName(): string
126: {
127: return $this->name;
128: }
129:
130: public function getClass(): ReflectionClass
131: {
132: return $this->reflector->reflectClass($this->getName());
133: }
134:
135: /** @return array<int|string, Node\Expr> */
136: public function getArgumentsExpressions(): array
137: {
138: return $this->argumentExpressions();
139: }
140:
141: /**
142: * @return array<int|string, mixed>
143: */
144: public function getArguments(): array
145: {
146: $compiler = new CompileNodeToValue();
147: $context = new CompilerContext($this->reflector, $this->owner);
148:
149: return array_map(static fn (Node\Expr $value) => $compiler->__invoke($value, $context)->value, $this->argumentExpressions());
150: }
151:
152: /** @return int-mask-of<Attribute::TARGET_*>|ReflectionAttributeAdapter::TARGET_CONSTANT_COMPATIBILITY */
153: public function getTarget(): int
154: {
155: switch (true) {
156: case $this->owner instanceof ReflectionClass:
157: return Attribute::TARGET_CLASS;
158: case $this->owner instanceof ReflectionFunction:
159: return Attribute::TARGET_FUNCTION;
160: case $this->owner instanceof ReflectionConstant:
161: return ReflectionAttributeAdapter::TARGET_CONSTANT_COMPATIBILITY;
162: case $this->owner instanceof ReflectionMethod:
163: return Attribute::TARGET_METHOD;
164: case $this->owner instanceof ReflectionProperty:
165: return Attribute::TARGET_PROPERTY;
166: case $this->owner instanceof ReflectionClassConstant:
167: return Attribute::TARGET_CLASS_CONSTANT;
168: case $this->owner instanceof ReflectionEnumCase:
169: return Attribute::TARGET_CLASS_CONSTANT;
170: case $this->owner instanceof ReflectionParameter:
171: return Attribute::TARGET_PARAMETER;
172: default:
173: throw new LogicException('unknown owner');
174: }
175: }
176:
177: public function isRepeated(): bool
178: {
179: return $this->isRepeated;
180: }
181:
182: /** @return non-empty-string */
183: public function __toString(): string
184: {
185: return ReflectionAttributeStringCast::toString($this);
186: }
187: }
188: