8b9ba566e33062aaeda94774888c0f1bf0ce38d4
[GitHub/WoltLab/WCF.git] /
1 <?php
2
3 declare(strict_types=1);
4
5 namespace CuyZ\Valinor\Mapper\Tree\Builder;
6
7 use CuyZ\Valinor\Definition\FunctionDefinition;
8 use CuyZ\Valinor\Definition\FunctionsContainer;
9 use CuyZ\Valinor\Mapper\Tree\Exception\InvalidAbstractObjectName;
10 use CuyZ\Valinor\Mapper\Tree\Exception\InvalidResolvedImplementationValue;
11 use CuyZ\Valinor\Mapper\Tree\Exception\MissingObjectImplementationRegistration;
12 use CuyZ\Valinor\Mapper\Tree\Exception\ObjectImplementationCallbackError;
13 use CuyZ\Valinor\Mapper\Tree\Exception\ObjectImplementationNotRegistered;
14 use CuyZ\Valinor\Mapper\Tree\Exception\ResolvedImplementationIsNotAccepted;
15 use CuyZ\Valinor\Type\ClassType;
16 use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
17 use CuyZ\Valinor\Type\Parser\TypeParser;
18 use CuyZ\Valinor\Type\Type;
19 use CuyZ\Valinor\Type\Types\ClassStringType;
20 use CuyZ\Valinor\Type\Types\InterfaceType;
21 use CuyZ\Valinor\Type\Types\UnionType;
22 use Exception;
23
24 /** @internal */
25 final class ObjectImplementations
26 {
27 /** @var array<string, non-empty-array<string, ClassType>> */
28 private array $implementations = [];
29
30 public function __construct(
31 private FunctionsContainer $functions,
32 private TypeParser $typeParser
33 ) {
34 foreach ($functions as $name => $function) {
35 /** @var string $name */
36 $this->implementations[$name] = $this->implementations($name);
37 }
38 }
39
40 public function has(string $name): bool
41 {
42 return $this->functions->has($name);
43 }
44
45 public function function(string $name): FunctionDefinition
46 {
47 return $this->functions->get($name)->definition();
48 }
49
50 /**
51 * @param mixed[] $arguments
52 */
53 public function implementation(string $name, array $arguments): ClassType
54 {
55 $class = $this->call($name, $arguments);
56
57 return $this->implementations[$name][$class]
58 ?? throw new ObjectImplementationNotRegistered($class, $name, $this->implementations[$name]);
59 }
60
61 /**
62 * @param mixed[] $arguments
63 */
64 private function call(string $name, array $arguments): string
65 {
66 try {
67 $signature = ($this->functions->get($name)->callback())(...$arguments);
68 } catch (Exception $exception) {
69 throw new ObjectImplementationCallbackError($name, $exception);
70 }
71
72 if (! is_string($signature)) {
73 throw new InvalidResolvedImplementationValue($name, $signature);
74 }
75
76 return $signature;
77 }
78
79 /**
80 * @return non-empty-array<string, ClassType>
81 */
82 private function implementations(string $name): array
83 {
84 $function = $this->functions->get($name)->definition();
85
86 try {
87 $type = $this->typeParser->parse($name);
88 } catch (InvalidType) {
89 }
90
91 if (! isset($type) || (! $type instanceof InterfaceType && ! $type instanceof ClassType)) {
92 throw new InvalidAbstractObjectName($name);
93 }
94
95 $classes = $this->implementationsByReturnSignature($name, $function);
96
97 if (empty($classes)) {
98 throw new MissingObjectImplementationRegistration($name, $function);
99 }
100
101 foreach ($classes as $classType) {
102 if (! $classType instanceof ClassType || ! $classType->matches($type)) {
103 throw new ResolvedImplementationIsNotAccepted($name, $classType);
104 }
105 }
106
107 /** @var non-empty-array<string, ClassType> $classes */
108 return $classes;
109 }
110
111 /**
112 * @return array<string, Type>
113 */
114 private function implementationsByReturnSignature(string $name, FunctionDefinition $function): array
115 {
116 $returnType = $function->returnType();
117
118 if (! $returnType instanceof ClassStringType && ! $returnType instanceof UnionType) {
119 if (count($function->parameters()) > 0) {
120 return [];
121 }
122
123 $class = $this->call($name, []);
124 $classType = $this->typeParser->parse($class);
125
126 return [$classType->toString() => $classType];
127 }
128
129 $types = $returnType instanceof UnionType
130 ? $returnType->types()
131 : [$returnType];
132
133 $classes = [];
134
135 foreach ($types as $type) {
136 if (! $type instanceof ClassStringType) {
137 return [];
138 }
139
140 $subType = $type->subType();
141
142 if ($subType === null) {
143 return [];
144 }
145
146 $subTypes = $subType instanceof UnionType
147 ? $subType->types()
148 : [$subType];
149
150 foreach ($subTypes as $classType) {
151 $classes[$classType->toString()] = $classType;
152 }
153 }
154
155 return $classes;
156 }
157 }