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 implode;
17: use function sprintf;
18:
19: /** @psalm-immutable */
20: class ReflectionUnionType extends ReflectionType
21: {
22: /** @var non-empty-list<ReflectionNamedType|ReflectionIntersectionType> */
23: private array $types;
24:
25: /** @internal
26: * @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 */
27: public function __construct(
28: Reflector $reflector,
29: $owner,
30: UnionType $type
31: ) {
32: /** @var non-empty-list<ReflectionNamedType|ReflectionIntersectionType> $types */
33: $types = array_map(static function ($type) use ($reflector, $owner) {
34: $type = ReflectionType::createFromNode($reflector, $owner, $type);
35: assert($type instanceof ReflectionNamedType || $type instanceof ReflectionIntersectionType);
36:
37: return $type;
38: }, $type->types);
39:
40: $this->types = $types;
41: }
42:
43: /**
44: * @return array<string, mixed>
45: */
46: public function exportToCache(): array
47: {
48: return [
49: 'types' => array_map(
50: static fn ($type) => [
51: 'class' => get_class($type),
52: 'data' => $type->exportToCache(),
53: ],
54: $this->types,
55: ),
56: ];
57: }
58:
59: /**
60: * @param array<string, mixed> $data
61: * @param ReflectionParameter|ReflectionMethod|ReflectionFunction|ReflectionEnum|ReflectionProperty|ReflectionClassConstant $owner
62: */
63: public static function importFromCache(Reflector $reflector, array $data, $owner): self
64: {
65: $reflection = new CoreReflectionClass(self::class);
66: /** @var self $ref */
67: $ref = $reflection->newInstanceWithoutConstructor();
68: $ref->types = array_map(
69: static function (array $typeData) use ($reflector, $owner) {
70: $typeClass = $typeData['class'];
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: $clone = clone $this;
85:
86: $clone->types = array_map(static fn ($type) => $type->withOwner($owner), $clone->types);
87:
88: return $clone;
89: }
90:
91: /** @return non-empty-list<ReflectionNamedType|ReflectionIntersectionType> */
92: public function getTypes(): array
93: {
94: return $this->types;
95: }
96:
97: public function allowsNull(): bool
98: {
99: foreach ($this->types as $type) {
100: if ($type->allowsNull()) {
101: return true;
102: }
103: }
104:
105: return false;
106: }
107:
108: /** @return non-empty-string */
109: public function __toString(): string
110: {
111: return implode('|', array_map(static function (ReflectionType $type): string {
112: if ($type instanceof ReflectionIntersectionType) {
113: return sprintf('(%s)', $type->__toString());
114: }
115:
116: return $type->__toString();
117: }, $this->types));
118: }
119: }
120: