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