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