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) {
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) {
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: $this->isDeprecated = false;
1438: $this->deprecatedDescription = null;
1439: }
1440:
1441: public function isBuiltin(): bool
1442: {
1443: return $this->reflection->isInternal();
1444: }
1445:
1446: public function isInternal(): bool
1447: {
1448: if ($this->isInternal === null) {
1449: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1450: $this->isInternal = $resolvedPhpDoc !== null && $resolvedPhpDoc->isInternal();
1451: }
1452:
1453: return $this->isInternal;
1454: }
1455:
1456: public function isFinal(): bool
1457: {
1458: if ($this->isFinalByKeyword()) {
1459: return true;
1460: }
1461:
1462: if ($this->isFinal === null) {
1463: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1464: $this->isFinal = $resolvedPhpDoc !== null && $resolvedPhpDoc->isFinal();
1465: }
1466:
1467: return $this->isFinal;
1468: }
1469:
1470: public function isImmutable(): bool
1471: {
1472: if ($this->isImmutable === null) {
1473: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1474: $this->isImmutable = $resolvedPhpDoc !== null && ($resolvedPhpDoc->isImmutable() || $resolvedPhpDoc->isReadOnly());
1475:
1476: $parentClass = $this->getParentClass();
1477: if ($parentClass !== null && !$this->isImmutable) {
1478: $this->isImmutable = $parentClass->isImmutable();
1479: }
1480: }
1481:
1482: return $this->isImmutable;
1483: }
1484:
1485: public function hasConsistentConstructor(): bool
1486: {
1487: if ($this->hasConsistentConstructor === null) {
1488: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1489: $this->hasConsistentConstructor = $resolvedPhpDoc !== null && $resolvedPhpDoc->hasConsistentConstructor();
1490: }
1491:
1492: return $this->hasConsistentConstructor;
1493: }
1494:
1495: public function acceptsNamedArguments(): bool
1496: {
1497: if ($this->acceptsNamedArguments === null) {
1498: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1499: $this->acceptsNamedArguments = $resolvedPhpDoc === null || $resolvedPhpDoc->acceptsNamedArguments();
1500: }
1501:
1502: return $this->acceptsNamedArguments;
1503: }
1504:
1505: public function hasFinalByKeywordOverride(): bool
1506: {
1507: return $this->finalByKeywordOverride !== null;
1508: }
1509:
1510: public function isFinalByKeyword(): bool
1511: {
1512: if ($this->isAnonymous()) {
1513: return true;
1514: }
1515:
1516: if ($this->finalByKeywordOverride !== null) {
1517: return $this->finalByKeywordOverride;
1518: }
1519:
1520: return $this->reflection->isFinal();
1521: }
1522:
1523: public function isAttributeClass(): bool
1524: {
1525: return $this->findAttributeFlags() !== null;
1526: }
1527:
1528: private function findAttributeFlags(): ?int
1529: {
1530: if ($this->isInterface() || $this->isTrait() || $this->isEnum()) {
1531: return null;
1532: }
1533:
1534: $nativeAttributes = $this->reflection->getAttributes(Attribute::class);
1535: if (count($nativeAttributes) === 1) {
1536: if (!$this->reflectionProvider->hasClass(Attribute::class)) {
1537: return null;
1538: }
1539:
1540: $attributeClass = $this->reflectionProvider->getClass(Attribute::class);
1541: $arguments = [];
1542: foreach ($nativeAttributes[0]->getArgumentsExpressions() as $i => $expression) {
1543: if ($i === '') {
1544: throw new ShouldNotHappenException();
1545: }
1546: $arguments[] = new Arg($expression, name: is_int($i) ? null : new Identifier($i));
1547: }
1548:
1549: if (!$attributeClass->hasConstructor()) {
1550: return null;
1551: }
1552: $attributeConstructor = $attributeClass->getConstructor();
1553: $attributeConstructorVariant = $attributeConstructor->getOnlyVariant();
1554:
1555: if (count($arguments) === 0) {
1556: $flagType = $attributeConstructorVariant->getParameters()[0]->getDefaultValue();
1557: } else {
1558: $staticCall = ArgumentsNormalizer::reorderStaticCallArguments(
1559: $attributeConstructorVariant,
1560: new StaticCall(new FullyQualified(Attribute::class), $attributeConstructor->getName(), $arguments),
1561: );
1562: if ($staticCall === null) {
1563: return null;
1564: }
1565: $flagExpr = $staticCall->getArgs()[0]->value;
1566: $flagType = $this->initializerExprTypeResolver->getType($flagExpr, InitializerExprContext::fromClassReflection($this));
1567: }
1568:
1569: if (!$flagType instanceof ConstantIntegerType) {
1570: return null;
1571: }
1572:
1573: return $flagType->getValue();
1574: }
1575:
1576: return null;
1577: }
1578:
1579: /**
1580: * @return list<AttributeReflection>
1581: */
1582: public function getAttributes(): array
1583: {
1584: return $this->attributeReflectionFactory->fromNativeReflection($this->reflection->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()));
1585: }
1586:
1587: public function getAttributeClassFlags(): int
1588: {
1589: $flags = $this->findAttributeFlags();
1590: if ($flags === null) {
1591: throw new ShouldNotHappenException();
1592: }
1593:
1594: return $flags;
1595: }
1596:
1597: public function getObjectType(): ObjectType
1598: {
1599: if (!$this->isGeneric()) {
1600: return new ObjectType($this->getName());
1601: }
1602:
1603: return new GenericObjectType(
1604: $this->getName(),
1605: $this->typeMapToList($this->getActiveTemplateTypeMap()),
1606: variances: $this->varianceMapToList($this->getCallSiteVarianceMap()),
1607: );
1608: }
1609:
1610: public function getTemplateTypeMap(): TemplateTypeMap
1611: {
1612: if ($this->templateTypeMap !== null) {
1613: return $this->templateTypeMap;
1614: }
1615:
1616: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1617: if ($resolvedPhpDoc === null) {
1618: $this->templateTypeMap = TemplateTypeMap::createEmpty();
1619: return $this->templateTypeMap;
1620: }
1621:
1622: $templateTypeScope = TemplateTypeScope::createWithClass($this->getName());
1623:
1624: $templateTypeMap = new TemplateTypeMap(array_map(static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag), $this->getTemplateTags()));
1625:
1626: $this->templateTypeMap = $templateTypeMap;
1627:
1628: return $templateTypeMap;
1629: }
1630:
1631: public function getActiveTemplateTypeMap(): TemplateTypeMap
1632: {
1633: if ($this->activeTemplateTypeMap !== null) {
1634: return $this->activeTemplateTypeMap;
1635: }
1636: $resolved = $this->resolvedTemplateTypeMap;
1637: if ($resolved !== null) {
1638: $templateTypeMap = $this->getTemplateTypeMap();
1639: return $this->activeTemplateTypeMap = $resolved->map(static function (string $name, Type $type) use ($templateTypeMap): Type {
1640: if ($type instanceof ErrorType) {
1641: $templateType = $templateTypeMap->getType($name);
1642: if ($templateType !== null) {
1643: return TemplateTypeHelper::resolveToDefaults($templateType);
1644: }
1645: }
1646:
1647: return $type;
1648: });
1649: }
1650:
1651: return $this->activeTemplateTypeMap = $this->getTemplateTypeMap();
1652: }
1653:
1654: public function getPossiblyIncompleteActiveTemplateTypeMap(): TemplateTypeMap
1655: {
1656: return $this->resolvedTemplateTypeMap ?? $this->getTemplateTypeMap();
1657: }
1658:
1659: private function getDefaultCallSiteVarianceMap(): TemplateTypeVarianceMap
1660: {
1661: if ($this->defaultCallSiteVarianceMap !== null) {
1662: return $this->defaultCallSiteVarianceMap;
1663: }
1664:
1665: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1666: if ($resolvedPhpDoc === null) {
1667: $this->defaultCallSiteVarianceMap = TemplateTypeVarianceMap::createEmpty();
1668: return $this->defaultCallSiteVarianceMap;
1669: }
1670:
1671: $map = [];
1672: foreach ($this->getTemplateTags() as $templateTag) {
1673: $map[$templateTag->getName()] = TemplateTypeVariance::createInvariant();
1674: }
1675:
1676: $this->defaultCallSiteVarianceMap = new TemplateTypeVarianceMap($map);
1677: return $this->defaultCallSiteVarianceMap;
1678: }
1679:
1680: public function getCallSiteVarianceMap(): TemplateTypeVarianceMap
1681: {
1682: return $this->callSiteVarianceMap ??= $this->resolvedCallSiteVarianceMap ?? $this->getDefaultCallSiteVarianceMap();
1683: }
1684:
1685: public function isGeneric(): bool
1686: {
1687: if ($this->isGeneric === null) {
1688: if ($this->isEnum()) {
1689: return $this->isGeneric = false;
1690: }
1691:
1692: $this->isGeneric = count($this->getTemplateTags()) > 0;
1693: }
1694:
1695: return $this->isGeneric;
1696: }
1697:
1698: /**
1699: * @param array<int, Type> $types
1700: */
1701: public function typeMapFromList(array $types): TemplateTypeMap
1702: {
1703: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1704: if ($resolvedPhpDoc === null) {
1705: return TemplateTypeMap::createEmpty();
1706: }
1707:
1708: $map = [];
1709: $i = 0;
1710: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1711: $map[$tag->getName()] = $types[$i] ?? $tag->getDefault() ?? $tag->getBound();
1712: $i++;
1713: }
1714:
1715: return new TemplateTypeMap($map);
1716: }
1717:
1718: /**
1719: * @param array<int, TemplateTypeVariance> $variances
1720: */
1721: public function varianceMapFromList(array $variances): TemplateTypeVarianceMap
1722: {
1723: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1724: if ($resolvedPhpDoc === null) {
1725: return new TemplateTypeVarianceMap([]);
1726: }
1727:
1728: $map = [];
1729: $i = 0;
1730: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1731: $map[$tag->getName()] = $variances[$i] ?? TemplateTypeVariance::createInvariant();
1732: $i++;
1733: }
1734:
1735: return new TemplateTypeVarianceMap($map);
1736: }
1737:
1738: /** @return list<Type> */
1739: public function typeMapToList(TemplateTypeMap $typeMap): array
1740: {
1741: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1742: if ($resolvedPhpDoc === null) {
1743: return [];
1744: }
1745:
1746: $list = [];
1747: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1748: $list[] = $typeMap->getType($tag->getName()) ?? $tag->getDefault() ?? $tag->getBound();
1749: }
1750:
1751: return $list;
1752: }
1753:
1754: /** @return list<TemplateTypeVariance> */
1755: public function varianceMapToList(TemplateTypeVarianceMap $varianceMap): array
1756: {
1757: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1758: if ($resolvedPhpDoc === null) {
1759: return [];
1760: }
1761:
1762: $list = [];
1763: foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
1764: $list[] = $varianceMap->getVariance($tag->getName()) ?? TemplateTypeVariance::createInvariant();
1765: }
1766:
1767: return $list;
1768: }
1769:
1770: /**
1771: * @param array<int, Type> $types
1772: */
1773: public function withTypes(array $types): self
1774: {
1775: return new self(
1776: $this->reflectionProvider,
1777: $this->initializerExprTypeResolver,
1778: $this->fileTypeMapper,
1779: $this->stubPhpDocProvider,
1780: $this->phpDocInheritanceResolver,
1781: $this->phpVersion,
1782: $this->signatureMapProvider,
1783: $this->deprecationProvider,
1784: $this->attributeReflectionFactory,
1785: $this->phpClassReflectionExtension,
1786: $this->propertiesClassReflectionExtensions,
1787: $this->methodsClassReflectionExtensions,
1788: $this->allowedSubTypesClassReflectionExtensions,
1789: $this->requireExtendsPropertiesClassReflectionExtension,
1790: $this->requireExtendsMethodsClassReflectionExtension,
1791: $this->displayName,
1792: $this->reflection,
1793: $this->anonymousFilename,
1794: $this->typeMapFromList($types),
1795: $this->stubPhpDocBlock,
1796: $this->universalObjectCratesClasses,
1797: null,
1798: $this->resolvedCallSiteVarianceMap,
1799: $this->finalByKeywordOverride,
1800: );
1801: }
1802:
1803: /**
1804: * @param array<int, TemplateTypeVariance> $variances
1805: */
1806: public function withVariances(array $variances): self
1807: {
1808: return new self(
1809: $this->reflectionProvider,
1810: $this->initializerExprTypeResolver,
1811: $this->fileTypeMapper,
1812: $this->stubPhpDocProvider,
1813: $this->phpDocInheritanceResolver,
1814: $this->phpVersion,
1815: $this->signatureMapProvider,
1816: $this->deprecationProvider,
1817: $this->attributeReflectionFactory,
1818: $this->phpClassReflectionExtension,
1819: $this->propertiesClassReflectionExtensions,
1820: $this->methodsClassReflectionExtensions,
1821: $this->allowedSubTypesClassReflectionExtensions,
1822: $this->requireExtendsPropertiesClassReflectionExtension,
1823: $this->requireExtendsMethodsClassReflectionExtension,
1824: $this->displayName,
1825: $this->reflection,
1826: $this->anonymousFilename,
1827: $this->resolvedTemplateTypeMap,
1828: $this->stubPhpDocBlock,
1829: $this->universalObjectCratesClasses,
1830: null,
1831: $this->varianceMapFromList($variances),
1832: $this->finalByKeywordOverride,
1833: );
1834: }
1835:
1836: public function asFinal(): self
1837: {
1838: if ($this->getNativeReflection()->isFinal()) {
1839: return $this;
1840: }
1841: if ($this->finalByKeywordOverride === true) {
1842: return $this;
1843: }
1844: if (!$this->isClass()) {
1845: return $this;
1846: }
1847: if ($this->isAbstract()) {
1848: return $this;
1849: }
1850:
1851: return new self(
1852: $this->reflectionProvider,
1853: $this->initializerExprTypeResolver,
1854: $this->fileTypeMapper,
1855: $this->stubPhpDocProvider,
1856: $this->phpDocInheritanceResolver,
1857: $this->phpVersion,
1858: $this->signatureMapProvider,
1859: $this->deprecationProvider,
1860: $this->attributeReflectionFactory,
1861: $this->phpClassReflectionExtension,
1862: $this->propertiesClassReflectionExtensions,
1863: $this->methodsClassReflectionExtensions,
1864: $this->allowedSubTypesClassReflectionExtensions,
1865: $this->requireExtendsPropertiesClassReflectionExtension,
1866: $this->requireExtendsMethodsClassReflectionExtension,
1867: $this->displayName,
1868: $this->reflection,
1869: $this->anonymousFilename,
1870: $this->resolvedTemplateTypeMap,
1871: $this->stubPhpDocBlock,
1872: $this->universalObjectCratesClasses,
1873: null,
1874: $this->resolvedCallSiteVarianceMap,
1875: true,
1876: );
1877: }
1878:
1879: public function removeFinalKeywordOverride(): self
1880: {
1881: if ($this->getNativeReflection()->isFinal()) {
1882: return $this;
1883: }
1884: if ($this->finalByKeywordOverride === false) {
1885: return $this;
1886: }
1887: if (!$this->isClass()) {
1888: return $this;
1889: }
1890: if ($this->isAbstract()) {
1891: return $this;
1892: }
1893:
1894: return new self(
1895: $this->reflectionProvider,
1896: $this->initializerExprTypeResolver,
1897: $this->fileTypeMapper,
1898: $this->stubPhpDocProvider,
1899: $this->phpDocInheritanceResolver,
1900: $this->phpVersion,
1901: $this->signatureMapProvider,
1902: $this->deprecationProvider,
1903: $this->attributeReflectionFactory,
1904: $this->phpClassReflectionExtension,
1905: $this->propertiesClassReflectionExtensions,
1906: $this->methodsClassReflectionExtensions,
1907: $this->allowedSubTypesClassReflectionExtensions,
1908: $this->requireExtendsPropertiesClassReflectionExtension,
1909: $this->requireExtendsMethodsClassReflectionExtension,
1910: $this->displayName,
1911: $this->reflection,
1912: $this->anonymousFilename,
1913: $this->resolvedTemplateTypeMap,
1914: $this->stubPhpDocBlock,
1915: $this->universalObjectCratesClasses,
1916: null,
1917: $this->resolvedCallSiteVarianceMap,
1918: false,
1919: );
1920: }
1921:
1922: public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock
1923: {
1924: if ($this->stubPhpDocBlock !== null) {
1925: return $this->stubPhpDocBlock;
1926: }
1927:
1928: $fileName = $this->getFileName();
1929: if (is_bool($this->reflectionDocComment)) {
1930: $docComment = $this->reflection->getDocComment();
1931: $this->reflectionDocComment = $docComment !== false ? $docComment : null;
1932: }
1933:
1934: if ($this->reflectionDocComment === null) {
1935: return null;
1936: }
1937:
1938: if ($this->resolvedPhpDocBlock !== false) {
1939: return $this->resolvedPhpDocBlock;
1940: }
1941:
1942: return $this->resolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $this->getName(), null, null, $this->reflectionDocComment);
1943: }
1944:
1945: public function getTraitContextResolvedPhpDoc(self $implementingClass): ?ResolvedPhpDocBlock
1946: {
1947: if (!$this->isTrait()) {
1948: throw new ShouldNotHappenException();
1949: }
1950: if ($implementingClass->isTrait()) {
1951: throw new ShouldNotHappenException();
1952: }
1953: $fileName = $this->getFileName();
1954: if (is_bool($this->reflectionDocComment)) {
1955: $docComment = $this->reflection->getDocComment();
1956: $this->reflectionDocComment = $docComment !== false ? $docComment : null;
1957: }
1958:
1959: if ($this->reflectionDocComment === null) {
1960: return null;
1961: }
1962:
1963: if ($this->traitContextResolvedPhpDocBlock !== false) {
1964: return $this->traitContextResolvedPhpDocBlock;
1965: }
1966:
1967: return $this->traitContextResolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $implementingClass->getName(), $this->getName(), null, $this->reflectionDocComment);
1968: }
1969:
1970: private function getFirstExtendsTag(): ?ExtendsTag
1971: {
1972: foreach ($this->getExtendsTags() as $tag) {
1973: return $tag;
1974: }
1975:
1976: return null;
1977: }
1978:
1979: /** @return array<string, ExtendsTag> */
1980: public function getExtendsTags(): array
1981: {
1982: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1983: if ($resolvedPhpDoc === null) {
1984: return [];
1985: }
1986:
1987: return $resolvedPhpDoc->getExtendsTags();
1988: }
1989:
1990: /** @return array<string, ImplementsTag> */
1991: public function getImplementsTags(): array
1992: {
1993: $resolvedPhpDoc = $this->getResolvedPhpDoc();
1994: if ($resolvedPhpDoc === null) {
1995: return [];
1996: }
1997:
1998: return $resolvedPhpDoc->getImplementsTags();
1999: }
2000:
2001: /** @return array<string,TemplateTag> */
2002: public function getTemplateTags(): array
2003: {
2004: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2005: if ($resolvedPhpDoc === null) {
2006: return [];
2007: }
2008:
2009: return $resolvedPhpDoc->getTemplateTags();
2010: }
2011:
2012: /**
2013: * @return array<string,ClassReflection>
2014: */
2015: public function getAncestors(): array
2016: {
2017: $ancestors = $this->ancestors;
2018:
2019: if ($ancestors === null) {
2020: $ancestors = [
2021: $this->getName() => $this,
2022: ];
2023:
2024: $addToAncestors = static function (string $name, ClassReflection $classReflection) use (&$ancestors): void {
2025: if (array_key_exists($name, $ancestors)) {
2026: return;
2027: }
2028:
2029: $ancestors[$name] = $classReflection;
2030: };
2031:
2032: foreach ($this->getInterfaces() as $interface) {
2033: $addToAncestors($interface->getName(), $interface);
2034: foreach ($interface->getAncestors() as $name => $ancestor) {
2035: $addToAncestors($name, $ancestor);
2036: }
2037: }
2038:
2039: foreach ($this->getTraits() as $trait) {
2040: $addToAncestors($trait->getName(), $trait);
2041: foreach ($trait->getAncestors() as $name => $ancestor) {
2042: $addToAncestors($name, $ancestor);
2043: }
2044: }
2045:
2046: $parent = $this->getParentClass();
2047: if ($parent !== null) {
2048: $addToAncestors($parent->getName(), $parent);
2049: foreach ($parent->getAncestors() as $name => $ancestor) {
2050: $addToAncestors($name, $ancestor);
2051: }
2052: }
2053:
2054: $this->ancestors = $ancestors;
2055: }
2056:
2057: return $ancestors;
2058: }
2059:
2060: public function getAncestorWithClassName(string $className): ?self
2061: {
2062: return $this->getAncestors()[$className] ?? null;
2063: }
2064:
2065: /**
2066: * @param string[] $ancestorClasses
2067: */
2068: private function isValidAncestorType(Type $type, array $ancestorClasses): bool
2069: {
2070: if (!$type instanceof GenericObjectType) {
2071: return false;
2072: }
2073:
2074: $reflection = $type->getClassReflection();
2075: if ($reflection === null) {
2076: return false;
2077: }
2078:
2079: return in_array($reflection->getName(), $ancestorClasses, true);
2080: }
2081:
2082: /**
2083: * @return array<MixinTag>
2084: */
2085: public function getMixinTags(): array
2086: {
2087: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2088: if ($resolvedPhpDoc === null) {
2089: return [];
2090: }
2091:
2092: return $resolvedPhpDoc->getMixinTags();
2093: }
2094:
2095: /**
2096: * @return array<RequireExtendsTag>
2097: */
2098: public function getRequireExtendsTags(): array
2099: {
2100: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2101: if ($resolvedPhpDoc === null) {
2102: return [];
2103: }
2104:
2105: return $resolvedPhpDoc->getRequireExtendsTags();
2106: }
2107:
2108: /**
2109: * @return array<RequireImplementsTag>
2110: */
2111: public function getRequireImplementsTags(): array
2112: {
2113: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2114: if ($resolvedPhpDoc === null) {
2115: return [];
2116: }
2117:
2118: return $resolvedPhpDoc->getRequireImplementsTags();
2119: }
2120:
2121: /**
2122: * @return array<SealedTypeTag>
2123: */
2124: public function getSealedTags(): array
2125: {
2126: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2127: if ($resolvedPhpDoc === null) {
2128: return [];
2129: }
2130:
2131: return $resolvedPhpDoc->getSealedTags();
2132: }
2133:
2134: /**
2135: * @return array<string, PropertyTag>
2136: */
2137: public function getPropertyTags(): array
2138: {
2139: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2140: if ($resolvedPhpDoc === null) {
2141: return [];
2142: }
2143:
2144: return $resolvedPhpDoc->getPropertyTags();
2145: }
2146:
2147: /**
2148: * @return array<string, MethodTag>
2149: */
2150: public function getMethodTags(): array
2151: {
2152: $resolvedPhpDoc = $this->getResolvedPhpDoc();
2153: if ($resolvedPhpDoc === null) {
2154: return [];
2155: }
2156:
2157: return $resolvedPhpDoc->getMethodTags();
2158: }
2159:
2160: /**
2161: * @return list<Type>
2162: */
2163: public function getResolvedMixinTypes(): array
2164: {
2165: $types = [];
2166: foreach ($this->getMixinTags() as $mixinTag) {
2167: if (!$this->isGeneric()) {
2168: $types[] = $mixinTag->getType();
2169: continue;
2170: }
2171:
2172: $types[] = TemplateTypeHelper::resolveTemplateTypes(
2173: $mixinTag->getType(),
2174: $this->getActiveTemplateTypeMap(),
2175: $this->getCallSiteVarianceMap(),
2176: TemplateTypeVariance::createStatic(),
2177: );
2178: }
2179:
2180: return $types;
2181: }
2182:
2183: /**
2184: * @return array<Type>|null
2185: */
2186: public function getAllowedSubTypes(): ?array
2187: {
2188: foreach ($this->allowedSubTypesClassReflectionExtensions as $allowedSubTypesClassReflectionExtension) {
2189: if ($allowedSubTypesClassReflectionExtension->supports($this)) {
2190: return $allowedSubTypesClassReflectionExtension->getAllowedSubTypes($this);
2191: }
2192: }
2193:
2194: return null;
2195: }
2196:
2197: }
2198: