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