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