1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Reflection;
4:
5: use Attribute;
6: use PhpParser\Node\Arg;
7: use PhpParser\Node\Expr\StaticCall;
8: use PhpParser\Node\Identifier;
9: use PhpParser\Node\Name\FullyQualified;
10: use PHPStan\Analyser\ArgumentsNormalizer;
11: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass;
12: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnum;
13: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumBackedCase;
14: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod;
15: use PHPStan\Php\PhpVersion;
16: use PHPStan\PhpDoc\PhpDocInheritanceResolver;
17: use PHPStan\PhpDoc\ResolvedPhpDocBlock;
18: use PHPStan\PhpDoc\StubPhpDocProvider;
19: use PHPStan\PhpDoc\Tag\ExtendsTag;
20: use PHPStan\PhpDoc\Tag\ImplementsTag;
21: use PHPStan\PhpDoc\Tag\MethodTag;
22: use PHPStan\PhpDoc\Tag\MixinTag;
23: use PHPStan\PhpDoc\Tag\PropertyTag;
24: use PHPStan\PhpDoc\Tag\RequireExtendsTag;
25: use PHPStan\PhpDoc\Tag\RequireImplementsTag;
26: use PHPStan\PhpDoc\Tag\SealedTypeTag;
27: use PHPStan\PhpDoc\Tag\TemplateTag;
28: use PHPStan\PhpDoc\Tag\TypeAliasImportTag;
29: use PHPStan\PhpDoc\Tag\TypeAliasTag;
30: use PHPStan\Reflection\Deprecation\DeprecationProvider;
31: use PHPStan\Reflection\Php\PhpClassReflectionExtension;
32: use PHPStan\Reflection\Php\PhpPropertyReflection;
33: use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension;
34: use PHPStan\Reflection\RequireExtension\RequireExtendsMethodsClassReflectionExtension;
35: use PHPStan\Reflection\RequireExtension\RequireExtendsPropertiesClassReflectionExtension;
36: use PHPStan\Reflection\SignatureMap\SignatureMapProvider;
37: use PHPStan\ShouldNotHappenException;
38: use PHPStan\Type\CircularTypeAliasDefinitionException;
39: use PHPStan\Type\Constant\ConstantIntegerType;
40: use PHPStan\Type\ErrorType;
41: use PHPStan\Type\FileTypeMapper;
42: use PHPStan\Type\Generic\GenericObjectType;
43: use PHPStan\Type\Generic\TemplateTypeFactory;
44: use PHPStan\Type\Generic\TemplateTypeHelper;
45: use PHPStan\Type\Generic\TemplateTypeMap;
46: use PHPStan\Type\Generic\TemplateTypeScope;
47: use PHPStan\Type\Generic\TemplateTypeVariance;
48: use PHPStan\Type\Generic\TemplateTypeVarianceMap;
49: use PHPStan\Type\Generic\TypeProjectionHelper;
50: use PHPStan\Type\ObjectType;
51: use PHPStan\Type\Type;
52: use PHPStan\Type\TypeAlias;
53: use PHPStan\Type\TypehintHelper;
54: use PHPStan\Type\VerbosityLevel;
55: use ReflectionException;
56: use function array_diff;
57: use function array_filter;
58: use function array_key_exists;
59: use function array_map;
60: use function array_merge;
61: use function array_shift;
62: use function array_unique;
63: use function array_values;
64: use function count;
65: use function implode;
66: use function in_array;
67: use function is_bool;
68: use function is_file;
69: use function is_int;
70: use function reset;
71: use function sprintf;
72: use function strtolower;
73:
74: /**
75: * @api
76: */
77: final class ClassReflection
78: {
79:
80: /** @var ExtendedMethodReflection[] */
81: private array $methods = [];
82:
83: /** @var ExtendedPropertyReflection[] */
84: private array $properties = [];
85:
86: /** @var RealClassClassConstantReflection[] */
87: private array $constants = [];
88:
89: /** @var EnumCaseReflection[]|null */
90: private ?array $enumCases = null;
91:
92: /** @var int[]|null */
93: private ?array $classHierarchyDistances = null;
94:
95: private ?string $deprecatedDescription = null;
96:
97: private ?bool $isDeprecated = null;
98:
99: private ?bool $isGeneric = null;
100:
101: private ?bool $isInternal = null;
102:
103: private ?bool $isFinal = null;
104:
105: private ?bool $isImmutable = null;
106:
107: private ?bool $hasConsistentConstructor = null;
108:
109: private ?bool $acceptsNamedArguments = null;
110:
111: private ?TemplateTypeMap $templateTypeMap = null;
112:
113: private ?TemplateTypeMap $activeTemplateTypeMap = null;
114:
115: private ?TemplateTypeVarianceMap $defaultCallSiteVarianceMap = null;
116:
117: private ?TemplateTypeVarianceMap $callSiteVarianceMap = null;
118:
119: /** @var array<string,ClassReflection>|null */
120: private ?array $ancestors = null;
121:
122: private ?string $cacheKey = null;
123:
124: /** @var array<string, bool> */
125: private array $subclasses = [];
126:
127: private string|false|null $filename = false;
128:
129: private string|false|null $reflectionDocComment = false;
130:
131: private false|ResolvedPhpDocBlock $resolvedPhpDocBlock = false;
132:
133: private false|ResolvedPhpDocBlock $traitContextResolvedPhpDocBlock = false;
134:
135: /** @var array<string, ClassReflection>|null */
136: private ?array $cachedInterfaces = null;
137:
138: private ClassReflection|false|null $cachedParentClass = false;
139:
140: /** @var array<string, TypeAlias>|null */
141: private ?array $typeAliases = null;
142:
143: /** @var array<string, true> */
144: private static array $resolvingTypeAliasImports = [];
145:
146: /** @var array<string, bool> */
147: private array $hasMethodCache = [];
148:
149: /** @var array<string, bool> */
150: private array $hasPropertyCache = [];
151:
152: /**
153: * @param PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions
154: * @param MethodsClassReflectionExtension[] $methodsClassReflectionExtensions
155: * @param AllowedSubTypesClassReflectionExtension[] $allowedSubTypesClassReflectionExtensions
156: * @param string[] $universalObjectCratesClasses
157: */
158: public function __construct(
159: private ReflectionProvider $reflectionProvider,
160: private InitializerExprTypeResolver $initializerExprTypeResolver,
161: private FileTypeMapper $fileTypeMapper,
162: private StubPhpDocProvider $stubPhpDocProvider,
163: private PhpDocInheritanceResolver $phpDocInheritanceResolver,
164: private PhpVersion $phpVersion,
165: private SignatureMapProvider $signatureMapProvider,
166: private DeprecationProvider $deprecationProvider,
167: private AttributeReflectionFactory $attributeReflectionFactory,
168: private array $propertiesClassReflectionExtensions,
169: private array $methodsClassReflectionExtensions,
170: private array $allowedSubTypesClassReflectionExtensions,
171: private RequireExtendsPropertiesClassReflectionExtension $requireExtendsPropertiesClassReflectionExtension,
172: private RequireExtendsMethodsClassReflectionExtension $requireExtendsMethodsClassReflectionExtension,
173: private string $displayName,
174: private ReflectionClass|ReflectionEnum $reflection,
175: private ?string $anonymousFilename,
176: private ?TemplateTypeMap $resolvedTemplateTypeMap,
177: private ?ResolvedPhpDocBlock $stubPhpDocBlock,
178: private array $universalObjectCratesClasses,
179: private ?string $extraCacheKey = null,
180: private ?TemplateTypeVarianceMap $resolvedCallSiteVarianceMap = null,
181: private ?bool $finalByKeywordOverride = null,
182: )
183: {
184: }
185:
186: public function getNativeReflection(): ReflectionClass|ReflectionEnum
187: {
188: return $this->reflection;
189: }
190:
191: public function getFileName(): ?string
192: {
193: if (!is_bool($this->filename)) {
194: return $this->filename;
195: }
196:
197: if ($this->anonymousFilename !== null) {
198: return $this->filename = $this->anonymousFilename;
199: }
200: $fileName = $this->reflection->getFileName();
201: if ($fileName === false) {
202: return $this->filename = null;
203: }
204:
205: if (!is_file($fileName)) {
206: return $this->filename = null;
207: }
208:
209: return $this->filename = $fileName;
210: }
211:
212: public function getParentClass(): ?ClassReflection
213: {
214: if (!is_bool($this->cachedParentClass)) {
215: return $this->cachedParentClass;
216: }
217:
218: $parentClass = $this->reflection->getParentClass();
219:
220: if ($parentClass === false) {
221: return $this->cachedParentClass = null;
222: }
223:
224: $extendsTag = $this->getFirstExtendsTag();
225:
226: if ($extendsTag !== null && $this->isValidAncestorType($extendsTag->getType(), [$parentClass->getName()])) {
227: $extendedType = $extendsTag->getType();
228:
229: if ($this->isGeneric()) {
230: $extendedType = TemplateTypeHelper::resolveTemplateTypes(
231: $extendedType,
232: $this->getPossiblyIncompleteActiveTemplateTypeMap(),
233: $this->getCallSiteVarianceMap(),
234: TemplateTypeVariance::createStatic(),
235: );
236: }
237:
238: if (!$extendedType instanceof GenericObjectType) {
239: return $this->reflectionProvider->getClass($parentClass->getName());
240: }
241:
242: return $extendedType->getClassReflection() ?? $this->reflectionProvider->getClass($parentClass->getName());
243: }
244:
245: $parentReflection = $this->reflectionProvider->getClass($parentClass->getName());
246: if ($parentReflection->isGeneric()) {
247: return $parentReflection->withTypes(
248: array_values($parentReflection->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()),
249: );
250: }
251:
252: $this->cachedParentClass = $parentReflection;
253:
254: return $parentReflection;
255: }
256:
257: /**
258: * @return class-string
259: */
260: public function getName(): string
261: {
262: return $this->reflection->getName();
263: }
264:
265: public function getDisplayName(bool $withTemplateTypes = true): string
266: {
267: if (
268: $withTemplateTypes === false
269: || $this->resolvedTemplateTypeMap === null
270: || count($this->resolvedTemplateTypeMap->getTypes()) === 0
271: ) {
272: return $this->displayName;
273: }
274:
275: $templateTypes = [];
276: $variances = $this->getCallSiteVarianceMap()->getVariances();
277: foreach ($this->getActiveTemplateTypeMap()->getTypes() as $name => $templateType) {
278: $variance = $variances[$name] ?? null;
279: if ($variance === null) {
280: continue;
281: }
282:
283: $templateTypes[] = TypeProjectionHelper::describe($templateType, $variance, VerbosityLevel::typeOnly());
284: }
285:
286: return $this->displayName . '<' . implode(',', $templateTypes) . '>';
287: }
288:
289: public function getCacheKey(): string
290: {
291: $cacheKey = $this->cacheKey;
292: if ($cacheKey !== null) {
293: return $this->cacheKey;
294: }
295:
296: $cacheKey = $this->displayName;
297:
298: if ($this->resolvedTemplateTypeMap !== null) {
299: $templateTypes = [];
300: $variances = $this->getCallSiteVarianceMap()->getVariances();
301: foreach ($this->getActiveTemplateTypeMap()->getTypes() as $name => $templateType) {
302: $variance = $variances[$name] ?? null;
303: if ($variance === null) {
304: continue;
305: }
306:
307: $templateTypes[] = TypeProjectionHelper::describe($templateType, $variance, VerbosityLevel::cache());
308: }
309:
310: $cacheKey .= '<' . implode(',', $templateTypes) . '>';
311: }
312:
313: if ($this->hasFinalByKeywordOverride()) {
314: $cacheKey .= '-f=' . ($this->isFinalByKeyword() ? 't' : 'f');
315: }
316:
317: if ($this->extraCacheKey !== null) {
318: $cacheKey .= '-' . $this->extraCacheKey;
319: }
320:
321: $this->cacheKey = $cacheKey;
322:
323: return $cacheKey;
324: }
325:
326: /**
327: * @return int[]
328: */
329: public function getClassHierarchyDistances(): array
330: {
331: if ($this->classHierarchyDistances === null) {
332: $distance = 0;
333: $distances = [
334: $this->getName() => $distance,
335: ];
336: $currentClassReflection = $this->getNativeReflection();
337: foreach ($this->collectTraits($this->getNativeReflection()) as $trait) {
338: $distance++;
339: if (array_key_exists($trait->getName(), $distances)) {
340: continue;
341: }
342:
343: $distances[$trait->getName()] = $distance;
344: }
345:
346: while ($currentClassReflection->getParentClass() !== false) {
347: $distance++;
348: $parentClassName = $currentClassReflection->getParentClass()->getName();
349: if (!array_key_exists($parentClassName, $distances)) {
350: $distances[$parentClassName] = $distance;
351: }
352: $currentClassReflection = $currentClassReflection->getParentClass();
353: foreach ($this->collectTraits($currentClassReflection) as $trait) {
354: $distance++;
355: if (array_key_exists($trait->getName(), $distances)) {
356: continue;
357: }
358:
359: $distances[$trait->getName()] = $distance;
360: }
361: }
362: foreach ($this->getNativeReflection()->getInterfaces() as $interface) {
363: $distance++;
364: if (array_key_exists($interface->getName(), $distances)) {
365: continue;
366: }
367:
368: $distances[$interface->getName()] = $distance;
369: }
370:
371: $this->classHierarchyDistances = $distances;
372: }
373:
374: return $this->classHierarchyDistances;
375: }
376:
377: /**
378: * @return list<ReflectionClass>
379: */
380: private function collectTraits(ReflectionClass|ReflectionEnum $class): array
381: {
382: $traits = [];
383: $traitsLeftToAnalyze = $class->getTraits();
384:
385: while (count($traitsLeftToAnalyze) !== 0) {
386: $trait = reset($traitsLeftToAnalyze);
387: $traits[] = $trait;
388:
389: foreach ($trait->getTraits() as $subTrait) {
390: if (in_array($subTrait, $traits, true)) {
391: continue;
392: }
393:
394: $traitsLeftToAnalyze[] = $subTrait;
395: }
396:
397: array_shift($traitsLeftToAnalyze);
398: }
399:
400: return $traits;
401: }
402:
403: public function allowsDynamicProperties(): bool
404: {
405: if ($this->isEnum()) {
406: return false;
407: }
408:
409: if (!$this->phpVersion->deprecatesDynamicProperties()) {
410: return true;
411: }
412:
413: if ($this->isReadOnly()) {
414: return false;
415: }
416:
417: if (UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate(
418: $this->reflectionProvider,
419: $this,
420: )) {
421: return true;
422: }
423:
424: $class = $this;
425: $attributes = $class->reflection->getAttributes('AllowDynamicProperties');
426: while (count($attributes) === 0 && $class->getParentClass() !== null) {
427: $attributes = $class->getParentClass()->reflection->getAttributes('AllowDynamicProperties');
428: $class = $class->getParentClass();
429: }
430:
431: return count($attributes) > 0;
432: }
433:
434: private function allowsDynamicPropertiesExtensions(): bool
435: {
436: if ($this->allowsDynamicProperties()) {
437: return true;
438: }
439:
440: $hasMagicMethod = $this->hasNativeMethod('__get') || $this->hasNativeMethod('__set') || $this->hasNativeMethod('__isset');
441: if ($hasMagicMethod) {
442: return true;
443: }
444:
445: foreach ($this->getRequireExtendsTags() as $extendsTag) {
446: $type = $extendsTag->getType();
447: if (!$type instanceof ObjectType) {
448: continue;
449: }
450:
451: $reflection = $type->getClassReflection();
452: if ($reflection === null) {
453: continue;
454: }
455:
456: if (!$reflection->allowsDynamicPropertiesExtensions()) {
457: continue;
458: }
459:
460: return true;
461: }
462:
463: return false;
464: }
465:
466: public function hasProperty(string $propertyName): bool
467: {
468: if (array_key_exists($propertyName, $this->hasPropertyCache)) {
469: return $this->hasPropertyCache[$propertyName];
470: }
471:
472: if ($this->isEnum()) {
473: return $this->hasPropertyCache[$propertyName] = $this->hasNativeProperty($propertyName);
474: }
475:
476: foreach ($this->propertiesClassReflectionExtensions as $i => $extension) {
477: if ($i > 0 && !$this->allowsDynamicPropertiesExtensions()) {
478: break;
479: }
480: if ($extension->hasProperty($this, $propertyName)) {
481: return $this->hasPropertyCache[$propertyName] = true;
482: }
483: }
484:
485: if ($this->requireExtendsPropertiesClassReflectionExtension->hasProperty($this, $propertyName)) {
486: return $this->hasPropertyCache[$propertyName] = true;
487: }
488:
489: return $this->hasPropertyCache[$propertyName] = false;
490: }
491:
492: public function hasMethod(string $methodName): bool
493: {
494: if (array_key_exists($methodName, $this->hasMethodCache)) {
495: return $this->hasMethodCache[$methodName];
496: }
497:
498: foreach ($this->methodsClassReflectionExtensions as $extension) {
499: if ($extension->hasMethod($this, $methodName)) {
500: return $this->hasMethodCache[$methodName] = true;
501: }
502: }
503:
504: if ($this->requireExtendsMethodsClassReflectionExtension->hasMethod($this, $methodName)) {
505: return $this->hasMethodCache[$methodName] = true;
506: }
507:
508: return $this->hasMethodCache[$methodName] = false;
509: }
510:
511: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
512: {
513: $key = $methodName;
514: if ($scope->isInClass()) {
515: $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey());
516: }
517:
518: if (!isset($this->methods[$key])) {
519: foreach ($this->methodsClassReflectionExtensions as $extension) {
520: if (!$extension->hasMethod($this, $methodName)) {
521: continue;
522: }
523:
524: $method = $this->wrapExtendedMethod($extension->getMethod($this, $methodName));
525: if ($scope->canCallMethod($method)) {
526: return $this->methods[$key] = $method;
527: }
528: $this->methods[$key] = $method;
529: }
530: }
531:
532: if (!isset($this->methods[$key])) {
533: if ($this->requireExtendsMethodsClassReflectionExtension->hasMethod($this, $methodName)) {
534: $method = $this->requireExtendsMethodsClassReflectionExtension->getMethod($this, $methodName);
535: $this->methods[$key] = $method;
536: }
537: }
538:
539: if (!isset($this->methods[$key])) {
540: throw new MissingMethodFromReflectionException($this->getName(), $methodName);
541: }
542:
543: return $this->methods[$key];
544: }
545:
546: private function wrapExtendedMethod(MethodReflection $method): ExtendedMethodReflection
547: {
548: if ($method instanceof ExtendedMethodReflection) {
549: return $method;
550: }
551:
552: return new WrappedExtendedMethodReflection($method);
553: }
554:
555: private function wrapExtendedProperty(string $propertyName, PropertyReflection $method): ExtendedPropertyReflection
556: {
557: if ($method instanceof ExtendedPropertyReflection) {
558: return $method;
559: }
560:
561: return new WrappedExtendedPropertyReflection($propertyName, $method);
562: }
563:
564: public function hasNativeMethod(string $methodName): bool
565: {
566: return $this->getPhpExtension()->hasNativeMethod($this, $methodName);
567: }
568:
569: public function getNativeMethod(string $methodName): ExtendedMethodReflection
570: {
571: if (!$this->hasNativeMethod($methodName)) {
572: throw new MissingMethodFromReflectionException($this->getName(), $methodName);
573: }
574: return $this->getPhpExtension()->getNativeMethod($this, $methodName);
575: }
576:
577: public function hasConstructor(): bool
578: {
579: return $this->findConstructor() !== null;
580: }
581:
582: public function getConstructor(): ExtendedMethodReflection
583: {
584: $constructor = $this->findConstructor();
585: if ($constructor === null) {
586: throw new ShouldNotHappenException();
587: }
588: return $this->getNativeMethod($constructor->getName());
589: }
590:
591: private function findConstructor(): ?ReflectionMethod
592: {
593: $constructor = $this->reflection->getConstructor();
594: if ($constructor === null) {
595: return null;
596: }
597:
598: if ($this->phpVersion->supportsLegacyConstructor()) {
599: return $constructor;
600: }
601:
602: if (strtolower($constructor->getName()) !== '__construct') {
603: return null;
604: }
605:
606: return $constructor;
607: }
608:
609: private function getPhpExtension(): PhpClassReflectionExtension
610: {
611: $extension = $this->methodsClassReflectionExtensions[0];
612: if (!$extension instanceof PhpClassReflectionExtension) {
613: throw new ShouldNotHappenException();
614: }
615:
616: return $extension;
617: }
618:
619: /** @internal */
620: public function evictPrivateSymbols(): void
621: {
622: foreach ($this->constants as $name => $constant) {
623: if (!$constant->isPrivate()) {
624: continue;
625: }
626:
627: unset($this->constants[$name]);
628: }
629: foreach ($this->properties as $name => $property) {
630: if (!$property->isPrivate()) {
631: continue;
632: }
633:
634: unset($this->properties[$name]);
635: }
636: foreach ($this->methods as $name => $method) {
637: if (!$method->isPrivate()) {
638: continue;
639: }
640:
641: unset($this->methods[$name]);
642: }
643: $this->getPhpExtension()->evictPrivateSymbols($this->getCacheKey());
644: }
645:
646: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
647: {
648: if ($this->isEnum()) {
649: return $this->getNativeProperty($propertyName);
650: }
651:
652: $key = $propertyName;
653: if ($scope->isInClass()) {
654: $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey());
655: }
656:
657: if (!isset($this->properties[$key])) {
658: foreach ($this->propertiesClassReflectionExtensions as $i => $extension) {
659: if ($i > 0 && !$this->allowsDynamicPropertiesExtensions()) {
660: break;
661: }
662:
663: if (!$extension->hasProperty($this, $propertyName)) {
664: continue;
665: }
666:
667: $property = $this->wrapExtendedProperty($propertyName, $extension->getProperty($this, $propertyName));
668: if ($scope->canReadProperty($property)) {
669: return $this->properties[$key] = $property;
670: }
671: $this->properties[$key] = $property;
672: }
673: }
674:
675: if (!isset($this->properties[$key])) {
676: if ($this->requireExtendsPropertiesClassReflectionExtension->hasProperty($this, $propertyName)) {
677: $property = $this->requireExtendsPropertiesClassReflectionExtension->getProperty($this, $propertyName);
678: $this->properties[$key] = $property;
679: }
680: }
681:
682: if (!isset($this->properties[$key])) {
683: throw new MissingPropertyFromReflectionException($this->getName(), $propertyName);
684: }
685:
686: return $this->properties[$key];
687: }
688:
689: public function hasNativeProperty(string $propertyName): bool
690: {
691: return $this->getPhpExtension()->hasProperty($this, $propertyName);
692: }
693:
694: public function getNativeProperty(string $propertyName): PhpPropertyReflection
695: {
696: if (!$this->hasNativeProperty($propertyName)) {
697: throw new MissingPropertyFromReflectionException($this->getName(), $propertyName);
698: }
699:
700: return $this->getPhpExtension()->getNativeProperty($this, $propertyName);
701: }
702:
703: public function isAbstract(): bool
704: {
705: return $this->reflection->isAbstract();
706: }
707:
708: public function isInterface(): bool
709: {
710: return $this->reflection->isInterface();
711: }
712:
713: public function isTrait(): bool
714: {
715: return $this->reflection->isTrait();
716: }
717:
718: /**
719: * @phpstan-assert-if-true ReflectionEnum $this->reflection
720: * @phpstan-assert-if-true ReflectionEnum $this->getNativeReflection()
721: */
722: public function isEnum(): bool
723: {
724: return $this->reflection instanceof ReflectionEnum && $this->reflection->isEnum();
725: }
726:
727: /**
728: * @return 'Interface'|'Trait'|'Enum'|'Class'
729: */
730: public function getClassTypeDescription(): string
731: {
732: if ($this->isInterface()) {
733: return 'Interface';
734: } elseif ($this->isTrait()) {
735: return 'Trait';
736: } elseif ($this->isEnum()) {
737: return 'Enum';
738: }
739:
740: return 'Class';
741: }
742:
743: public function isReadOnly(): bool
744: {
745: return $this->reflection->isReadOnly();
746: }
747:
748: public function isBackedEnum(): bool
749: {
750: if (!$this->reflection instanceof ReflectionEnum) {
751: return false;
752: }
753:
754: return $this->reflection->isBacked();
755: }
756:
757: public function getBackedEnumType(): ?Type
758: {
759: if (!$this->reflection instanceof ReflectionEnum) {
760: return null;
761: }
762:
763: if (!$this->reflection->isBacked()) {
764: return null;
765: }
766:
767: return TypehintHelper::decideTypeFromReflection($this->reflection->getBackingType());
768: }
769:
770: public function hasEnumCase(string $name): bool
771: {
772: if (!$this->isEnum()) {
773: return false;
774: }
775:
776: return $this->reflection->hasCase($name);
777: }
778:
779: /**
780: * @return array<string, EnumCaseReflection>
781: */
782: public function getEnumCases(): array
783: {
784: if (!$this->isEnum()) {
785: throw new ShouldNotHappenException();
786: }
787:
788: if ($this->enumCases !== null) {
789: return $this->enumCases;
790: }
791:
792: $cases = [];
793: $initializerExprContext = InitializerExprContext::fromClassReflection($this);
794: foreach ($this->reflection->getCases() as $case) {
795: $valueType = null;
796: if ($case instanceof ReflectionEnumBackedCase) {
797: $valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), $initializerExprContext);
798: }
799: $caseName = $case->getName();
800: $attributes = $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()));
801: $cases[$caseName] = new EnumCaseReflection($this, $case, $valueType, $attributes, $this->deprecationProvider);
802: }
803:
804: return $this->enumCases = $cases;
805: }
806:
807: public function getEnumCase(string $name): EnumCaseReflection
808: {
809: if (!$this->hasEnumCase($name)) {
810: throw new ShouldNotHappenException(sprintf('Enum case %s::%s does not exist.', $this->getDisplayName(), $name));
811: }
812:
813: if (!$this->reflection instanceof ReflectionEnum) {
814: throw new ShouldNotHappenException();
815: }
816:
817: if ($this->enumCases !== null && array_key_exists($name, $this->enumCases)) {
818: return $this->enumCases[$name];
819: }
820:
821: $case = $this->reflection->getCase($name);
822: $valueType = null;
823: if ($case instanceof ReflectionEnumBackedCase) {
824: $valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), InitializerExprContext::fromClassReflection($this));
825: }
826:
827: $attributes = $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()));
828:
829: return new EnumCaseReflection($this, $case, $valueType, $attributes, $this->deprecationProvider);
830: }
831:
832: public function isClass(): bool
833: {
834: return !$this->isInterface() && !$this->isTrait() && !$this->isEnum();
835: }
836:
837: public function isAnonymous(): bool
838: {
839: return $this->anonymousFilename !== null;
840: }
841:
842: public function is(string $className): bool
843: {
844: return $this->getName() === $className || $this->isSubclassOf($className);
845: }
846:
847: /**
848: * @deprecated Use isSubclassOfClass instead.
849: */
850: public function isSubclassOf(string $className): bool
851: {
852: if (!$this->reflectionProvider->hasClass($className)) {
853: return false;
854: }
855:
856: return $this->isSubclassOfClass($this->reflectionProvider->getClass($className));
857: }
858:
859: public function isSubclassOfClass(self $class): bool
860: {
861: $cacheKey = $class->getCacheKey();
862: if (isset($this->subclasses[$cacheKey])) {
863: return $this->subclasses[$cacheKey];
864: }
865:
866: if ($class->isFinalByKeyword() || $class->isAnonymous()) {
867: return $this->subclasses[$cacheKey] = false;
868: }
869:
870: try {
871: return $this->subclasses[$cacheKey] = $this->reflection->isSubclassOf($class->getName());
872: } catch (ReflectionException) {
873: return $this->subclasses[$cacheKey] = false;
874: }
875: }
876:
877: public function implementsInterface(string $className): bool
878: {
879: try {
880: return $this->reflection->implementsInterface($className);
881: } catch (ReflectionException) {
882: return false;
883: }
884: }
885:
886: /**
887: * @return list<ClassReflection>
888: */
889: public function getParents(): array
890: {
891: $parents = [];
892: $parent = $this->getParentClass();
893: while ($parent !== null) {
894: $parents[] = $parent;
895: $parent = $parent->getParentClass();
896: }
897:
898: return $parents;
899: }
900:
901: /**
902: * @return array<string, ClassReflection>
903: */
904: public function getInterfaces(): array
905: {
906: if ($this->cachedInterfaces !== null) {
907: return $this->cachedInterfaces;
908: }
909:
910: $interfaces = $this->getImmediateInterfaces();
911: $immediateInterfaces = $interfaces;
912: $parent = $this->getParentClass();
913: while ($parent !== null) {
914: foreach ($parent->getImmediateInterfaces() as $parentInterface) {
915: $interfaces[$parentInterface->getName()] = $parentInterface;
916: foreach ($this->collectInterfaces($parentInterface) as $parentInterfaceInterface) {
917: $interfaces[$parentInterfaceInterface->getName()] = $parentInterfaceInterface;
918: }
919: }
920:
921: $parent = $parent->getParentClass();
922: }
923:
924: foreach ($immediateInterfaces as $immediateInterface) {
925: foreach ($this->collectInterfaces($immediateInterface) as $interfaceInterface) {
926: $interfaces[$interfaceInterface->getName()] = $interfaceInterface;
927: }
928: }
929:
930: $this->cachedInterfaces = $interfaces;
931:
932: return $interfaces;
933: }
934:
935: /**
936: * @return array<string, ClassReflection>
937: */
938: private function collectInterfaces(ClassReflection $interface): array
939: {
940: $interfaces = [];
941: foreach ($interface->getImmediateInterfaces() as $immediateInterface) {
942: $interfaces[$immediateInterface->getName()] = $immediateInterface;
943: foreach ($this->collectInterfaces($immediateInterface) as $immediateInterfaceInterface) {
944: $interfaces[$immediateInterfaceInterface->getName()] = $immediateInterfaceInterface;
945: }
946: }
947:
948: return $interfaces;
949: }
950:
951: /**
952: * @return array<string, ClassReflection>
953: */
954: public function getImmediateInterfaces(): array
955: {
956: $indirectInterfaceNames = [];
957: $parent = $this->getParentClass();
958: while ($parent !== null) {
959: foreach ($parent->getNativeReflection()->getInterfaceNames() as $parentInterfaceName) {
960: $indirectInterfaceNames[] = $parentInterfaceName;
961: }
962:
963: $parent = $parent->getParentClass();
964: }
965:
966: foreach ($this->getNativeReflection()->getInterfaces() as $interfaceInterface) {
967: foreach ($interfaceInterface->getInterfaceNames() as $interfaceInterfaceName) {
968: $indirectInterfaceNames[] = $interfaceInterfaceName;
969: }
970: }
971:
972: if ($this->reflection->isInterface()) {
973: $implementsTags = $this->getExtendsTags();
974: } else {
975: $implementsTags = $this->getImplementsTags();
976: }
977:
978: $immediateInterfaceNames = array_diff($this->getNativeReflection()->getInterfaceNames(), $indirectInterfaceNames);
979: $immediateInterfaces = [];
980: foreach ($immediateInterfaceNames as $immediateInterfaceName) {
981: if (!$this->reflectionProvider->hasClass($immediateInterfaceName)) {
982: continue;
983: }
984:
985: $immediateInterface = $this->reflectionProvider->getClass($immediateInterfaceName);
986: if (array_key_exists($immediateInterface->getName(), $implementsTags)) {
987: $implementsTag = $implementsTags[$immediateInterface->getName()];
988: $implementedType = $implementsTag->getType();
989: if ($this->isGeneric()) {
990: $implementedType = TemplateTypeHelper::resolveTemplateTypes(
991: $implementedType,
992: $this->getPossiblyIncompleteActiveTemplateTypeMap(),
993: $this->getCallSiteVarianceMap(),
994: TemplateTypeVariance::createStatic(),
995: true,
996: );
997: }
998:
999: if (
1000: $implementedType instanceof GenericObjectType
1001: && $implementedType->getClassReflection() !== null
1002: ) {
1003: $immediateInterfaces[$immediateInterface->getName()] = $implementedType->getClassReflection();
1004: continue;
1005: }
1006: }
1007:
1008: if ($immediateInterface->isGeneric()) {
1009: $immediateInterfaces[$immediateInterface->getName()] = $immediateInterface->withTypes(
1010: array_values($immediateInterface->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()),
1011: );
1012: continue;
1013: }
1014:
1015: $immediateInterfaces[$immediateInterface->getName()] = $immediateInterface;
1016: }
1017:
1018: return $immediateInterfaces;
1019: }
1020:
1021: /**
1022: * @return array<string, ClassReflection>
1023: */
1024: public function getTraits(bool $recursive = false): array
1025: {
1026: $traits = [];
1027:
1028: if ($recursive) {
1029: foreach ($this->collectTraits($this->getNativeReflection()) as $trait) {
1030: $traits[$trait->getName()] = $trait;
1031: }
1032: } else {
1033: $traits = $this->getNativeReflection()->getTraits();
1034: }
1035:
1036: $traits = array_map(fn (ReflectionClass $trait): ClassReflection => $this->reflectionProvider->getClass($trait->getName()), $traits);
1037:
1038: if ($recursive) {
1039: $parentClass = $this->getNativeReflection()->getParentClass();
1040:
1041: if ($parentClass !== false) {
1042: return array_merge(
1043: $traits,
1044: $this->reflectionProvider->getClass($parentClass->getName())->getTraits(true),
1045: );
1046: }
1047: }
1048:
1049: return $traits;
1050: }
1051:
1052: /**
1053: * @return list<class-string>
1054: */
1055: public function getParentClassesNames(): array
1056: {
1057: $parentNames = [];
1058: $parentClass = $this->getParentClass();
1059: while ($parentClass !== null) {
1060: $parentNames[] = $parentClass->getName();
1061: $parentClass = $parentClass->getParentClass();
1062: }
1063:
1064: return $parentNames;
1065: }
1066:
1067: public function hasConstant(string $name): bool
1068: {
1069: if (!$this->getNativeReflection()->hasConstant($name)) {
1070: return false;
1071: }
1072:
1073: $reflectionConstant = $this->getNativeReflection()->getReflectionConstant($name);
1074: if ($reflectionConstant === false) {
1075: return false;
1076: }
1077:
1078: return $this->reflectionProvider->hasClass($reflectionConstant->getDeclaringClass()->getName());
1079: }
1080:
1081: public function getConstant(string $name): ClassConstantReflection
1082: {
1083: if (!isset($this->constants[$name])) {
1084: $reflectionConstant = $this->getNativeReflection()->getReflectionConstant($name);
1085: if ($reflectionConstant === false) {
1086: throw new MissingConstantFromReflectionException($this->getName(), $name);
1087: }
1088:
1089: $deprecation = $this->deprecationProvider->getClassConstantDeprecation($reflectionConstant);
1090: $deprecatedDescription = $deprecation === null ? null : $deprecation->getDescription();
1091: $isDeprecated = $deprecation !== null;
1092:
1093: $declaringClass = $this->reflectionProvider->getClass($reflectionConstant->getDeclaringClass()->getName());
1094: $fileName = $declaringClass->getFileName();
1095: $phpDocType = null;
1096: $resolvedPhpDoc = $this->stubPhpDocProvider->findClassConstantPhpDoc(
1097: $declaringClass->getName(),
1098: $name,
1099: );
1100: if ($resolvedPhpDoc === null) {
1101: $docComment = null;
1102: if ($reflectionConstant->getDocComment() !== false) {
1103: $docComment = $reflectionConstant->getDocComment();
1104: }
1105: $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForConstant(
1106: $docComment,
1107: $declaringClass,
1108: $fileName,
1109: $name,
1110: );
1111: }
1112:
1113: if (!$isDeprecated) {
1114: $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null;
1115: $isDeprecated = $resolvedPhpDoc->isDeprecated();
1116: }
1117: $isInternal = $resolvedPhpDoc->isInternal();
1118: $isFinal = $resolvedPhpDoc->isFinal();
1119:
1120: $nativeType = null;
1121: if ($reflectionConstant->getType() !== null) {
1122: $nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), selfClass: $declaringClass);
1123: } elseif ($this->signatureMapProvider->hasClassConstantMetadata($declaringClass->getName(), $name)) {
1124: $nativeType = $this->signatureMapProvider->getClassConstantMetadata($declaringClass->getName(), $name)['nativeType'];
1125: }
1126:
1127: $varTags = $resolvedPhpDoc->getVarTags();
1128: if (isset($varTags[0]) && count($varTags) === 1) {
1129: $varTag = $varTags[0];
1130: if ($varTag->isExplicit() || $nativeType === null || $nativeType->isSuperTypeOf($varTag->getType())->yes()) {
1131: $phpDocType = $varTag->getType();
1132: }
1133: }
1134:
1135: $this->constants[$name] = new RealClassClassConstantReflection(
1136: $this->initializerExprTypeResolver,
1137: $declaringClass,
1138: $reflectionConstant,
1139: $nativeType,
1140: $phpDocType,
1141: $deprecatedDescription,
1142: $isDeprecated,
1143: $isInternal,
1144: $isFinal,
1145: $this->attributeReflectionFactory->fromNativeReflection($reflectionConstant->getAttributes(), InitializerExprContext::fromClass($declaringClass->getName(), $fileName)),
1146: );
1147: }
1148: return $this->constants[$name];
1149: }
1150:
1151: public function hasTraitUse(string $traitName): bool
1152: {
1153: return in_array($traitName, $this->getTraitNames(), true);
1154: }
1155:
1156: /**
1157: * @return list<string>
1158: */
1159: private function getTraitNames(): array
1160: {
1161: $class = $this->reflection;
1162: $traitNames = array_map(static fn (ReflectionClass $class) => $class->getName(), $this->collectTraits($class));
1163: while ($class->getParentClass() !== false) {
1164: $traitNames = array_values(array_unique(array_merge($traitNames, $class->getParentClass()->getTraitNames())));
1165: $class = $class->getParentClass();
1166: }
1167:
1168: return $traitNames;
1169: }
1170:
1171: /**
1172: * @return array<string, TypeAlias>
1173: */
1174: public function getTypeAliases(): array
1175: {
1176: if ($this->typeAliases === null) {
1177: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1178: if ($resolvedPhpDoc === null) {
1179: return $this->typeAliases = [];
1180: }
1181:
1182: $typeAliasImportTags = $resolvedPhpDoc->getTypeAliasImportTags();
1183: $typeAliasTags = $resolvedPhpDoc->getTypeAliasTags();
1184:
1185: // prevent circular imports
1186: if (array_key_exists($this->getName(), self::$resolvingTypeAliasImports)) {
1187: throw new CircularTypeAliasDefinitionException();
1188: }
1189:
1190: self::$resolvingTypeAliasImports[$this->getName()] = true;
1191:
1192: $importedAliases = array_map(function (TypeAliasImportTag $typeAliasImportTag): ?TypeAlias {
1193: $importedAlias = $typeAliasImportTag->getImportedAlias();
1194: $importedFromClassName = $typeAliasImportTag->getImportedFrom();
1195:
1196: if (!$this->reflectionProvider->hasClass($importedFromClassName)) {
1197: return null;
1198: }
1199:
1200: $importedFromReflection = $this->reflectionProvider->getClass($importedFromClassName);
1201:
1202: try {
1203: $typeAliases = $importedFromReflection->getTypeAliases();
1204: } catch (CircularTypeAliasDefinitionException) {
1205: return TypeAlias::invalid();
1206: }
1207:
1208: if (!array_key_exists($importedAlias, $typeAliases)) {
1209: return null;
1210: }
1211:
1212: return $typeAliases[$importedAlias];
1213: }, $typeAliasImportTags);
1214:
1215: unset(self::$resolvingTypeAliasImports[$this->getName()]);
1216:
1217: $localAliases = array_map(static fn (TypeAliasTag $typeAliasTag): TypeAlias => $typeAliasTag->getTypeAlias(), $typeAliasTags);
1218:
1219: $this->typeAliases = array_filter(
1220: array_merge($importedAliases, $localAliases),
1221: static fn (?TypeAlias $typeAlias): bool => $typeAlias !== null,
1222: );
1223: }
1224:
1225: return $this->typeAliases;
1226: }
1227:
1228: public function getDeprecatedDescription(): ?string
1229: {
1230: if ($this->isDeprecated === null) {
1231: $this->resolveDeprecation();
1232: }
1233:
1234: return $this->deprecatedDescription;
1235: }
1236:
1237: public function isDeprecated(): bool
1238: {
1239: if ($this->isDeprecated === null) {
1240: $this->resolveDeprecation();
1241: }
1242:
1243: return $this->isDeprecated;
1244: }
1245:
1246: /**
1247: * @phpstan-assert bool $this->isDeprecated
1248: */
1249: private function resolveDeprecation(): void
1250: {
1251: $deprecation = $this->deprecationProvider->getClassDeprecation($this->reflection);
1252: if ($deprecation !== null) {
1253: $this->isDeprecated = true;
1254: $this->deprecatedDescription = $deprecation->getDescription();
1255: return;
1256: }
1257:
1258: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1259:
1260: if ($resolvedPhpDoc !== null && $resolvedPhpDoc->isDeprecated()) {
1261: $this->isDeprecated = true;
1262: $this->deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null;
1263: return;
1264: }
1265:
1266: $this->isDeprecated = false;
1267: $this->deprecatedDescription = null;
1268: }
1269:
1270: public function isBuiltin(): bool
1271: {
1272: return $this->reflection->isInternal();
1273: }
1274:
1275: public function isInternal(): bool
1276: {
1277: if ($this->isInternal === null) {
1278: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1279: $this->isInternal = $resolvedPhpDoc !== null && $resolvedPhpDoc->isInternal();
1280: }
1281:
1282: return $this->isInternal;
1283: }
1284:
1285: public function isFinal(): bool
1286: {
1287: if ($this->isFinalByKeyword()) {
1288: return true;
1289: }
1290:
1291: if ($this->isFinal === null) {
1292: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1293: $this->isFinal = $resolvedPhpDoc !== null && $resolvedPhpDoc->isFinal();
1294: }
1295:
1296: return $this->isFinal;
1297: }
1298:
1299: public function isImmutable(): bool
1300: {
1301: if ($this->isImmutable === null) {
1302: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1303: $this->isImmutable = $resolvedPhpDoc !== null && ($resolvedPhpDoc->isImmutable() || $resolvedPhpDoc->isReadOnly());
1304:
1305: $parentClass = $this->getParentClass();
1306: if ($parentClass !== null && !$this->isImmutable) {
1307: $this->isImmutable = $parentClass->isImmutable();
1308: }
1309: }
1310:
1311: return $this->isImmutable;
1312: }
1313:
1314: public function hasConsistentConstructor(): bool
1315: {
1316: if ($this->hasConsistentConstructor === null) {
1317: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1318: $this->hasConsistentConstructor = $resolvedPhpDoc !== null && $resolvedPhpDoc->hasConsistentConstructor();
1319: }
1320:
1321: return $this->hasConsistentConstructor;
1322: }
1323:
1324: public function acceptsNamedArguments(): bool
1325: {
1326: if ($this->acceptsNamedArguments === null) {
1327: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1328: $this->acceptsNamedArguments = $resolvedPhpDoc === null || $resolvedPhpDoc->acceptsNamedArguments();
1329: }
1330:
1331: return $this->acceptsNamedArguments;
1332: }
1333:
1334: public function hasFinalByKeywordOverride(): bool
1335: {
1336: return $this->finalByKeywordOverride !== null;
1337: }
1338:
1339: public function isFinalByKeyword(): bool
1340: {
1341: if ($this->isAnonymous()) {
1342: return true;
1343: }
1344:
1345: if ($this->finalByKeywordOverride !== null) {
1346: return $this->finalByKeywordOverride;
1347: }
1348:
1349: return $this->reflection->isFinal();
1350: }
1351:
1352: public function isAttributeClass(): bool
1353: {
1354: return $this->findAttributeFlags() !== null;
1355: }
1356:
1357: private function findAttributeFlags(): ?int
1358: {
1359: if ($this->isInterface() || $this->isTrait() || $this->isEnum()) {
1360: return null;
1361: }
1362:
1363: $nativeAttributes = $this->reflection->getAttributes(Attribute::class);
1364: if (count($nativeAttributes) === 1) {
1365: if (!$this->reflectionProvider->hasClass(Attribute::class)) {
1366: return null;
1367: }
1368:
1369: $attributeClass = $this->reflectionProvider->getClass(Attribute::class);
1370: $arguments = [];
1371: foreach ($nativeAttributes[0]->getArgumentsExpressions() as $i => $expression) {
1372: if ($i === '') {
1373: throw new ShouldNotHappenException();
1374: }
1375: $arguments[] = new Arg($expression, name: is_int($i) ? null : new Identifier($i));
1376: }
1377:
1378: if (!$attributeClass->hasConstructor()) {
1379: return null;
1380: }
1381: $attributeConstructor = $attributeClass->getConstructor();
1382: $attributeConstructorVariant = $attributeConstructor->getOnlyVariant();
1383:
1384: if (count($arguments) === 0) {
1385: $flagType = $attributeConstructorVariant->getParameters()[0]->getDefaultValue();
1386: } else {
1387: $staticCall = ArgumentsNormalizer::reorderStaticCallArguments(
1388: $attributeConstructorVariant,
1389: new StaticCall(new FullyQualified(Attribute::class), $attributeConstructor->getName(), $arguments),
1390: );
1391: if ($staticCall === null) {
1392: return null;
1393: }
1394: $flagExpr = $staticCall->getArgs()[0]->value;
1395: $flagType = $this->initializerExprTypeResolver->getType($flagExpr, InitializerExprContext::fromClassReflection($this));
1396: }
1397:
1398: if (!$flagType instanceof ConstantIntegerType) {
1399: return null;
1400: }
1401:
1402: return $flagType->getValue();
1403: }
1404:
1405: return null;
1406: }
1407:
1408: /**
1409: * @return list<AttributeReflection>
1410: */
1411: public function getAttributes(): array
1412: {
1413: return $this->attributeReflectionFactory->fromNativeReflection($this->reflection->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()));
1414: }
1415:
1416: public function getAttributeClassFlags(): int
1417: {
1418: $flags = $this->findAttributeFlags();
1419: if ($flags === null) {
1420: throw new ShouldNotHappenException();
1421: }
1422:
1423: return $flags;
1424: }
1425:
1426: public function getObjectType(): ObjectType
1427: {
1428: if (!$this->isGeneric()) {
1429: return new ObjectType($this->getName());
1430: }
1431:
1432: return new GenericObjectType(
1433: $this->getName(),
1434: $this->typeMapToList($this->getActiveTemplateTypeMap()),
1435: variances: $this->varianceMapToList($this->getCallSiteVarianceMap()),
1436: );
1437: }
1438:
1439: public function getTemplateTypeMap(): TemplateTypeMap
1440: {
1441: if ($this->templateTypeMap !== null) {
1442: return $this->templateTypeMap;
1443: }
1444:
1445: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1446: if ($resolvedPhpDoc === null) {
1447: $this->templateTypeMap = TemplateTypeMap::createEmpty();
1448: return $this->templateTypeMap;
1449: }
1450:
1451: $templateTypeScope = TemplateTypeScope::createWithClass($this->getName());
1452:
1453: $templateTypeMap = new TemplateTypeMap(array_map(static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag), $this->getTemplateTags()));
1454:
1455: $this->templateTypeMap = $templateTypeMap;
1456:
1457: return $templateTypeMap;
1458: }
1459:
1460: public function getActiveTemplateTypeMap(): TemplateTypeMap
1461: {
1462: if ($this->activeTemplateTypeMap !== null) {
1463: return $this->activeTemplateTypeMap;
1464: }
1465: $resolved = $this->resolvedTemplateTypeMap;
1466: if ($resolved !== null) {
1467: $templateTypeMap = $this->getTemplateTypeMap();
1468: return $this->activeTemplateTypeMap = $resolved->map(static function (string $name, Type $type) use ($templateTypeMap): Type {
1469: if ($type instanceof ErrorType) {
1470: $templateType = $templateTypeMap->getType($name);
1471: if ($templateType !== null) {
1472: return TemplateTypeHelper::resolveToDefaults($templateType);
1473: }
1474: }
1475:
1476: return $type;
1477: });
1478: }
1479:
1480: return $this->activeTemplateTypeMap = $this->getTemplateTypeMap();
1481: }
1482:
1483: public function getPossiblyIncompleteActiveTemplateTypeMap(): TemplateTypeMap
1484: {
1485: return $this->resolvedTemplateTypeMap ?? $this->getTemplateTypeMap();
1486: }
1487:
1488: private function getDefaultCallSiteVarianceMap(): TemplateTypeVarianceMap
1489: {
1490: if ($this->defaultCallSiteVarianceMap !== null) {
1491: return $this->defaultCallSiteVarianceMap;
1492: }
1493:
1494: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1495: if ($resolvedPhpDoc === null) {
1496: $this->defaultCallSiteVarianceMap = TemplateTypeVarianceMap::createEmpty();
1497: return $this->defaultCallSiteVarianceMap;
1498: }
1499:
1500: $map = [];
1501: foreach ($this->getTemplateTags() as $templateTag) {
1502: $map[$templateTag->getName()] = TemplateTypeVariance::createInvariant();
1503: }
1504:
1505: $this->defaultCallSiteVarianceMap = new TemplateTypeVarianceMap($map);
1506: return $this->defaultCallSiteVarianceMap;
1507: }
1508:
1509: public function getCallSiteVarianceMap(): TemplateTypeVarianceMap
1510: {
1511: return $this->callSiteVarianceMap ??= $this->resolvedCallSiteVarianceMap ?? $this->getDefaultCallSiteVarianceMap();
1512: }
1513:
1514: public function isGeneric(): bool
1515: {
1516: if ($this->isGeneric === null) {
1517: if ($this->isEnum()) {
1518: return $this->isGeneric = false;
1519: }
1520:
1521: $this->isGeneric = count($this->getTemplateTags()) > 0;
1522: }
1523:
1524: return $this->isGeneric;
1525: }
1526:
1527: /**
1528: * @param array<int, Type> $types
1529: */
1530: public function typeMapFromList(array $types): TemplateTypeMap
1531: {
1532: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1533: if ($resolvedPhpDoc === null) {
1534: return TemplateTypeMap::createEmpty();
1535: }
1536:
1537: $map = [];
1538: $i = 0;
1539: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1540: $map[$tag->getName()] = $types[$i] ?? $tag->getDefault() ?? $tag->getBound();
1541: $i++;
1542: }
1543:
1544: return new TemplateTypeMap($map);
1545: }
1546:
1547: /**
1548: * @param array<int, TemplateTypeVariance> $variances
1549: */
1550: public function varianceMapFromList(array $variances): TemplateTypeVarianceMap
1551: {
1552: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1553: if ($resolvedPhpDoc === null) {
1554: return new TemplateTypeVarianceMap([]);
1555: }
1556:
1557: $map = [];
1558: $i = 0;
1559: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1560: $map[$tag->getName()] = $variances[$i] ?? TemplateTypeVariance::createInvariant();
1561: $i++;
1562: }
1563:
1564: return new TemplateTypeVarianceMap($map);
1565: }
1566:
1567: /** @return list<Type> */
1568: public function typeMapToList(TemplateTypeMap $typeMap): array
1569: {
1570: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1571: if ($resolvedPhpDoc === null) {
1572: return [];
1573: }
1574:
1575: $list = [];
1576: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1577: $list[] = $typeMap->getType($tag->getName()) ?? $tag->getDefault() ?? $tag->getBound();
1578: }
1579:
1580: return $list;
1581: }
1582:
1583: /** @return list<TemplateTypeVariance> */
1584: public function varianceMapToList(TemplateTypeVarianceMap $varianceMap): array
1585: {
1586: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1587: if ($resolvedPhpDoc === null) {
1588: return [];
1589: }
1590:
1591: $list = [];
1592: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1593: $list[] = $varianceMap->getVariance($tag->getName()) ?? TemplateTypeVariance::createInvariant();
1594: }
1595:
1596: return $list;
1597: }
1598:
1599: /**
1600: * @param array<int, Type> $types
1601: */
1602: public function withTypes(array $types): self
1603: {
1604: return new self(
1605: $this->reflectionProvider,
1606: $this->initializerExprTypeResolver,
1607: $this->fileTypeMapper,
1608: $this->stubPhpDocProvider,
1609: $this->phpDocInheritanceResolver,
1610: $this->phpVersion,
1611: $this->signatureMapProvider,
1612: $this->deprecationProvider,
1613: $this->attributeReflectionFactory,
1614: $this->propertiesClassReflectionExtensions,
1615: $this->methodsClassReflectionExtensions,
1616: $this->allowedSubTypesClassReflectionExtensions,
1617: $this->requireExtendsPropertiesClassReflectionExtension,
1618: $this->requireExtendsMethodsClassReflectionExtension,
1619: $this->displayName,
1620: $this->reflection,
1621: $this->anonymousFilename,
1622: $this->typeMapFromList($types),
1623: $this->stubPhpDocBlock,
1624: $this->universalObjectCratesClasses,
1625: null,
1626: $this->resolvedCallSiteVarianceMap,
1627: $this->finalByKeywordOverride,
1628: );
1629: }
1630:
1631: /**
1632: * @param array<int, TemplateTypeVariance> $variances
1633: */
1634: public function withVariances(array $variances): self
1635: {
1636: return new self(
1637: $this->reflectionProvider,
1638: $this->initializerExprTypeResolver,
1639: $this->fileTypeMapper,
1640: $this->stubPhpDocProvider,
1641: $this->phpDocInheritanceResolver,
1642: $this->phpVersion,
1643: $this->signatureMapProvider,
1644: $this->deprecationProvider,
1645: $this->attributeReflectionFactory,
1646: $this->propertiesClassReflectionExtensions,
1647: $this->methodsClassReflectionExtensions,
1648: $this->allowedSubTypesClassReflectionExtensions,
1649: $this->requireExtendsPropertiesClassReflectionExtension,
1650: $this->requireExtendsMethodsClassReflectionExtension,
1651: $this->displayName,
1652: $this->reflection,
1653: $this->anonymousFilename,
1654: $this->resolvedTemplateTypeMap,
1655: $this->stubPhpDocBlock,
1656: $this->universalObjectCratesClasses,
1657: null,
1658: $this->varianceMapFromList($variances),
1659: $this->finalByKeywordOverride,
1660: );
1661: }
1662:
1663: public function asFinal(): self
1664: {
1665: if ($this->getNativeReflection()->isFinal()) {
1666: return $this;
1667: }
1668: if ($this->finalByKeywordOverride === true) {
1669: return $this;
1670: }
1671: if (!$this->isClass()) {
1672: return $this;
1673: }
1674: if ($this->isAbstract()) {
1675: return $this;
1676: }
1677:
1678: return new self(
1679: $this->reflectionProvider,
1680: $this->initializerExprTypeResolver,
1681: $this->fileTypeMapper,
1682: $this->stubPhpDocProvider,
1683: $this->phpDocInheritanceResolver,
1684: $this->phpVersion,
1685: $this->signatureMapProvider,
1686: $this->deprecationProvider,
1687: $this->attributeReflectionFactory,
1688: $this->propertiesClassReflectionExtensions,
1689: $this->methodsClassReflectionExtensions,
1690: $this->allowedSubTypesClassReflectionExtensions,
1691: $this->requireExtendsPropertiesClassReflectionExtension,
1692: $this->requireExtendsMethodsClassReflectionExtension,
1693: $this->displayName,
1694: $this->reflection,
1695: $this->anonymousFilename,
1696: $this->resolvedTemplateTypeMap,
1697: $this->stubPhpDocBlock,
1698: $this->universalObjectCratesClasses,
1699: null,
1700: $this->resolvedCallSiteVarianceMap,
1701: true,
1702: );
1703: }
1704:
1705: public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock
1706: {
1707: if ($this->stubPhpDocBlock !== null) {
1708: return $this->stubPhpDocBlock;
1709: }
1710:
1711: $fileName = $this->getFileName();
1712: if (is_bool($this->reflectionDocComment)) {
1713: $docComment = $this->reflection->getDocComment();
1714: $this->reflectionDocComment = $docComment !== false ? $docComment : null;
1715: }
1716:
1717: if ($this->reflectionDocComment === null) {
1718: return null;
1719: }
1720:
1721: if ($this->resolvedPhpDocBlock !== false) {
1722: return $this->resolvedPhpDocBlock;
1723: }
1724:
1725: return $this->resolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $this->getName(), null, null, $this->reflectionDocComment);
1726: }
1727:
1728: public function getTraitContextResolvedPhpDoc(self $implementingClass): ?ResolvedPhpDocBlock
1729: {
1730: if (!$this->isTrait()) {
1731: throw new ShouldNotHappenException();
1732: }
1733: if ($implementingClass->isTrait()) {
1734: throw new ShouldNotHappenException();
1735: }
1736: $fileName = $this->getFileName();
1737: if (is_bool($this->reflectionDocComment)) {
1738: $docComment = $this->reflection->getDocComment();
1739: $this->reflectionDocComment = $docComment !== false ? $docComment : null;
1740: }
1741:
1742: if ($this->reflectionDocComment === null) {
1743: return null;
1744: }
1745:
1746: if ($this->traitContextResolvedPhpDocBlock !== false) {
1747: return $this->traitContextResolvedPhpDocBlock;
1748: }
1749:
1750: return $this->traitContextResolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $implementingClass->getName(), $this->getName(), null, $this->reflectionDocComment);
1751: }
1752:
1753: private function getFirstExtendsTag(): ?ExtendsTag
1754: {
1755: foreach ($this->getExtendsTags() as $tag) {
1756: return $tag;
1757: }
1758:
1759: return null;
1760: }
1761:
1762: /** @return array<string, ExtendsTag> */
1763: public function getExtendsTags(): array
1764: {
1765: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1766: if ($resolvedPhpDoc === null) {
1767: return [];
1768: }
1769:
1770: return $resolvedPhpDoc->getExtendsTags();
1771: }
1772:
1773: /** @return array<string, ImplementsTag> */
1774: public function getImplementsTags(): array
1775: {
1776: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1777: if ($resolvedPhpDoc === null) {
1778: return [];
1779: }
1780:
1781: return $resolvedPhpDoc->getImplementsTags();
1782: }
1783:
1784: /** @return array<string,TemplateTag> */
1785: public function getTemplateTags(): array
1786: {
1787: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1788: if ($resolvedPhpDoc === null) {
1789: return [];
1790: }
1791:
1792: return $resolvedPhpDoc->getTemplateTags();
1793: }
1794:
1795: /**
1796: * @return array<string,ClassReflection>
1797: */
1798: public function getAncestors(): array
1799: {
1800: $ancestors = $this->ancestors;
1801:
1802: if ($ancestors === null) {
1803: $ancestors = [
1804: $this->getName() => $this,
1805: ];
1806:
1807: $addToAncestors = static function (string $name, ClassReflection $classReflection) use (&$ancestors): void {
1808: if (array_key_exists($name, $ancestors)) {
1809: return;
1810: }
1811:
1812: $ancestors[$name] = $classReflection;
1813: };
1814:
1815: foreach ($this->getInterfaces() as $interface) {
1816: $addToAncestors($interface->getName(), $interface);
1817: foreach ($interface->getAncestors() as $name => $ancestor) {
1818: $addToAncestors($name, $ancestor);
1819: }
1820: }
1821:
1822: foreach ($this->getTraits() as $trait) {
1823: $addToAncestors($trait->getName(), $trait);
1824: foreach ($trait->getAncestors() as $name => $ancestor) {
1825: $addToAncestors($name, $ancestor);
1826: }
1827: }
1828:
1829: $parent = $this->getParentClass();
1830: if ($parent !== null) {
1831: $addToAncestors($parent->getName(), $parent);
1832: foreach ($parent->getAncestors() as $name => $ancestor) {
1833: $addToAncestors($name, $ancestor);
1834: }
1835: }
1836:
1837: $this->ancestors = $ancestors;
1838: }
1839:
1840: return $ancestors;
1841: }
1842:
1843: public function getAncestorWithClassName(string $className): ?self
1844: {
1845: return $this->getAncestors()[$className] ?? null;
1846: }
1847:
1848: /**
1849: * @param string[] $ancestorClasses
1850: */
1851: private function isValidAncestorType(Type $type, array $ancestorClasses): bool
1852: {
1853: if (!$type instanceof GenericObjectType) {
1854: return false;
1855: }
1856:
1857: $reflection = $type->getClassReflection();
1858: if ($reflection === null) {
1859: return false;
1860: }
1861:
1862: return in_array($reflection->getName(), $ancestorClasses, true);
1863: }
1864:
1865: /**
1866: * @return array<MixinTag>
1867: */
1868: public function getMixinTags(): array
1869: {
1870: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1871: if ($resolvedPhpDoc === null) {
1872: return [];
1873: }
1874:
1875: return $resolvedPhpDoc->getMixinTags();
1876: }
1877:
1878: /**
1879: * @return array<RequireExtendsTag>
1880: */
1881: public function getRequireExtendsTags(): array
1882: {
1883: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1884: if ($resolvedPhpDoc === null) {
1885: return [];
1886: }
1887:
1888: return $resolvedPhpDoc->getRequireExtendsTags();
1889: }
1890:
1891: /**
1892: * @return array<RequireImplementsTag>
1893: */
1894: public function getRequireImplementsTags(): array
1895: {
1896: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1897: if ($resolvedPhpDoc === null) {
1898: return [];
1899: }
1900:
1901: return $resolvedPhpDoc->getRequireImplementsTags();
1902: }
1903:
1904: /**
1905: * @return array<SealedTypeTag>
1906: */
1907: public function getSealedTags(): array
1908: {
1909: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1910: if ($resolvedPhpDoc === null) {
1911: return [];
1912: }
1913:
1914: return $resolvedPhpDoc->getSealedTags();
1915: }
1916:
1917: /**
1918: * @return array<string, PropertyTag>
1919: */
1920: public function getPropertyTags(): array
1921: {
1922: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1923: if ($resolvedPhpDoc === null) {
1924: return [];
1925: }
1926:
1927: return $resolvedPhpDoc->getPropertyTags();
1928: }
1929:
1930: /**
1931: * @return array<string, MethodTag>
1932: */
1933: public function getMethodTags(): array
1934: {
1935: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1936: if ($resolvedPhpDoc === null) {
1937: return [];
1938: }
1939:
1940: return $resolvedPhpDoc->getMethodTags();
1941: }
1942:
1943: /**
1944: * @return list<Type>
1945: */
1946: public function getResolvedMixinTypes(): array
1947: {
1948: $types = [];
1949: foreach ($this->getMixinTags() as $mixinTag) {
1950: if (!$this->isGeneric()) {
1951: $types[] = $mixinTag->getType();
1952: continue;
1953: }
1954:
1955: $types[] = TemplateTypeHelper::resolveTemplateTypes(
1956: $mixinTag->getType(),
1957: $this->getActiveTemplateTypeMap(),
1958: $this->getCallSiteVarianceMap(),
1959: TemplateTypeVariance::createStatic(),
1960: );
1961: }
1962:
1963: return $types;
1964: }
1965:
1966: /**
1967: * @return array<Type>|null
1968: */
1969: public function getAllowedSubTypes(): ?array
1970: {
1971: foreach ($this->allowedSubTypesClassReflectionExtensions as $allowedSubTypesClassReflectionExtension) {
1972: if ($allowedSubTypesClassReflectionExtension->supports($this)) {
1973: return $allowedSubTypesClassReflectionExtension->getAllowedSubTypes($this);
1974: }
1975: }
1976:
1977: return null;
1978: }
1979:
1980: }
1981: