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