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