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