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(
38: Reflector $reflector,
39: Node\Attribute $node,
40: $owner,
41: bool $isRepeated
42: ) {
43: $this->reflector = $reflector;
44: $this->owner = $owner;
45: $this->isRepeated = $isRepeated;
46: /** @var class-string $name */
47: $name = $node->name->toString();
48:
49: $this->name = $name;
50:
51: $arguments = [];
52: foreach ($node->args as $argNo => $arg) {
53: $arguments[(($nullsafeVariable1 = $arg->name) ? $nullsafeVariable1->toString() : null) ?? $argNo] = $arg->value;
54: }
55:
56: $this->arguments = $arguments;
57: }
58:
59: /**
60: * @return array<string, mixed>
61: */
62: public function exportToCache(): array
63: {
64: return [
65: 'name' => $this->name,
66: 'isRepeated' => $this->isRepeated,
67: 'arguments' => array_map(
68: static fn (Expr $expr) => ExprCacheHelper::export($expr),
69: $this->arguments,
70: ),
71: ];
72: }
73:
74: /**
75: * @param array<string, mixed> $data
76: * @param ReflectionClass|ReflectionMethod|ReflectionFunction|ReflectionConstant|ReflectionClassConstant|ReflectionEnumCase|ReflectionProperty|ReflectionParameter $owner
77: */
78: public static function importFromCache(Reflector $reflector, array $data, $owner): self
79: {
80: $reflection = new CoreReflectionClass(self::class);
81: /** @var self $ref */
82: $ref = $reflection->newInstanceWithoutConstructor();
83: $ref->reflector = $reflector;
84:
85: $ref->owner = $owner;
86: $ref->name = $data['name'];
87: $ref->isRepeated = $data['isRepeated'];
88:
89: $ref->arguments = array_map(
90: static fn (array $exprData) => ExprCacheHelper::import($exprData),
91: $data['arguments'],
92: );
93:
94: return $ref;
95: }
96:
97: /** @internal
98: * @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 */
99: public function withOwner($owner): self
100: {
101: $clone = clone $this;
102: $clone->owner = $owner;
103:
104: return $clone;
105: }
106:
107: /** @return class-string */
108: public function getName(): string
109: {
110: return $this->name;
111: }
112:
113: public function getClass(): ReflectionClass
114: {
115: return $this->reflector->reflectClass($this->getName());
116: }
117:
118: /** @return array<int|string, Node\Expr> */
119: public function getArgumentsExpressions(): array
120: {
121: return $this->arguments;
122: }
123:
124: /**
125: * @return array<int|string, mixed>
126: */
127: public function getArguments(): array
128: {
129: $compiler = new CompileNodeToValue();
130: $context = new CompilerContext($this->reflector, $this->owner);
131:
132: return array_map(static fn (Node\Expr $value) => $compiler->__invoke($value, $context)->value, $this->arguments);
133: }
134:
135: /** @return int-mask-of<Attribute::TARGET_*>|ReflectionAttributeAdapter::TARGET_CONSTANT_COMPATIBILITY */
136: public function getTarget(): int
137: {
138: switch (true) {
139: case $this->owner instanceof ReflectionClass:
140: return Attribute::TARGET_CLASS;
141: case $this->owner instanceof ReflectionFunction:
142: return Attribute::TARGET_FUNCTION;
143: case $this->owner instanceof ReflectionConstant:
144: return ReflectionAttributeAdapter::TARGET_CONSTANT_COMPATIBILITY;
145: case $this->owner instanceof ReflectionMethod:
146: return Attribute::TARGET_METHOD;
147: case $this->owner instanceof ReflectionProperty:
148: return Attribute::TARGET_PROPERTY;
149: case $this->owner instanceof ReflectionClassConstant:
150: return Attribute::TARGET_CLASS_CONSTANT;
151: case $this->owner instanceof ReflectionEnumCase:
152: return Attribute::TARGET_CLASS_CONSTANT;
153: case $this->owner instanceof ReflectionParameter:
154: return Attribute::TARGET_PARAMETER;
155: default:
156: throw new LogicException('unknown owner');
157: }
158: }
159:
160: public function isRepeated(): bool
161: {
162: return $this->isRepeated;
163: }
164:
165: /** @return non-empty-string */
166: public function __toString(): string
167: {
168: return ReflectionAttributeStringCast::toString($this);
169: }
170: }
171: