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