1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection\Adapter;
6:
7: use OutOfBoundsException;
8: use ReflectionClass as CoreReflectionClass;
9: use ReflectionException as CoreReflectionException;
10: use ReflectionExtension as CoreReflectionExtension;
11: use ReflectionObject as CoreReflectionObject;
12: use ReturnTypeWillChange;
13: use PHPStan\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute;
14: use PHPStan\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass;
15: use PHPStan\BetterReflection\Reflection\ReflectionClassConstant as BetterReflectionClassConstant;
16: use PHPStan\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod;
17: use PHPStan\BetterReflection\Reflection\ReflectionObject as BetterReflectionObject;
18: use PHPStan\BetterReflection\Reflection\ReflectionProperty as BetterReflectionProperty;
19: use PHPStan\BetterReflection\Util\FileHelper;
20: use ValueError;
21:
22: use function array_combine;
23: use function array_map;
24: use function array_values;
25: use function assert;
26: use function func_num_args;
27: use function sprintf;
28: use function strtolower;
29:
30: /** @psalm-suppress PropertyNotSetInConstructor */
31: final class ReflectionObject extends CoreReflectionObject
32: {
33: /**
34: * @var BetterReflectionObject
35: */
36: private $betterReflectionObject;
37: public function __construct(BetterReflectionObject $betterReflectionObject)
38: {
39: $this->betterReflectionObject = $betterReflectionObject;
40: unset($this->name);
41: }
42:
43: public function __toString(): string
44: {
45: return $this->betterReflectionObject->__toString();
46: }
47:
48: public function getName(): string
49: {
50: return $this->betterReflectionObject->getName();
51: }
52:
53: public function isInternal(): bool
54: {
55: return $this->betterReflectionObject->isInternal();
56: }
57:
58: public function isUserDefined(): bool
59: {
60: return $this->betterReflectionObject->isUserDefined();
61: }
62:
63: public function isInstantiable(): bool
64: {
65: return $this->betterReflectionObject->isInstantiable();
66: }
67:
68: public function isCloneable(): bool
69: {
70: return $this->betterReflectionObject->isCloneable();
71: }
72:
73: /**
74: * {@inheritDoc}
75: */
76: #[ReturnTypeWillChange]
77: public function getFileName()
78: {
79: $fileName = $this->betterReflectionObject->getFileName();
80:
81: return $fileName !== null ? FileHelper::normalizeSystemPath($fileName) : false;
82: }
83:
84: /**
85: * {@inheritDoc}
86: */
87: #[ReturnTypeWillChange]
88: public function getStartLine()
89: {
90: return $this->betterReflectionObject->getStartLine();
91: }
92:
93: /**
94: * {@inheritDoc}
95: */
96: #[ReturnTypeWillChange]
97: public function getEndLine()
98: {
99: return $this->betterReflectionObject->getEndLine();
100: }
101:
102: /**
103: * {@inheritDoc}
104: */
105: #[ReturnTypeWillChange]
106: public function getDocComment()
107: {
108: return $this->betterReflectionObject->getDocComment() ?? false;
109: }
110:
111: public function getConstructor(): ?\PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod
112: {
113: $constructor = $this->betterReflectionObject->getConstructor();
114:
115: if ($constructor === null) {
116: return null;
117: }
118:
119: return new ReflectionMethod($constructor);
120: }
121:
122: /**
123: * {@inheritDoc}
124: */
125: public function hasMethod($name): bool
126: {
127: assert($name !== '');
128:
129: return $this->betterReflectionObject->hasMethod($this->getMethodRealName($name));
130: }
131:
132: /**
133: * {@inheritDoc}
134: */
135: public function getMethod($name): ReflectionMethod
136: {
137: assert($name !== '');
138:
139: $method = $this->betterReflectionObject->getMethod($this->getMethodRealName($name));
140:
141: if ($method === null) {
142: throw new CoreReflectionException(sprintf('Method %s::%s() does not exist', $this->betterReflectionObject->getName(), $name));
143: }
144:
145: return new ReflectionMethod($method);
146: }
147:
148: /**
149: * @param non-empty-string $name
150: *
151: * @return non-empty-string
152: */
153: private function getMethodRealName(string $name): string
154: {
155: $realMethodNames = array_map(static function (BetterReflectionMethod $method) : string {
156: return $method->getName();
157: }, $this->betterReflectionObject->getMethods());
158:
159: $methodNames = array_combine(array_map(static function (string $methodName) : string {
160: return strtolower($methodName);
161: }, $realMethodNames), $realMethodNames);
162:
163: $lowercasedName = strtolower($name);
164:
165: return $methodNames[$lowercasedName] ?? $name;
166: }
167:
168: /**
169: * @param int-mask-of<ReflectionMethod::IS_*>|null $filter
170: * @return ReflectionMethod[]
171: */
172: public function getMethods($filter = null): array
173: {
174: return array_values(array_map(static function (BetterReflectionMethod $method) : ReflectionMethod {
175: return new ReflectionMethod($method);
176: }, $this->betterReflectionObject->getMethods($filter ?? 0)));
177: }
178:
179: /**
180: * {@inheritDoc}
181: */
182: public function hasProperty($name): bool
183: {
184: assert($name !== '');
185:
186: return $this->betterReflectionObject->hasProperty($name);
187: }
188:
189: /**
190: * @param string $name
191: * @return ReflectionProperty
192: */
193: public function getProperty($name): \ReflectionProperty
194: {
195: assert($name !== '');
196:
197: $property = $this->betterReflectionObject->getProperty($name);
198:
199: if ($property === null) {
200: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionObject->getName(), $name));
201: }
202:
203: return new ReflectionProperty($property);
204: }
205:
206: /**
207: * @param int-mask-of<ReflectionProperty::IS_*>|null $filter
208: * @return ReflectionProperty[]
209: */
210: public function getProperties($filter = null): array
211: {
212: return array_values(array_map(static function (BetterReflectionProperty $property) : ReflectionProperty {
213: return new ReflectionProperty($property);
214: }, $this->betterReflectionObject->getProperties($filter ?? 0)));
215: }
216:
217: /**
218: * {@inheritDoc}
219: */
220: public function hasConstant($name): bool
221: {
222: assert($name !== '');
223:
224: return $this->betterReflectionObject->hasConstant($name);
225: }
226:
227: /**
228: * @param int-mask-of<ReflectionClassConstant::IS_*>|null $filter
229: *
230: * @return array<string, mixed>
231: */
232: public function getConstants(?int $filter = null): array
233: {
234: return array_map(static function (BetterReflectionClassConstant $betterConstant) {
235: return $betterConstant->getValue();
236: }, $this->betterReflectionObject->getConstants($filter ?? 0));
237: }
238:
239: /**
240: * {@inheritDoc}
241: */
242: #[ReturnTypeWillChange]
243: public function getConstant($name)
244: {
245: assert($name !== '');
246:
247: $betterReflectionConstant = $this->betterReflectionObject->getConstant($name);
248: if ($betterReflectionConstant === null) {
249: return false;
250: }
251:
252: return $betterReflectionConstant->getValue();
253: }
254:
255: /**
256: * @param string $name
257: * @return ReflectionClassConstant|false
258: */
259: #[ReturnTypeWillChange]
260: public function getReflectionConstant($name)
261: {
262: assert($name !== '');
263:
264: $betterReflectionConstant = $this->betterReflectionObject->getConstant($name);
265:
266: if ($betterReflectionConstant === null) {
267: return false;
268: }
269:
270: return new ReflectionClassConstant($betterReflectionConstant);
271: }
272:
273: /**
274: * @param int-mask-of<ReflectionClassConstant::IS_*>|null $filter
275: *
276: * @return list<ReflectionClassConstant>
277: */
278: public function getReflectionConstants(?int $filter = null): array
279: {
280: return array_values(array_map(static function (BetterReflectionClassConstant $betterConstant) : ReflectionClassConstant {
281: return new ReflectionClassConstant($betterConstant);
282: }, $this->betterReflectionObject->getConstants($filter ?? 0)));
283: }
284:
285: /** @return array<class-string, ReflectionClass> */
286: public function getInterfaces(): array
287: {
288: return array_map(static function (BetterReflectionClass $interface) : ReflectionClass {
289: return new ReflectionClass($interface);
290: }, $this->betterReflectionObject->getInterfaces());
291: }
292:
293: /** @return list<class-string> */
294: public function getInterfaceNames(): array
295: {
296: return $this->betterReflectionObject->getInterfaceNames();
297: }
298:
299: public function isInterface(): bool
300: {
301: return $this->betterReflectionObject->isInterface();
302: }
303:
304: /** @return array<trait-string, ReflectionClass> */
305: public function getTraits(): array
306: {
307: $traits = $this->betterReflectionObject->getTraits();
308:
309: /** @var list<trait-string> $traitNames */
310: $traitNames = array_map(static function (BetterReflectionClass $trait) : string {
311: return $trait->getName();
312: }, $traits);
313:
314: return array_combine($traitNames, array_map(static function (BetterReflectionClass $trait) : ReflectionClass {
315: return new ReflectionClass($trait);
316: }, $traits));
317: }
318:
319: /** @return list<trait-string> */
320: public function getTraitNames(): array
321: {
322: return $this->betterReflectionObject->getTraitNames();
323: }
324:
325: /** @return array<string, string> */
326: public function getTraitAliases(): array
327: {
328: return $this->betterReflectionObject->getTraitAliases();
329: }
330:
331: public function isTrait(): bool
332: {
333: return $this->betterReflectionObject->isTrait();
334: }
335:
336: public function isAbstract(): bool
337: {
338: return $this->betterReflectionObject->isAbstract();
339: }
340:
341: public function isFinal(): bool
342: {
343: return $this->betterReflectionObject->isFinal();
344: }
345:
346: public function isReadOnly(): bool
347: {
348: return $this->betterReflectionObject->isReadOnly();
349: }
350:
351: public function getModifiers(): int
352: {
353: return $this->betterReflectionObject->getModifiers();
354: }
355:
356: /**
357: * {@inheritDoc}
358: */
359: public function isInstance($object): bool
360: {
361: return $this->betterReflectionObject->isInstance($object);
362: }
363:
364: /**
365: * @param mixed $arg
366: * @param mixed ...$args
367: *
368: * @return object
369: */
370: #[ReturnTypeWillChange]
371: public function newInstance($arg = null, ...$args)
372: {
373: throw new Exception\NotImplemented('Not implemented');
374: }
375:
376: /**
377: * {@inheritDoc}
378: */
379: #[ReturnTypeWillChange]
380: public function newInstanceWithoutConstructor()
381: {
382: throw new Exception\NotImplemented('Not implemented');
383: }
384:
385: /**
386: * {@inheritDoc}
387: */
388: #[ReturnTypeWillChange]
389: public function newInstanceArgs(?array $args = null)
390: {
391: throw new Exception\NotImplemented('Not implemented');
392: }
393:
394: /**
395: * @return ReflectionClass|false
396: */
397: #[ReturnTypeWillChange]
398: public function getParentClass()
399: {
400: $parentClass = $this->betterReflectionObject->getParentClass();
401:
402: if ($parentClass === null) {
403: return false;
404: }
405:
406: return new ReflectionClass($parentClass);
407: }
408:
409: /**
410: * {@inheritDoc}
411: */
412: public function isSubclassOf($class): bool
413: {
414: $realParentClassNames = $this->betterReflectionObject->getParentClassNames();
415:
416: $parentClassNames = array_combine(array_map(static function (string $parentClassName) : string {
417: return strtolower($parentClassName);
418: }, $realParentClassNames), $realParentClassNames);
419:
420: $className = $class instanceof CoreReflectionClass ? $class->getName() : $class;
421: $lowercasedClassName = strtolower($className);
422:
423: $realParentClassName = $parentClassNames[$lowercasedClassName] ?? $className;
424:
425: return $this->betterReflectionObject->isSubclassOf($realParentClassName);
426: }
427:
428: /**
429: * @return array<string, mixed>
430: *
431: * @psalm-suppress LessSpecificImplementedReturnType
432: */
433: public function getStaticProperties(): array
434: {
435: return $this->betterReflectionObject->getStaticProperties();
436: }
437:
438: /**
439: * {@inheritDoc}
440: */
441: #[ReturnTypeWillChange]
442: public function getStaticPropertyValue($name, $default = null)
443: {
444: assert($name !== '');
445:
446: $betterReflectionProperty = $this->betterReflectionObject->getProperty($name);
447:
448: if ($betterReflectionProperty === null) {
449: if (func_num_args() === 2) {
450: return $default;
451: }
452:
453: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionObject->getName(), $name));
454: }
455:
456: $property = new ReflectionProperty($betterReflectionProperty);
457:
458: if (! $property->isStatic()) {
459: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionObject->getName(), $name));
460: }
461:
462: return $property->getValue();
463: }
464:
465: /**
466: * {@inheritDoc}
467: */
468: public function setStaticPropertyValue($name, $value): void
469: {
470: assert($name !== '');
471:
472: $betterReflectionProperty = $this->betterReflectionObject->getProperty($name);
473:
474: if ($betterReflectionProperty === null) {
475: throw new CoreReflectionException(sprintf('Class %s does not have a property named %s', $this->betterReflectionObject->getName(), $name));
476: }
477:
478: $property = new ReflectionProperty($betterReflectionProperty);
479:
480: if (! $property->isStatic()) {
481: throw new CoreReflectionException(sprintf('Class %s does not have a property named %s', $this->betterReflectionObject->getName(), $name));
482: }
483:
484: $property->setValue($value);
485: }
486:
487: /** @return array<string, scalar|array<scalar>|null> */
488: public function getDefaultProperties(): array
489: {
490: return $this->betterReflectionObject->getDefaultProperties();
491: }
492:
493: public function isIterateable(): bool
494: {
495: return $this->betterReflectionObject->isIterateable();
496: }
497:
498: public function isIterable(): bool
499: {
500: return $this->isIterateable();
501: }
502:
503: /**
504: * @param \ReflectionClass|string $interface
505: */
506: public function implementsInterface($interface): bool
507: {
508: $realInterfaceNames = $this->betterReflectionObject->getInterfaceNames();
509:
510: $interfaceNames = array_combine(array_map(static function (string $interfaceName) : string {
511: return strtolower($interfaceName);
512: }, $realInterfaceNames), $realInterfaceNames);
513:
514: $interfaceName = $interface instanceof CoreReflectionClass ? $interface->getName() : $interface;
515: $lowercasedIntefaceName = strtolower($interfaceName);
516:
517: $realInterfaceName = $interfaceNames[$lowercasedIntefaceName] ?? $interfaceName;
518:
519: return $this->betterReflectionObject->implementsInterface($realInterfaceName);
520: }
521:
522: public function getExtension(): ?CoreReflectionExtension
523: {
524: throw new Exception\NotImplemented('Not implemented');
525: }
526:
527: /**
528: * {@inheritDoc}
529: */
530: #[ReturnTypeWillChange]
531: public function getExtensionName()
532: {
533: return $this->betterReflectionObject->getExtensionName() ?? false;
534: }
535:
536: public function inNamespace(): bool
537: {
538: return $this->betterReflectionObject->inNamespace();
539: }
540:
541: public function getNamespaceName(): string
542: {
543: return $this->betterReflectionObject->getNamespaceName() ?? '';
544: }
545:
546: public function getShortName(): string
547: {
548: return $this->betterReflectionObject->getShortName();
549: }
550:
551: public function isAnonymous(): bool
552: {
553: return $this->betterReflectionObject->isAnonymous();
554: }
555:
556: /**
557: * @param class-string|null $name
558: *
559: * @return list<ReflectionAttribute|FakeReflectionAttribute>
560: */
561: public function getAttributes(?string $name = null, int $flags = 0): array
562: {
563: if ($flags !== 0 && $flags !== ReflectionAttribute::IS_INSTANCEOF) {
564: throw new ValueError('Argument #2 ($flags) must be a valid attribute filter flag');
565: }
566:
567: if ($name !== null && $flags !== 0) {
568: $attributes = $this->betterReflectionObject->getAttributesByInstance($name);
569: } elseif ($name !== null) {
570: $attributes = $this->betterReflectionObject->getAttributesByName($name);
571: } else {
572: $attributes = $this->betterReflectionObject->getAttributes();
573: }
574:
575: return array_map(static function (BetterReflectionAttribute $betterReflectionAttribute) {
576: return ReflectionAttributeFactory::create($betterReflectionAttribute);
577: }, $attributes);
578: }
579:
580: public function isEnum(): bool
581: {
582: return $this->betterReflectionObject->isEnum();
583: }
584:
585: /**
586: * @return mixed
587: */
588: public function __get(string $name)
589: {
590: if ($name === 'name') {
591: return $this->betterReflectionObject->getName();
592: }
593:
594: throw new OutOfBoundsException(sprintf('Property %s::$%s does not exist.', self::class, $name));
595: }
596: }
597: