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