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