1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection;
6:
7: use LogicException;
8: use PhpParser\Node\Identifier;
9: use PhpParser\Node\Name;
10: use ReflectionClass as CoreReflectionClass;
11: use PHPStan\BetterReflection\Reflector\Reflector;
12:
13: use function array_key_exists;
14: use function assert;
15: use function sprintf;
16: use function strtolower;
17:
18: /** @psalm-immutable */
19: class ReflectionNamedType extends ReflectionType
20: {
21: private Reflector $reflector;
22: /**
23: * @var \PHPStan\BetterReflection\Reflection\ReflectionParameter|\PHPStan\BetterReflection\Reflection\ReflectionMethod|\PHPStan\BetterReflection\Reflection\ReflectionFunction|\PHPStan\BetterReflection\Reflection\ReflectionEnum|\PHPStan\BetterReflection\Reflection\ReflectionProperty|\PHPStan\BetterReflection\Reflection\ReflectionClassConstant
24: */
25: private $owner;
26: private const BUILT_IN_TYPES = [
27: 'int' => null,
28: 'float' => null,
29: 'string' => null,
30: 'bool' => null,
31: 'callable' => null,
32: 'self' => null,
33: 'parent' => null,
34: 'array' => null,
35: 'iterable' => null,
36: 'object' => null,
37: 'void' => null,
38: 'mixed' => null,
39: 'static' => null,
40: 'null' => null,
41: 'never' => null,
42: 'false' => null,
43: 'true' => null,
44: ];
45:
46: /** @var non-empty-string */
47: private string $name;
48:
49: private bool $isIdentifier;
50:
51: /** @internal
52: * @param \PHPStan\BetterReflection\Reflection\ReflectionParameter|\PHPStan\BetterReflection\Reflection\ReflectionMethod|\PHPStan\BetterReflection\Reflection\ReflectionFunction|\PHPStan\BetterReflection\Reflection\ReflectionEnum|\PHPStan\BetterReflection\Reflection\ReflectionProperty|\PHPStan\BetterReflection\Reflection\ReflectionClassConstant $owner
53: * @param \PhpParser\Node\Identifier|\PhpParser\Node\Name $type */
54: public function __construct(Reflector $reflector, $owner, $type)
55: {
56: $this->reflector = $reflector;
57: $this->owner = $owner;
58: $this->name = $type->toString();
59: $this->isIdentifier = $type instanceof Identifier;
60: }
61:
62: /**
63: * @return array<string, mixed>
64: */
65: public function exportToCache(): array
66: {
67: return [
68: 'name' => $this->name,
69: 'isIdentifier' => $this->isIdentifier,
70: ];
71: }
72:
73: /**
74: * @param array<string, mixed> $data
75: * @param ReflectionParameter|ReflectionMethod|ReflectionFunction|ReflectionEnum|ReflectionProperty|ReflectionClassConstant $owner
76: */
77: public static function importFromCache(Reflector $reflector, array $data, $owner): self
78: {
79: $reflection = new CoreReflectionClass(self::class);
80: /** @var self $ref */
81: $ref = $reflection->newInstanceWithoutConstructor();
82: $ref->reflector = $reflector;
83: $ref->owner = $owner;
84: $ref->name = $data['name'];
85: $ref->isIdentifier = $data['isIdentifier'];
86:
87: return $ref;
88: }
89:
90: /** @internal
91: * @param \PHPStan\BetterReflection\Reflection\ReflectionParameter|\PHPStan\BetterReflection\Reflection\ReflectionMethod|\PHPStan\BetterReflection\Reflection\ReflectionFunction|\PHPStan\BetterReflection\Reflection\ReflectionEnum|\PHPStan\BetterReflection\Reflection\ReflectionProperty|\PHPStan\BetterReflection\Reflection\ReflectionClassConstant $owner
92: * @return static */
93: public function withOwner($owner)
94: {
95: $clone = clone $this;
96: $clone->owner = $owner;
97:
98: return $clone;
99: }
100:
101: /** @return non-empty-string */
102: public function getName(): string
103: {
104: return $this->name;
105: }
106:
107: /**
108: * Checks if it is a built-in type (i.e., it's not an object...)
109: *
110: * @see https://php.net/manual/en/reflectiontype.isbuiltin.php
111: */
112: public function isBuiltin(): bool
113: {
114: return array_key_exists(strtolower($this->name), self::BUILT_IN_TYPES);
115: }
116:
117: public function getClass(): ReflectionClass
118: {
119: if (! $this->isBuiltin()) {
120: return $this->reflector->reflectClass($this->name);
121: }
122:
123: if (
124: $this->owner instanceof ReflectionEnum
125: || $this->owner instanceof ReflectionFunction
126: || ($this->owner instanceof ReflectionParameter && $this->owner->getDeclaringFunction() instanceof ReflectionFunction)
127: ) {
128: throw new LogicException(sprintf('The type %s cannot be resolved to class', $this->name));
129: }
130:
131: $lowercaseName = strtolower($this->name);
132:
133: if ($lowercaseName === 'self') {
134: $class = $this->owner->getImplementingClass();
135: assert($class instanceof ReflectionClass);
136:
137: return $class;
138: }
139:
140: if ($lowercaseName === 'parent') {
141: $class = $this->owner->getDeclaringClass();
142: assert($class instanceof ReflectionClass);
143: $parentClass = $class->getParentClass();
144: assert($parentClass instanceof ReflectionClass);
145:
146: return $parentClass;
147: }
148:
149: if (
150: $this->owner instanceof ReflectionMethod
151: && $lowercaseName === 'static'
152: ) {
153: return $this->owner->getCurrentClass();
154: }
155:
156: throw new LogicException(sprintf('The type %s cannot be resolved to class', $this->name));
157: }
158:
159: public function allowsNull(): bool
160: {
161: switch (strtolower($this->name)) {
162: case 'mixed':
163: return true;
164: case 'null':
165: return true;
166: default:
167: return false;
168: }
169: }
170:
171: public function isIdentifier(): bool
172: {
173: return $this->isIdentifier;
174: }
175:
176: /** @return non-empty-string */
177: public function __toString(): string
178: {
179: return $this->getName();
180: }
181: }
182: