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