1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection\Adapter;
6:
7: use LogicException;
8: use OutOfBoundsException;
9: use PhpParser\Node\Expr;
10: use ReflectionClass as CoreReflectionClass;
11: use ReflectionFunctionAbstract as CoreReflectionFunctionAbstract;
12: use ReflectionParameter as CoreReflectionParameter;
13: use ReturnTypeWillChange;
14: use PHPStan\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute;
15: use PHPStan\BetterReflection\Reflection\ReflectionIntersectionType as BetterReflectionIntersectionType;
16: use PHPStan\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod;
17: use PHPStan\BetterReflection\Reflection\ReflectionNamedType as BetterReflectionNamedType;
18: use PHPStan\BetterReflection\Reflection\ReflectionParameter as BetterReflectionParameter;
19: use PHPStan\BetterReflection\Reflection\ReflectionType as BetterReflectionType;
20: use PHPStan\BetterReflection\Reflection\ReflectionUnionType as BetterReflectionUnionType;
21: use ValueError;
22:
23: use function array_map;
24: use function count;
25: use function sprintf;
26: use function strtolower;
27:
28: /**
29: * @psalm-suppress PropertyNotSetInConstructor
30: * @psalm-immutable
31: */
32: final class ReflectionParameter extends CoreReflectionParameter
33: {
34: private BetterReflectionParameter $betterReflectionParameter;
35: public function __construct(BetterReflectionParameter $betterReflectionParameter)
36: {
37: $this->betterReflectionParameter = $betterReflectionParameter;
38: unset($this->name);
39: }
40:
41: /** @return non-empty-string */
42: public function __toString(): string
43: {
44: return $this->betterReflectionParameter->__toString();
45: }
46:
47: public function getName(): string
48: {
49: return $this->betterReflectionParameter->getName();
50: }
51:
52: public function getBetterReflection(): BetterReflectionParameter
53: {
54: return $this->betterReflectionParameter;
55: }
56:
57: public function isPassedByReference(): bool
58: {
59: return $this->betterReflectionParameter->isPassedByReference();
60: }
61:
62: public function canBePassedByValue(): bool
63: {
64: return $this->betterReflectionParameter->canBePassedByValue();
65: }
66:
67: /**
68: * @return ReflectionFunction|ReflectionMethod
69: */
70: public function getDeclaringFunction(): CoreReflectionFunctionAbstract
71: {
72: $function = $this->betterReflectionParameter->getDeclaringFunction();
73:
74: if ($function instanceof BetterReflectionMethod) {
75: return new ReflectionMethod($function);
76: }
77:
78: return new ReflectionFunction($function);
79: }
80:
81: /** @return ReflectionClass|null */
82: public function getDeclaringClass(): ?CoreReflectionClass
83: {
84: $declaringClass = $this->betterReflectionParameter->getDeclaringClass();
85:
86: if ($declaringClass === null) {
87: return null;
88: }
89:
90: return new ReflectionClass($declaringClass);
91: }
92:
93: /** @return ReflectionClass|null */
94: public function getClass(): ?CoreReflectionClass
95: {
96: $type = $this->betterReflectionParameter->getType();
97:
98: if ($type === null) {
99: return null;
100: }
101:
102: if ($type instanceof BetterReflectionIntersectionType) {
103: return null;
104: }
105:
106: if ($type instanceof BetterReflectionNamedType) {
107: $classType = $type;
108: } else {
109: $unionTypes = $type->getTypes();
110:
111: if (count($unionTypes) !== 2) {
112: return null;
113: }
114:
115: if (! $type->allowsNull()) {
116: return null;
117: }
118:
119: foreach ($unionTypes as $unionInnerType) {
120: if (! $unionInnerType instanceof BetterReflectionNamedType) {
121: return null;
122: }
123:
124: if ($unionInnerType->allowsNull()) {
125: continue;
126: }
127:
128: $classType = $unionInnerType;
129: break;
130: }
131: }
132:
133: try {
134: /** @phpstan-ignore variable.undefined */
135: return new ReflectionClass($classType->getClass());
136: } catch (LogicException $exception) {
137: return null;
138: }
139: }
140:
141: public function isArray(): bool
142: {
143: return $this->isType($this->betterReflectionParameter->getType(), 'array');
144: }
145:
146: public function isCallable(): bool
147: {
148: return $this->isType($this->betterReflectionParameter->getType(), 'callable');
149: }
150:
151: /**
152: * For isArray() and isCallable().
153: * @param BetterReflectionNamedType|BetterReflectionUnionType|BetterReflectionIntersectionType|null $typeReflection
154: */
155: private function isType($typeReflection, string $type): bool
156: {
157: if ($typeReflection === null) {
158: return false;
159: }
160:
161: if ($typeReflection instanceof BetterReflectionIntersectionType) {
162: return false;
163: }
164:
165: $isOneOfAllowedTypes = static function (BetterReflectionType $namedType, string ...$types): bool {
166: foreach ($types as $type) {
167: if ($namedType instanceof BetterReflectionNamedType && strtolower($namedType->getName()) === $type) {
168: return true;
169: }
170: }
171:
172: return false;
173: };
174:
175: if ($typeReflection instanceof BetterReflectionUnionType) {
176: $unionTypes = $typeReflection->getTypes();
177:
178: foreach ($unionTypes as $unionType) {
179: if (! $isOneOfAllowedTypes($unionType, $type, 'null')) {
180: return false;
181: }
182: }
183:
184: return true;
185: }
186:
187: return $isOneOfAllowedTypes($typeReflection, $type);
188: }
189:
190: public function allowsNull(): bool
191: {
192: return $this->betterReflectionParameter->allowsNull();
193: }
194:
195: public function getPosition(): int
196: {
197: return $this->betterReflectionParameter->getPosition();
198: }
199:
200: public function isOptional(): bool
201: {
202: return $this->betterReflectionParameter->isOptional();
203: }
204:
205: public function isVariadic(): bool
206: {
207: return $this->betterReflectionParameter->isVariadic();
208: }
209:
210: public function isDefaultValueAvailable(): bool
211: {
212: return $this->betterReflectionParameter->isDefaultValueAvailable();
213: }
214:
215: #[ReturnTypeWillChange]
216: public function getDefaultValue()
217: {
218: return $this->betterReflectionParameter->getDefaultValue();
219: }
220:
221: public function getDefaultValueExpression(): Expr
222: {
223: return $this->betterReflectionParameter->getDefaultValueExpression();
224: }
225:
226: public function isDefaultValueConstant(): bool
227: {
228: return $this->betterReflectionParameter->isDefaultValueConstant();
229: }
230:
231: public function getDefaultValueConstantName(): string
232: {
233: return $this->betterReflectionParameter->getDefaultValueConstantName();
234: }
235:
236: public function hasType(): bool
237: {
238: return $this->betterReflectionParameter->hasType();
239: }
240:
241: /**
242: * @phpstan-ignore return.unusedType
243: * @return ReflectionUnionType|ReflectionNamedType|ReflectionIntersectionType|null
244: */
245: public function getType(): ?\ReflectionType
246: {
247: return ReflectionType::fromTypeOrNull($this->betterReflectionParameter->getType());
248: }
249:
250: public function isPromoted(): bool
251: {
252: return $this->betterReflectionParameter->isPromoted();
253: }
254:
255: /**
256: * @param class-string|null $name
257: *
258: * @return list<ReflectionAttribute|FakeReflectionAttribute>
259: */
260: public function getAttributes(?string $name = null, int $flags = 0): array
261: {
262: if ($flags !== 0 && $flags !== ReflectionAttribute::IS_INSTANCEOF) {
263: throw new ValueError('Argument #2 ($flags) must be a valid attribute filter flag');
264: }
265:
266: if ($name !== null && $flags !== 0) {
267: $attributes = $this->betterReflectionParameter->getAttributesByInstance($name);
268: } elseif ($name !== null) {
269: $attributes = $this->betterReflectionParameter->getAttributesByName($name);
270: } else {
271: $attributes = $this->betterReflectionParameter->getAttributes();
272: }
273:
274: /** @psalm-suppress ImpureFunctionCall */
275: return array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute) => ReflectionAttributeFactory::create($betterReflectionAttribute), $attributes);
276: }
277:
278: /**
279: * @return mixed
280: */
281: public function __get(string $name)
282: {
283: if ($name === 'name') {
284: return $this->betterReflectionParameter->getName();
285: }
286:
287: throw new OutOfBoundsException(sprintf('Property %s::$%s does not exist.', self::class, $name));
288: }
289: }
290: