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