1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection;
6:
7: use PhpParser\Node\Identifier;
8: use PhpParser\Node\IntersectionType;
9: use PhpParser\Node\Name;
10: use PhpParser\Node\UnionType;
11: use ReflectionClass as CoreReflectionClass;
12: use PHPStan\BetterReflection\Reflector\Reflector;
13:
14: use function array_map;
15: use function assert;
16: use function get_class;
17: use function implode;
18: use function sprintf;
19:
20: /** @psalm-immutable */
21: class ReflectionUnionType extends ReflectionType
22: {
23: /** @var non-empty-list<ReflectionNamedType|ReflectionIntersectionType> */
24: private array $types;
25:
26: /** @internal
27: * @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 */
28: public function __construct(
29: Reflector $reflector,
30: $owner,
31: UnionType $type
32: ) {
33: /** @var non-empty-list<ReflectionNamedType|ReflectionIntersectionType> $types */
34: $types = array_map(static function ($type) use ($reflector, $owner) {
35: $type = ReflectionType::createFromNode($reflector, $owner, $type);
36: assert($type instanceof ReflectionNamedType || $type instanceof ReflectionIntersectionType);
37:
38: return $type;
39: }, $type->types);
40:
41: $this->types = $types;
42: }
43:
44: /** @return array<string, mixed> */
45: public function exportToCache(): array
46: {
47: return [
48: 'types' => array_map(
49: static fn ($type) => [
50: 'class' => get_class($type),
51: 'data' => $type->exportToCache(),
52: ],
53: $this->types,
54: ),
55: ];
56: }
57:
58: /**
59: * @param array<string, mixed> $data
60: * @param ReflectionParameter|ReflectionMethod|ReflectionFunction|ReflectionEnum|ReflectionProperty|ReflectionClassConstant $owner
61: */
62: public static function importFromCache(Reflector $reflector, array $data, $owner): self
63: {
64: $reflection = new CoreReflectionClass(self::class);
65: $ref = $reflection->newInstanceWithoutConstructor();
66: assert($ref instanceof self);
67: $ref->types = array_map(
68: static function (array $typeData) use ($reflector, $owner) {
69: $typeClass = $typeData['class'];
70:
71: return $typeClass::importFromCache($reflector, $typeData['data'], $owner);
72: },
73: $data['types'],
74: );
75:
76: return $ref;
77: }
78:
79: /** @internal
80: * @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
81: * @return static */
82: public function withOwner($owner)
83: {
84: $types = array_map(static fn ($type) => $type->withOwner($owner), $this->types);
85: if ($types === $this->types) {
86: // no child consults the owner - see ReflectionNamedType::withOwner()
87: return $this;
88: }
89:
90: $clone = clone $this;
91:
92: $clone->types = $types;
93:
94: return $clone;
95: }
96:
97: /** @return non-empty-list<ReflectionNamedType|ReflectionIntersectionType> */
98: public function getTypes(): array
99: {
100: return $this->types;
101: }
102:
103: public function allowsNull(): bool
104: {
105: foreach ($this->types as $type) {
106: if ($type->allowsNull()) {
107: return true;
108: }
109: }
110:
111: return false;
112: }
113:
114: /** @return non-empty-string */
115: public function __toString(): string
116: {
117: return implode('|', array_map(static function (ReflectionType $type): string {
118: if ($type instanceof ReflectionIntersectionType) {
119: return sprintf('(%s)', $type->__toString());
120: }
121:
122: return $type->__toString();
123: }, $this->types));
124: }
125: }
126: