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: return $this->getName() === $className || $this->isSubclassOf($className);
1031: }
1032:
1033: /**
1034: * @deprecated Use isSubclassOfClass instead.
1035: */
1036: public function isSubclassOf(string $className): bool
1037: {
1038: if (!$this->reflectionProvider->hasClass($className)) {
1039: return false;
1040: }
1041:
1042: return $this->isSubclassOfClass($this->reflectionProvider->getClass($className));
1043: }
1044:
1045: public function isSubclassOfClass(self $class): bool
1046: {
1047: $cacheKey = $class->getCacheKey();
1048: if (isset($this->subclasses[$cacheKey])) {
1049: return $this->subclasses[$cacheKey];
1050: }
1051:
1052: if ($class->isFinalByKeyword() || $class->isAnonymous()) {
1053: return $this->subclasses[$cacheKey] = false;
1054: }
1055:
1056: try {
1057: return $this->subclasses[$cacheKey] = $this->reflection->isSubclassOf($class->getName());
1058: } catch (ReflectionException) {
1059: return $this->subclasses[$cacheKey] = false;
1060: }
1061: }
1062:
1063: public function implementsInterface(string $className): bool
1064: {
1065: try {
1066: return $this->reflection->implementsInterface($className);
1067: } catch (ReflectionException) {
1068: return false;
1069: }
1070: }
1071:
1072: /**
1073: * @return list<ClassReflection>
1074: */
1075: public function getParents(): array
1076: {
1077: $parents = [];
1078: $parent = $this->getParentClass();
1079: while ($parent !== null) {
1080: $parents[] = $parent;
1081: $parent = $parent->getParentClass();
1082: }
1083:
1084: return $parents;
1085: }
1086:
1087: /**
1088: * @return array<string, ClassReflection>
1089: */
1090: public function getInterfaces(): array
1091: {
1092: if ($this->cachedInterfaces !== null) {
1093: return $this->cachedInterfaces;
1094: }
1095:
1096: $interfaces = $this->getImmediateInterfaces();
1097: $immediateInterfaces = $interfaces;
1098: $parent = $this->getParentClass();
1099: while ($parent !== null) {
1100: foreach ($parent->getImmediateInterfaces() as $parentInterface) {
1101: $interfaces[$parentInterface->getName()] = $parentInterface;
1102: foreach ($this->collectInterfaces($parentInterface) as $parentInterfaceInterface) {
1103: $interfaces[$parentInterfaceInterface->getName()] = $parentInterfaceInterface;
1104: }
1105: }
1106:
1107: $parent = $parent->getParentClass();
1108: }
1109:
1110: foreach ($immediateInterfaces as $immediateInterface) {
1111: foreach ($this->collectInterfaces($immediateInterface) as $interfaceInterface) {
1112: $interfaces[$interfaceInterface->getName()] = $interfaceInterface;
1113: }
1114: }
1115:
1116: $this->cachedInterfaces = $interfaces;
1117:
1118: return $interfaces;
1119: }
1120:
1121: /**
1122: * @return array<string, ClassReflection>
1123: */
1124: private function collectInterfaces(ClassReflection $interface): array
1125: {
1126: $interfaces = [];
1127: $queue = [$interface];
1128: while ($queue !== []) {
1129: $current = array_pop($queue);
1130: foreach ($current->getImmediateInterfaces() as $immediateInterface) {
1131: $name = $immediateInterface->getName();
1132: if (array_key_exists($name, $interfaces)) {
1133: continue;
1134: }
1135: $interfaces[$name] = $immediateInterface;
1136: $queue[] = $immediateInterface;
1137: }
1138: }
1139:
1140: return $interfaces;
1141: }
1142:
1143: /**
1144: * @return array<string, ClassReflection>
1145: */
1146: public function getImmediateInterfaces(): array
1147: {
1148: $indirectInterfaceNames = [];
1149: $parent = $this->getParentClass();
1150: while ($parent !== null) {
1151: foreach ($parent->getNativeReflection()->getInterfaceNames() as $parentInterfaceName) {
1152: $indirectInterfaceNames[] = $parentInterfaceName;
1153: }
1154:
1155: $parent = $parent->getParentClass();
1156: }
1157:
1158: foreach ($this->getNativeReflection()->getInterfaces() as $interfaceInterface) {
1159: foreach ($interfaceInterface->getInterfaceNames() as $interfaceInterfaceName) {
1160: $indirectInterfaceNames[] = $interfaceInterfaceName;
1161: }
1162: }
1163:
1164: if ($this->reflection->isInterface()) {
1165: $implementsTags = $this->getExtendsTags();
1166: } else {
1167: $implementsTags = $this->getImplementsTags();
1168: }
1169:
1170: $immediateInterfaceNames = array_diff($this->getNativeReflection()->getInterfaceNames(), $indirectInterfaceNames);
1171: $immediateInterfaces = [];
1172: foreach ($immediateInterfaceNames as $immediateInterfaceName) {
1173: if (!$this->reflectionProvider->hasClass($immediateInterfaceName)) {
1174: continue;
1175: }
1176:
1177: $immediateInterface = $this->reflectionProvider->getClass($immediateInterfaceName);
1178: if (array_key_exists($immediateInterface->getName(), $implementsTags)) {
1179: $implementsTag = $implementsTags[$immediateInterface->getName()];
1180: $implementedType = $implementsTag->getType();
1181: if ($this->isGeneric()) {
1182: $implementedType = TemplateTypeHelper::resolveTemplateTypes(
1183: $implementedType,
1184: $this->getActiveTemplateTypeMapForAncestorResolution(),
1185: $this->getCallSiteVarianceMap(),
1186: TemplateTypeVariance::createStatic(),
1187: true,
1188: );
1189: }
1190:
1191: if (
1192: $implementedType instanceof GenericObjectType
1193: && $implementedType->getClassReflection() !== null
1194: ) {
1195: $immediateInterfaces[$immediateInterface->getName()] = $implementedType->getClassReflection();
1196: continue;
1197: }
1198: }
1199:
1200: if ($immediateInterface->isGeneric()) {
1201: $immediateInterfaces[$immediateInterface->getName()] = $immediateInterface->withTypes(
1202: array_values($immediateInterface->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()),
1203: );
1204: continue;
1205: }
1206:
1207: $immediateInterfaces[$immediateInterface->getName()] = $immediateInterface;
1208: }
1209:
1210: return $immediateInterfaces;
1211: }
1212:
1213: /**
1214: * @return array<string, ClassReflection>
1215: */
1216: public function getTraits(bool $recursive = false): array
1217: {
1218: $traits = [];
1219:
1220: if ($recursive) {
1221: foreach ($this->collectTraits($this->getNativeReflection()) as $trait) {
1222: $traits[$trait->getName()] = $trait;
1223: }
1224: } else {
1225: $traits = $this->getNativeReflection()->getTraits();
1226: }
1227:
1228: $traits = array_map(fn (ReflectionClass $trait): ClassReflection => $this->reflectionProvider->getClass($trait->getName()), $traits);
1229:
1230: if ($recursive) {
1231: $parentClass = $this->getNativeReflection()->getParentClass();
1232:
1233: if ($parentClass !== false) {
1234: return array_merge(
1235: $traits,
1236: $this->reflectionProvider->getClass($parentClass->getName())->getTraits(true),
1237: );
1238: }
1239: }
1240:
1241: return $traits;
1242: }
1243:
1244: /**
1245: * @return list<class-string>
1246: */
1247: public function getParentClassesNames(): array
1248: {
1249: $parentNames = [];
1250: $parentClass = $this->getParentClass();
1251: while ($parentClass !== null) {
1252: $parentNames[] = $parentClass->getName();
1253: $parentClass = $parentClass->getParentClass();
1254: }
1255:
1256: return $parentNames;
1257: }
1258:
1259: public function hasConstant(string $name): bool
1260: {
1261: if (!$this->getNativeReflection()->hasConstant($name)) {
1262: return false;
1263: }
1264:
1265: $reflectionConstant = $this->getNativeReflection()->getReflectionConstant($name);
1266: if ($reflectionConstant === false) {
1267: return false;
1268: }
1269:
1270: return $this->reflectionProvider->hasClass($reflectionConstant->getDeclaringClass()->getName());
1271: }
1272:
1273: public function getConstant(string $name): ClassConstantReflection
1274: {
1275: if (!isset($this->constants[$name])) {
1276: $reflectionConstant = $this->getNativeReflection()->getReflectionConstant($name);
1277: if ($reflectionConstant === false) {
1278: throw new MissingConstantFromReflectionException($this->getName(), $name);
1279: }
1280:
1281: $deprecation = $this->deprecationProvider->getClassConstantDeprecation($reflectionConstant);
1282: $deprecatedDescription = $deprecation === null ? null : $deprecation->getDescription();
1283: $isDeprecated = $deprecation !== null;
1284:
1285: $declaringClass = $this->getAncestorWithClassName($reflectionConstant->getDeclaringClass()->getName());
1286: if ($declaringClass === null) {
1287: throw new ShouldNotHappenException();
1288: }
1289: $fileName = $declaringClass->getFileName();
1290: $phpDocType = null;
1291: $currentResolvedPhpDoc = $this->findConstantResolvedPhpDoc($reflectionConstant);
1292:
1293: $nativeType = null;
1294: if ($reflectionConstant->getType() !== null) {
1295: $nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), selfClass: $declaringClass);
1296: } elseif ($this->signatureMapProvider->hasClassConstantMetadata($declaringClass->getName(), $name)) {
1297: $nativeType = $this->signatureMapProvider->getClassConstantMetadata($declaringClass->getName(), $name)['nativeType'];
1298: }
1299:
1300: $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForConstant(
1301: $declaringClass,
1302: $name,
1303: $currentResolvedPhpDoc,
1304: );
1305:
1306: $isInternal = false;
1307: $isFinal = false;
1308: if ($resolvedPhpDoc !== null) {
1309: if (!$isDeprecated) {
1310: $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null;
1311: $isDeprecated = $resolvedPhpDoc->isDeprecated();
1312: }
1313: $isInternal = $resolvedPhpDoc->isInternal();
1314: $isFinal = $resolvedPhpDoc->isFinal();
1315: $phpDocType = self::resolveConstantVarPhpDocType($resolvedPhpDoc, $nativeType, $declaringClass);
1316: }
1317:
1318: $this->constants[$name] = new RealClassClassConstantReflection(
1319: $this->initializerExprTypeResolver,
1320: $declaringClass,
1321: $reflectionConstant,
1322: $nativeType,
1323: $phpDocType,
1324: $resolvedPhpDoc,
1325: $deprecatedDescription,
1326: $isDeprecated,
1327: $isInternal,
1328: $isFinal,
1329: $this->attributeReflectionFactory->fromNativeReflection($reflectionConstant->getAttributes(), InitializerExprContext::fromClass($declaringClass->getName(), $fileName)),
1330: );
1331: }
1332: return $this->constants[$name];
1333: }
1334:
1335: /**
1336: * Returns the @var PHPDoc type of a class constant, if any.
1337: * Does not walk ancestors, so it's safe to call from class-constant
1338: * type resolution (which re-enters via @extends<value-of<...>>).
1339: *
1340: * @internal
1341: */
1342: public function getConstantPhpDocType(string $name): ?Type
1343: {
1344: $reflectionConstant = $this->getNativeReflection()->getReflectionConstant($name);
1345: if ($reflectionConstant === false) {
1346: return null;
1347: }
1348:
1349: $resolvedPhpDoc = $this->findConstantResolvedPhpDoc($reflectionConstant);
1350: if ($resolvedPhpDoc === null) {
1351: return null;
1352: }
1353:
1354: $nativeType = null;
1355: if ($reflectionConstant->getType() !== null) {
1356: $nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType());
1357: }
1358:
1359: $declaringClassName = $reflectionConstant->getDeclaringClass()->getName();
1360: if ($declaringClassName === $this->getName()) {
1361: $declaringClass = $this;
1362: } else {
1363: $declaringClass = $this->getAncestorWithClassName($declaringClassName);
1364: if ($declaringClass === null) {
1365: return null;
1366: }
1367: }
1368:
1369: return self::resolveConstantVarPhpDocType($resolvedPhpDoc, $nativeType, $declaringClass);
1370: }
1371:
1372: private function findConstantResolvedPhpDoc(ReflectionClassConstant $reflectionConstant): ?ResolvedPhpDocBlock
1373: {
1374: $declaringClassName = $reflectionConstant->getDeclaringClass()->getName();
1375:
1376: $resolvedPhpDoc = $this->stubPhpDocProvider->findClassConstantPhpDoc(
1377: $declaringClassName,
1378: $reflectionConstant->getName(),
1379: );
1380: if ($resolvedPhpDoc !== null) {
1381: return $resolvedPhpDoc;
1382: }
1383:
1384: $docComment = $reflectionConstant->getDocComment();
1385: if ($docComment === false) {
1386: return null;
1387: }
1388:
1389: return $this->fileTypeMapper->getResolvedPhpDoc(
1390: $reflectionConstant->getDeclaringClass()->getFileName() ?: null,
1391: $declaringClassName,
1392: null,
1393: null,
1394: $docComment,
1395: );
1396: }
1397:
1398: private static function resolveConstantVarPhpDocType(
1399: ResolvedPhpDocBlock $resolvedPhpDoc,
1400: ?Type $nativeType,
1401: self $declaringClass,
1402: ): ?Type
1403: {
1404: $varTags = $resolvedPhpDoc->getVarTags();
1405: if (!isset($varTags[0]) || count($varTags) !== 1) {
1406: return null;
1407: }
1408:
1409: $varTag = $varTags[0];
1410: if (!$varTag->isExplicit() && $nativeType !== null && !$nativeType->isSuperTypeOf($varTag->getType())->yes()) {
1411: return null;
1412: }
1413:
1414: return TemplateTypeHelper::resolveTemplateTypes(
1415: $varTag->getType(),
1416: $declaringClass->getActiveTemplateTypeMap(),
1417: $declaringClass->getCallSiteVarianceMap(),
1418: TemplateTypeVariance::createInvariant(),
1419: );
1420: }
1421:
1422: public function hasTraitUse(string $traitName): bool
1423: {
1424: return in_array($traitName, $this->getTraitNames(), true);
1425: }
1426:
1427: /**
1428: * @return list<string>
1429: */
1430: private function getTraitNames(): array
1431: {
1432: $class = $this->reflection;
1433: $traitNames = array_map(static fn (ReflectionClass $class) => $class->getName(), $this->collectTraits($class));
1434: while ($class->getParentClass() !== false) {
1435: $traitNames = array_values(array_unique(array_merge($traitNames, $class->getParentClass()->getTraitNames())));
1436: $class = $class->getParentClass();
1437: }
1438:
1439: return $traitNames;
1440: }
1441:
1442: /**
1443: * @return array<string, TypeAlias>
1444: */
1445: public function getTypeAliases(): array
1446: {
1447: if ($this->typeAliases === null) {
1448: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1449: if ($resolvedPhpDoc === null) {
1450: return $this->typeAliases = [];
1451: }
1452:
1453: $typeAliasImportTags = $resolvedPhpDoc->getTypeAliasImportTags();
1454: $typeAliasTags = $resolvedPhpDoc->getTypeAliasTags();
1455: $localAliases = array_map(static fn (TypeAliasTag $typeAliasTag): TypeAlias => $typeAliasTag->getTypeAlias(), $typeAliasTags);
1456:
1457: // prevent circular imports
1458: if (array_key_exists($this->getName(), self::$resolvingTypeAliasImports)) {
1459: if ($localAliases !== []) {
1460: return $this->typeAliases = $localAliases;
1461: }
1462: throw new CircularTypeAliasDefinitionException();
1463: }
1464:
1465: self::$resolvingTypeAliasImports[$this->getName()] = true;
1466:
1467: $importedAliases = array_map(function (TypeAliasImportTag $typeAliasImportTag): ?TypeAlias {
1468: $importedAlias = $typeAliasImportTag->getImportedAlias();
1469: $importedFromClassName = $typeAliasImportTag->getImportedFrom();
1470:
1471: if (!$this->reflectionProvider->hasClass($importedFromClassName)) {
1472: return null;
1473: }
1474:
1475: $importedFromReflection = $this->reflectionProvider->getClass($importedFromClassName);
1476:
1477: try {
1478: $typeAliases = $importedFromReflection->getTypeAliases();
1479: } catch (CircularTypeAliasDefinitionException) {
1480: return TypeAlias::invalid();
1481: }
1482:
1483: if (!array_key_exists($importedAlias, $typeAliases)) {
1484: return null;
1485: }
1486:
1487: return $typeAliases[$importedAlias];
1488: }, $typeAliasImportTags);
1489:
1490: unset(self::$resolvingTypeAliasImports[$this->getName()]);
1491:
1492: $this->typeAliases = array_filter(
1493: array_merge($importedAliases, $localAliases),
1494: static fn (?TypeAlias $typeAlias): bool => $typeAlias !== null,
1495: );
1496: }
1497:
1498: return $this->typeAliases;
1499: }
1500:
1501: public function getDeprecatedDescription(): ?string
1502: {
1503: if ($this->isDeprecated === null) {
1504: $this->resolveDeprecation();
1505: }
1506:
1507: return $this->deprecatedDescription;
1508: }
1509:
1510: public function isDeprecated(): bool
1511: {
1512: if ($this->isDeprecated === null) {
1513: $this->resolveDeprecation();
1514: }
1515:
1516: return $this->isDeprecated;
1517: }
1518:
1519: /**
1520: * @phpstan-assert bool $this->isDeprecated
1521: */
1522: private function resolveDeprecation(): void
1523: {
1524: $deprecation = $this->deprecationProvider->getClassDeprecation($this->reflection);
1525: if ($deprecation !== null) {
1526: $this->isDeprecated = true;
1527: $this->deprecatedDescription = $deprecation->getDescription();
1528: return;
1529: }
1530:
1531: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1532:
1533: if ($resolvedPhpDoc !== null && $resolvedPhpDoc->isDeprecated()) {
1534: $this->isDeprecated = true;
1535: $this->deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null;
1536: return;
1537: }
1538:
1539: if ($this->isTrait() && count($this->getNativeReflection()->getAttributes('Deprecated')) > 0) {
1540: $this->isDeprecated = true;
1541: $this->deprecatedDescription = null;
1542: return;
1543: }
1544:
1545: $this->isDeprecated = false;
1546: $this->deprecatedDescription = null;
1547: }
1548:
1549: public function isBuiltin(): bool
1550: {
1551: return $this->reflection->isInternal();
1552: }
1553:
1554: public function isInternal(): bool
1555: {
1556: if ($this->isInternal === null) {
1557: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1558: $this->isInternal = $resolvedPhpDoc !== null && $resolvedPhpDoc->isInternal();
1559: }
1560:
1561: return $this->isInternal;
1562: }
1563:
1564: public function isFinal(): bool
1565: {
1566: if ($this->isFinalByKeyword()) {
1567: return true;
1568: }
1569:
1570: if ($this->isFinal === null) {
1571: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1572: $this->isFinal = $resolvedPhpDoc !== null && $resolvedPhpDoc->isFinal();
1573: }
1574:
1575: return $this->isFinal;
1576: }
1577:
1578: public function isImmutable(): bool
1579: {
1580: if ($this->isImmutable === null) {
1581: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1582: $this->isImmutable = $resolvedPhpDoc !== null && ($resolvedPhpDoc->isImmutable() || $resolvedPhpDoc->isReadOnly());
1583:
1584: $parentClass = $this->getParentClass();
1585: if ($parentClass !== null && !$this->isImmutable) {
1586: $this->isImmutable = $parentClass->isImmutable();
1587: }
1588: }
1589:
1590: return $this->isImmutable;
1591: }
1592:
1593: public function hasConsistentConstructor(): bool
1594: {
1595: if ($this->hasConsistentConstructor === null) {
1596: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1597: $this->hasConsistentConstructor = $resolvedPhpDoc !== null && $resolvedPhpDoc->hasConsistentConstructor();
1598: }
1599:
1600: return $this->hasConsistentConstructor;
1601: }
1602:
1603: public function acceptsNamedArguments(): bool
1604: {
1605: if ($this->acceptsNamedArguments === null) {
1606: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1607: $this->acceptsNamedArguments = $resolvedPhpDoc === null || $resolvedPhpDoc->acceptsNamedArguments();
1608: }
1609:
1610: return $this->acceptsNamedArguments;
1611: }
1612:
1613: public function hasFinalByKeywordOverride(): bool
1614: {
1615: return $this->finalByKeywordOverride !== null;
1616: }
1617:
1618: public function isFinalByKeyword(): bool
1619: {
1620: if ($this->isAnonymous()) {
1621: return true;
1622: }
1623:
1624: if ($this->finalByKeywordOverride !== null) {
1625: return $this->finalByKeywordOverride;
1626: }
1627:
1628: return $this->reflection->isFinal();
1629: }
1630:
1631: public function isAttributeClass(): bool
1632: {
1633: return $this->findAttributeFlags() !== null;
1634: }
1635:
1636: private function findAttributeFlags(): ?int
1637: {
1638: if ($this->isInterface() || $this->isTrait() || $this->isEnum()) {
1639: return null;
1640: }
1641:
1642: $nativeAttributes = $this->reflection->getAttributes(Attribute::class);
1643: if (count($nativeAttributes) === 1) {
1644: if (!$this->reflectionProvider->hasClass(Attribute::class)) {
1645: return null;
1646: }
1647:
1648: $attributeClass = $this->reflectionProvider->getClass(Attribute::class);
1649: $arguments = [];
1650: foreach ($nativeAttributes[0]->getArgumentsExpressions() as $i => $expression) {
1651: if ($i === '') {
1652: throw new ShouldNotHappenException();
1653: }
1654: $arguments[] = new Arg($expression, name: is_int($i) ? null : new Identifier($i));
1655: }
1656:
1657: if (!$attributeClass->hasConstructor()) {
1658: return null;
1659: }
1660: $attributeConstructor = $attributeClass->getConstructor();
1661: $attributeConstructorVariant = $attributeConstructor->getOnlyVariant();
1662:
1663: if (count($arguments) === 0) {
1664: $flagType = $attributeConstructorVariant->getParameters()[0]->getDefaultValue();
1665: } else {
1666: $staticCall = ArgumentsNormalizer::reorderStaticCallArguments(
1667: $attributeConstructorVariant,
1668: new StaticCall(new FullyQualified(Attribute::class), $attributeConstructor->getName(), $arguments),
1669: );
1670: if ($staticCall === null) {
1671: return null;
1672: }
1673: $flagExpr = $staticCall->getArgs()[0]->value;
1674: $flagType = $this->initializerExprTypeResolver->getType($flagExpr, InitializerExprContext::fromClassReflection($this));
1675: }
1676:
1677: if (!$flagType instanceof ConstantIntegerType) {
1678: return null;
1679: }
1680:
1681: return $flagType->getValue();
1682: }
1683:
1684: return null;
1685: }
1686:
1687: /**
1688: * @return list<AttributeReflection>
1689: */
1690: public function getAttributes(): array
1691: {
1692: return $this->attributeReflectionFactory->fromNativeReflection($this->reflection->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()));
1693: }
1694:
1695: public function getAttributeClassFlags(): int
1696: {
1697: $flags = $this->findAttributeFlags();
1698: if ($flags === null) {
1699: throw new ShouldNotHappenException();
1700: }
1701:
1702: return $flags;
1703: }
1704:
1705: public function getObjectType(): ObjectType
1706: {
1707: if (!$this->isGeneric()) {
1708: return new ObjectType($this->getName());
1709: }
1710:
1711: return new GenericObjectType(
1712: $this->getName(),
1713: $this->typeMapToList($this->getActiveTemplateTypeMap()),
1714: variances: $this->varianceMapToList($this->getCallSiteVarianceMap()),
1715: );
1716: }
1717:
1718: public function getTemplateTypeMap(): TemplateTypeMap
1719: {
1720: if ($this->templateTypeMap !== null) {
1721: return $this->templateTypeMap;
1722: }
1723:
1724: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1725: if ($resolvedPhpDoc === null) {
1726: $this->templateTypeMap = TemplateTypeMap::createEmpty();
1727: return $this->templateTypeMap;
1728: }
1729:
1730: $templateTypeScope = TemplateTypeScope::createWithClass($this->getName());
1731:
1732: $templateTypeMap = new TemplateTypeMap(array_map(static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag), $this->getTemplateTags()));
1733:
1734: $this->templateTypeMap = $templateTypeMap;
1735:
1736: return $templateTypeMap;
1737: }
1738:
1739: public function getActiveTemplateTypeMap(): TemplateTypeMap
1740: {
1741: if ($this->activeTemplateTypeMap !== null) {
1742: return $this->activeTemplateTypeMap;
1743: }
1744: $resolved = $this->resolvedTemplateTypeMap;
1745: if ($resolved !== null) {
1746: $templateTypeMap = $this->getTemplateTypeMap();
1747: return $this->activeTemplateTypeMap = $resolved->map(static function (string $name, Type $type) use ($templateTypeMap): Type {
1748: if ($type instanceof ErrorType) {
1749: $templateType = $templateTypeMap->getType($name);
1750: if ($templateType !== null) {
1751: return TemplateTypeHelper::resolveToDefaults($templateType);
1752: }
1753: }
1754:
1755: return $type;
1756: });
1757: }
1758:
1759: return $this->activeTemplateTypeMap = $this->getTemplateTypeMap();
1760: }
1761:
1762: public function getPossiblyIncompleteActiveTemplateTypeMap(): TemplateTypeMap
1763: {
1764: return $this->resolvedTemplateTypeMap ?? $this->getTemplateTypeMap();
1765: }
1766:
1767: /**
1768: * Returns a template type map for resolving ancestor type declarations (@extends, @implements).
1769: * Like getPossiblyIncompleteActiveTemplateTypeMap(), but resolves ErrorType entries
1770: * to their template bounds when the bound is not mixed. This ensures that when a child
1771: * class narrows a template bound (e.g. `@template T of SpecificType`), the narrowed bound
1772: * is propagated to ancestor declarations instead of being lost as ErrorType.
1773: */
1774: private function getActiveTemplateTypeMapForAncestorResolution(): TemplateTypeMap
1775: {
1776: $map = $this->getPossiblyIncompleteActiveTemplateTypeMap();
1777: $templateTypeMap = $this->getTemplateTypeMap();
1778:
1779: return $map->map(static function (string $name, Type $type) use ($templateTypeMap): Type {
1780: if (!$type instanceof ErrorType) {
1781: return $type;
1782: }
1783:
1784: $templateType = $templateTypeMap->getType($name);
1785: if (!$templateType instanceof TemplateType) {
1786: return $type;
1787: }
1788:
1789: $bound = $templateType->getBound();
1790: if ($bound instanceof MixedType) {
1791: return $type;
1792: }
1793:
1794: return TemplateTypeHelper::resolveToDefaults($templateType);
1795: });
1796: }
1797:
1798: private function getDefaultCallSiteVarianceMap(): TemplateTypeVarianceMap
1799: {
1800: if ($this->defaultCallSiteVarianceMap !== null) {
1801: return $this->defaultCallSiteVarianceMap;
1802: }
1803:
1804: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1805: if ($resolvedPhpDoc === null) {
1806: $this->defaultCallSiteVarianceMap = TemplateTypeVarianceMap::createEmpty();
1807: return $this->defaultCallSiteVarianceMap;
1808: }
1809:
1810: $map = [];
1811: foreach ($this->getTemplateTags() as $templateTag) {
1812: $map[$templateTag->getName()] = TemplateTypeVariance::createInvariant();
1813: }
1814:
1815: $this->defaultCallSiteVarianceMap = new TemplateTypeVarianceMap($map);
1816: return $this->defaultCallSiteVarianceMap;
1817: }
1818:
1819: public function getCallSiteVarianceMap(): TemplateTypeVarianceMap
1820: {
1821: return $this->callSiteVarianceMap ??= $this->resolvedCallSiteVarianceMap ?? $this->getDefaultCallSiteVarianceMap();
1822: }
1823:
1824: public function isGeneric(): bool
1825: {
1826: if ($this->isGeneric === null) {
1827: if ($this->isEnum()) {
1828: return $this->isGeneric = false;
1829: }
1830:
1831: $this->isGeneric = count($this->getTemplateTags()) > 0;
1832: }
1833:
1834: return $this->isGeneric;
1835: }
1836:
1837: /**
1838: * @param array<int, Type> $types
1839: */
1840: public function typeMapFromList(array $types): TemplateTypeMap
1841: {
1842: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1843: if ($resolvedPhpDoc === null) {
1844: return TemplateTypeMap::createEmpty();
1845: }
1846:
1847: $map = [];
1848: $i = 0;
1849: $className = $this->getName();
1850: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1851: $type = $types[$i] ?? $tag->getDefault() ?? $tag->getBound();
1852: if ($type instanceof TemplateType && $type->getScope()->getClassName() === $className) {
1853: $resolved = $map[$type->getName()] ?? null;
1854: if ($resolved !== null && !$resolved instanceof TemplateType) {
1855: $type = $resolved;
1856: }
1857: }
1858: $map[$tag->getName()] = $type;
1859: $i++;
1860: }
1861:
1862: return new TemplateTypeMap($map);
1863: }
1864:
1865: /**
1866: * @param array<int, TemplateTypeVariance> $variances
1867: */
1868: public function varianceMapFromList(array $variances): TemplateTypeVarianceMap
1869: {
1870: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1871: if ($resolvedPhpDoc === null) {
1872: return new TemplateTypeVarianceMap([]);
1873: }
1874:
1875: $map = [];
1876: $i = 0;
1877: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1878: $map[$tag->getName()] = $variances[$i] ?? TemplateTypeVariance::createInvariant();
1879: $i++;
1880: }
1881:
1882: return new TemplateTypeVarianceMap($map);
1883: }
1884:
1885: /** @return list<Type> */
1886: public function typeMapToList(TemplateTypeMap $typeMap): array
1887: {
1888: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1889: if ($resolvedPhpDoc === null) {
1890: return [];
1891: }
1892:
1893: $list = [];
1894: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1895: $list[] = $typeMap->getType($tag->getName()) ?? $tag->getDefault() ?? $tag->getBound();
1896: }
1897:
1898: return $list;
1899: }
1900:
1901: /** @return list<TemplateTypeVariance> */
1902: public function varianceMapToList(TemplateTypeVarianceMap $varianceMap): array
1903: {
1904: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1905: if ($resolvedPhpDoc === null) {
1906: return [];
1907: }
1908:
1909: $list = [];
1910: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1911: $list[] = $varianceMap->getVariance($tag->getName()) ?? TemplateTypeVariance::createInvariant();
1912: }
1913:
1914: return $list;
1915: }
1916:
1917: /**
1918: * @param array<int, Type> $types
1919: */
1920: public function withTypes(array $types): self
1921: {
1922: return $this->classReflectionFactory->create(
1923: $this->displayName,
1924: $this->reflection,
1925: $this->anonymousFilename,
1926: $this->typeMapFromList($types),
1927: $this->stubPhpDocBlock,
1928: null,
1929: $this->resolvedCallSiteVarianceMap,
1930: $this->finalByKeywordOverride,
1931: );
1932: }
1933:
1934: /**
1935: * @param array<int, TemplateTypeVariance> $variances
1936: */
1937: public function withVariances(array $variances): self
1938: {
1939: return $this->classReflectionFactory->create(
1940: $this->displayName,
1941: $this->reflection,
1942: $this->anonymousFilename,
1943: $this->resolvedTemplateTypeMap,
1944: $this->stubPhpDocBlock,
1945: null,
1946: $this->varianceMapFromList($variances),
1947: $this->finalByKeywordOverride,
1948: );
1949: }
1950:
1951: public function asFinal(): self
1952: {
1953: if ($this->getNativeReflection()->isFinal()) {
1954: return $this;
1955: }
1956: if ($this->finalByKeywordOverride === true) {
1957: return $this;
1958: }
1959: if (!$this->isClass()) {
1960: return $this;
1961: }
1962: if ($this->isAbstract()) {
1963: return $this;
1964: }
1965:
1966: return $this->classReflectionFactory->create(
1967: $this->displayName,
1968: $this->reflection,
1969: $this->anonymousFilename,
1970: $this->resolvedTemplateTypeMap,
1971: $this->stubPhpDocBlock,
1972: null,
1973: $this->resolvedCallSiteVarianceMap,
1974: true,
1975: );
1976: }
1977:
1978: public function removeFinalKeywordOverride(): self
1979: {
1980: if ($this->getNativeReflection()->isFinal()) {
1981: return $this;
1982: }
1983: if ($this->finalByKeywordOverride === false) {
1984: return $this;
1985: }
1986: if (!$this->isClass()) {
1987: return $this;
1988: }
1989: if ($this->isAbstract()) {
1990: return $this;
1991: }
1992:
1993: return $this->classReflectionFactory->create(
1994: $this->displayName,
1995: $this->reflection,
1996: $this->anonymousFilename,
1997: $this->resolvedTemplateTypeMap,
1998: $this->stubPhpDocBlock,
1999: null,
2000: $this->resolvedCallSiteVarianceMap,
2001: false,
2002: );
2003: }
2004:
2005: public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock
2006: {
2007: if ($this->stubPhpDocBlock !== null) {
2008: return $this->stubPhpDocBlock;
2009: }
2010:
2011: $fileName = $this->getFileName();
2012: if (is_bool($this->reflectionDocComment)) {
2013: $docComment = $this->reflection->getDocComment();
2014: $this->reflectionDocComment = $docComment !== false ? $docComment : null;
2015: }
2016:
2017: if ($this->reflectionDocComment === null) {
2018: return null;
2019: }
2020:
2021: if ($this->resolvedPhpDocBlock !== false) {
2022: return $this->resolvedPhpDocBlock;
2023: }
2024:
2025: return $this->resolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $this->getName(), null, null, $this->reflectionDocComment);
2026: }
2027:
2028: public function getTraitContextResolvedPhpDoc(self $implementingClass): ?ResolvedPhpDocBlock
2029: {
2030: if (!$this->isTrait()) {
2031: throw new ShouldNotHappenException();
2032: }
2033: if ($implementingClass->isTrait()) {
2034: throw new ShouldNotHappenException();
2035: }
2036: $fileName = $this->getFileName();
2037: if (is_bool($this->reflectionDocComment)) {
2038: $docComment = $this->reflection->getDocComment();
2039: $this->reflectionDocComment = $docComment !== false ? $docComment : null;
2040: }
2041:
2042: if ($this->reflectionDocComment === null) {
2043: return null;
2044: }
2045:
2046: if ($this->traitContextResolvedPhpDocBlock !== false) {
2047: return $this->traitContextResolvedPhpDocBlock;
2048: }
2049:
2050: return $this->traitContextResolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $implementingClass->getName(), $this->getName(), null, $this->reflectionDocComment);
2051: }
2052:
2053: private function getFirstExtendsTag(): ?ExtendsTag
2054: {
2055: foreach ($this->getExtendsTags() as $tag) {
2056: return $tag;
2057: }
2058:
2059: return null;
2060: }
2061:
2062: /** @return array<string, ExtendsTag> */
2063: public function getExtendsTags(): array
2064: {
2065: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2066: if ($resolvedPhpDoc === null) {
2067: return [];
2068: }
2069:
2070: return $resolvedPhpDoc->getExtendsTags();
2071: }
2072:
2073: /** @return array<string, ImplementsTag> */
2074: public function getImplementsTags(): array
2075: {
2076: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2077: if ($resolvedPhpDoc === null) {
2078: return [];
2079: }
2080:
2081: return $resolvedPhpDoc->getImplementsTags();
2082: }
2083:
2084: /** @return array<string,TemplateTag> */
2085: public function getTemplateTags(): array
2086: {
2087: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2088: if ($resolvedPhpDoc === null) {
2089: return [];
2090: }
2091:
2092: return $resolvedPhpDoc->getTemplateTags();
2093: }
2094:
2095: /**
2096: * @return array<string,ClassReflection>
2097: */
2098: public function getAncestors(): array
2099: {
2100: $ancestors = $this->ancestors;
2101:
2102: if ($ancestors === null) {
2103: $ancestors = [
2104: $this->getName() => $this,
2105: ];
2106:
2107: $addToAncestors = static function (string $name, ClassReflection $classReflection) use (&$ancestors): void {
2108: if (array_key_exists($name, $ancestors)) {
2109: return;
2110: }
2111:
2112: $ancestors[$name] = $classReflection;
2113: };
2114:
2115: foreach ($this->getInterfaces() as $interface) {
2116: $addToAncestors($interface->getName(), $interface);
2117: foreach ($interface->getAncestors() as $name => $ancestor) {
2118: $addToAncestors($name, $ancestor);
2119: }
2120: }
2121:
2122: foreach ($this->getTraits() as $trait) {
2123: $addToAncestors($trait->getName(), $trait);
2124: foreach ($trait->getAncestors() as $name => $ancestor) {
2125: $addToAncestors($name, $ancestor);
2126: }
2127: }
2128:
2129: $parent = $this->getParentClass();
2130: if ($parent !== null) {
2131: $addToAncestors($parent->getName(), $parent);
2132: foreach ($parent->getAncestors() as $name => $ancestor) {
2133: $addToAncestors($name, $ancestor);
2134: }
2135: }
2136:
2137: $this->ancestors = $ancestors;
2138: }
2139:
2140: return $ancestors;
2141: }
2142:
2143: public function getAncestorWithClassName(string $className): ?self
2144: {
2145: return $this->getAncestors()[$className] ?? null;
2146: }
2147:
2148: /**
2149: * @param string[] $ancestorClasses
2150: */
2151: private function isValidAncestorType(Type $type, array $ancestorClasses): bool
2152: {
2153: if (!$type instanceof GenericObjectType) {
2154: return false;
2155: }
2156:
2157: $reflection = $type->getClassReflection();
2158: if ($reflection === null) {
2159: return false;
2160: }
2161:
2162: return in_array($reflection->getName(), $ancestorClasses, true);
2163: }
2164:
2165: /**
2166: * @return array<MixinTag>
2167: */
2168: public function getMixinTags(): array
2169: {
2170: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2171: if ($resolvedPhpDoc === null) {
2172: return [];
2173: }
2174:
2175: return $resolvedPhpDoc->getMixinTags();
2176: }
2177:
2178: /**
2179: * @return array<RequireExtendsTag>
2180: */
2181: public function getRequireExtendsTags(): array
2182: {
2183: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2184: if ($resolvedPhpDoc === null) {
2185: return [];
2186: }
2187:
2188: return $resolvedPhpDoc->getRequireExtendsTags();
2189: }
2190:
2191: /**
2192: * @return array<RequireImplementsTag>
2193: */
2194: public function getRequireImplementsTags(): array
2195: {
2196: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2197: if ($resolvedPhpDoc === null) {
2198: return [];
2199: }
2200:
2201: return $resolvedPhpDoc->getRequireImplementsTags();
2202: }
2203:
2204: /**
2205: * @return array<SealedTypeTag>
2206: */
2207: public function getSealedTags(): array
2208: {
2209: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2210: if ($resolvedPhpDoc === null) {
2211: return [];
2212: }
2213:
2214: return $resolvedPhpDoc->getSealedTags();
2215: }
2216:
2217: /**
2218: * @return array<string, PropertyTag>
2219: */
2220: public function getPropertyTags(): array
2221: {
2222: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2223: if ($resolvedPhpDoc === null) {
2224: return [];
2225: }
2226:
2227: return $resolvedPhpDoc->getPropertyTags();
2228: }
2229:
2230: /**
2231: * @return array<string, MethodTag>
2232: */
2233: public function getMethodTags(): array
2234: {
2235: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2236: if ($resolvedPhpDoc === null) {
2237: return [];
2238: }
2239:
2240: return $resolvedPhpDoc->getMethodTags();
2241: }
2242:
2243: /**
2244: * @return list<Type>
2245: */
2246: public function getResolvedMixinTypes(): array
2247: {
2248: $types = [];
2249: foreach ($this->getMixinTags() as $mixinTag) {
2250: if (!$this->isGeneric()) {
2251: $types[] = $mixinTag->getType();
2252: continue;
2253: }
2254:
2255: $types[] = TemplateTypeHelper::resolveTemplateTypes(
2256: $mixinTag->getType(),
2257: $this->getActiveTemplateTypeMap(),
2258: $this->getCallSiteVarianceMap(),
2259: TemplateTypeVariance::createStatic(),
2260: );
2261: }
2262:
2263: return $types;
2264: }
2265:
2266: /**
2267: * @return array<Type>|null
2268: */
2269: public function getAllowedSubTypes(): ?array
2270: {
2271: foreach ($this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions() as $allowedSubTypesClassReflectionExtension) {
2272: if ($allowedSubTypesClassReflectionExtension->supports($this)) {
2273: return $allowedSubTypesClassReflectionExtension->getAllowedSubTypes($this);
2274: }
2275: }
2276:
2277: return null;
2278: }
2279:
2280: }
2281: