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: #[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: * @return ReflectionUnionType|ReflectionNamedType|ReflectionIntersectionType|null
241: */
242: public function getType(): ?\ReflectionType
243: {
244: return ReflectionType::fromTypeOrNull($this->betterReflectionParameter->getType());
245: }
246:
247: public function isPromoted(): bool
248: {
249: return $this->betterReflectionParameter->isPromoted();
250: }
251:
252: /**
253: * @param class-string|null $name
254: *
255: * @return list<ReflectionAttribute|FakeReflectionAttribute>
256: */
257: public function getAttributes(?string $name = null, int $flags = 0): array
258: {
259: if ($flags !== 0 && $flags !== ReflectionAttribute::IS_INSTANCEOF) {
260: throw new ValueError('Argument #2 ($flags) must be a valid attribute filter flag');
261: }
262:
263: if ($name !== null && $flags !== 0) {
264: $attributes = $this->betterReflectionParameter->getAttributesByInstance($name);
265: } elseif ($name !== null) {
266: $attributes = $this->betterReflectionParameter->getAttributesByName($name);
267: } else {
268: $attributes = $this->betterReflectionParameter->getAttributes();
269: }
270:
271: /** @psalm-suppress ImpureFunctionCall */
272: return array_map(static function (BetterReflectionAttribute $betterReflectionAttribute) {
273: return ReflectionAttributeFactory::create($betterReflectionAttribute);
274: }, $attributes);
275: }
276:
277: /**
278: * @return mixed
279: */
280: public function __get(string $name)
281: {
282: if ($name === 'name') {
283: return $this->betterReflectionParameter->getName();
284: }
285:
286: throw new OutOfBoundsException(sprintf('Property %s::$%s does not exist.', self::class, $name));
287: }
288: }
289: