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