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