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 variable.undefined */
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: #[ReturnTypeWillChange]
214: public function getDefaultValue()
215: {
216: return $this->betterReflectionParameter->getDefaultValue();
217: }
218:
219: public function getDefaultValueExpression(): Expr
220: {
221: return $this->betterReflectionParameter->getDefaultValueExpression();
222: }
223:
224: public function isDefaultValueConstant(): bool
225: {
226: return $this->betterReflectionParameter->isDefaultValueConstant();
227: }
228:
229: public function getDefaultValueConstantName(): string
230: {
231: return $this->betterReflectionParameter->getDefaultValueConstantName();
232: }
233:
234: public function hasType(): bool
235: {
236: return $this->betterReflectionParameter->hasType();
237: }
238:
239: /**
240: * @phpstan-ignore return.unusedType
241: * @return ReflectionUnionType|ReflectionNamedType|ReflectionIntersectionType|null
242: */
243: public function getType(): ?\ReflectionType
244: {
245: return ReflectionType::fromTypeOrNull($this->betterReflectionParameter->getType());
246: }
247:
248: public function isPromoted(): bool
249: {
250: return $this->betterReflectionParameter->isPromoted();
251: }
252:
253: /**
254: * @param class-string|null $name
255: *
256: * @return list<ReflectionAttribute|FakeReflectionAttribute>
257: */
258: public function getAttributes(?string $name = null, int $flags = 0): array
259: {
260: if ($flags !== 0 && $flags !== ReflectionAttribute::IS_INSTANCEOF) {
261: throw new ValueError('Argument #2 ($flags) must be a valid attribute filter flag');
262: }
263:
264: if ($name !== null && $flags !== 0) {
265: $attributes = $this->betterReflectionParameter->getAttributesByInstance($name);
266: } elseif ($name !== null) {
267: $attributes = $this->betterReflectionParameter->getAttributesByName($name);
268: } else {
269: $attributes = $this->betterReflectionParameter->getAttributes();
270: }
271:
272: /** @psalm-suppress ImpureFunctionCall */
273: return array_map(static function (BetterReflectionAttribute $betterReflectionAttribute) {
274: return ReflectionAttributeFactory::create($betterReflectionAttribute);
275: }, $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: