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