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