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