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