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