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