1
|
|
|
<?php |
2
|
|
|
declare(strict_types = 1); |
3
|
|
|
|
4
|
|
|
namespace Innmind\Compose\Loader; |
5
|
|
|
|
6
|
|
|
use Innmind\Compose\{ |
7
|
|
|
Loader, |
8
|
|
|
Definitions, |
9
|
|
|
Arguments, |
10
|
|
|
Definition\Argument, |
11
|
|
|
Definition\Argument\Types, |
12
|
|
|
Definition\Name, |
13
|
|
|
Definition\Service, |
14
|
|
|
Definition\Service\Arguments as ServiceArguments, |
15
|
|
|
Definition\Service\Constructors, |
16
|
|
|
Exception\DomainException |
17
|
|
|
}; |
18
|
|
|
use Innmind\Url\PathInterface; |
19
|
|
|
use Innmind\Immutable\{ |
20
|
|
|
Str, |
21
|
|
|
Stream, |
22
|
|
|
Map, |
23
|
|
|
Pair, |
24
|
|
|
Sequence |
25
|
|
|
}; |
26
|
|
|
use Symfony\Component\{ |
27
|
|
|
Yaml\Yaml as Lib, |
28
|
|
|
OptionsResolver\OptionsResolver |
29
|
|
|
}; |
30
|
|
|
|
31
|
|
|
final class Yaml implements Loader |
32
|
|
|
{ |
33
|
|
|
private const ARGUMENT_PATTERN = '~^(?<optional>\?)?(?<type>.+)( \?\? \$.+)?$~'; |
34
|
|
|
private const ARGUMENT_DEFAULT_PATTERN = '~( \?\? \$(?<default>.+))$~'; |
35
|
|
|
private const SERVICE_NAME = "~^(?<name>[a-zA-Z0-9]+)[\s ](?<constructor>.+)$~"; //split on space or non breaking space |
36
|
|
|
private const STACK_NAME = "~^(?<name>[a-zA-Z0-9]+)[\s ]stack$~"; //split on space or non breaking space |
37
|
|
|
|
38
|
|
|
private $resolver; |
39
|
|
|
private $types; |
40
|
|
|
private $arguments; |
41
|
|
|
private $constructors; |
42
|
|
|
private $stacks; |
43
|
|
|
|
44
|
3 |
|
public function __construct( |
45
|
|
|
Types $types = null, |
46
|
|
|
ServiceArguments $arguments = null, |
47
|
|
|
Constructors $constructors = null |
48
|
|
|
) { |
49
|
3 |
|
$this->resolver = new OptionsResolver; |
50
|
3 |
|
$this->resolver->setRequired(['expose', 'services']); |
51
|
3 |
|
$this->resolver->setDefined('arguments'); |
52
|
3 |
|
$this->resolver->setAllowedTypes('arguments', 'array'); |
53
|
3 |
|
$this->resolver->setAllowedTypes('expose', 'array'); |
54
|
3 |
|
$this->resolver->setAllowedTypes('services', 'array'); |
55
|
3 |
|
$this->resolver->setDefault('arguments', []); |
56
|
3 |
|
$this->types = $types ?? new Types; |
57
|
3 |
|
$this->arguments = $arguments ?? new ServiceArguments; |
58
|
3 |
|
$this->constructors = $constructors ?? new Constructors; |
59
|
3 |
|
$this->stacks = new Map(Name::class, Sequence::class); |
60
|
3 |
|
} |
61
|
|
|
|
62
|
2 |
|
public function __invoke(PathInterface $definition): Definitions |
63
|
|
|
{ |
64
|
2 |
|
$this->stacks->clear(); |
65
|
|
|
|
66
|
2 |
|
$data = Lib::parseFile((string) $definition); |
67
|
2 |
|
$data = $this->resolver->resolve($data); |
68
|
|
|
|
69
|
2 |
|
$arguments = $this->buildArguments($data['arguments']); |
70
|
2 |
|
$definitions = $this->buildDefinitions( |
71
|
2 |
|
Stream::of('string'), |
72
|
2 |
|
$data['services'] |
73
|
|
|
); |
74
|
|
|
|
75
|
2 |
|
$exposed = Map::of( |
76
|
2 |
|
'string', |
77
|
2 |
|
'string', |
78
|
2 |
|
array_keys($data['expose']), |
79
|
2 |
|
array_values($data['expose']) |
80
|
|
|
); |
81
|
|
|
|
82
|
2 |
|
$definitions = new Definitions( |
83
|
2 |
|
$arguments, |
84
|
2 |
|
...$definitions->values() |
85
|
|
|
); |
86
|
2 |
|
$definitions = $this->buildStacks($definitions); |
87
|
|
|
|
88
|
|
|
return $exposed |
89
|
2 |
|
->map(static function(string $as, string $name): Pair { |
90
|
2 |
|
return new Pair( |
91
|
2 |
|
$as, |
92
|
2 |
|
(string) Str::of($name)->substring(1) //remove the $ sign |
93
|
|
|
); |
94
|
2 |
|
}) |
95
|
2 |
|
->reduce( |
96
|
2 |
|
$definitions, |
97
|
2 |
|
static function(Definitions $definitions, string $as, string $name): Definitions { |
98
|
2 |
|
return $definitions->expose( |
99
|
2 |
|
new Name($name), |
100
|
2 |
|
new Name($as) |
101
|
|
|
); |
102
|
2 |
|
} |
103
|
|
|
); |
104
|
|
|
} |
105
|
|
|
|
106
|
2 |
|
private function buildArguments(array $definitions): Arguments |
107
|
|
|
{ |
108
|
2 |
|
$arguments = []; |
109
|
|
|
|
110
|
2 |
|
foreach ($definitions as $name => $type) { |
111
|
2 |
|
$arguments[] = $this->buildArgument($name, Str::of($type)->trim()); |
112
|
|
|
} |
113
|
|
|
|
114
|
2 |
|
return new Arguments(...$arguments); |
115
|
|
|
} |
116
|
|
|
|
117
|
2 |
|
private function buildArgument(string $name, Str $type): Argument |
118
|
|
|
{ |
119
|
2 |
|
if (!$type->matches(self::ARGUMENT_PATTERN)) { |
120
|
|
|
throw new DomainException; |
121
|
|
|
} |
122
|
|
|
|
123
|
2 |
|
$components = $type->capture(self::ARGUMENT_PATTERN); |
124
|
|
|
|
125
|
2 |
|
$argument = new Argument( |
126
|
2 |
|
new Name($name), |
127
|
2 |
|
$this->types->load( |
128
|
|
|
$components |
129
|
2 |
|
->get('type') |
130
|
2 |
|
->pregReplace( |
131
|
2 |
|
self::ARGUMENT_DEFAULT_PATTERN, |
132
|
2 |
|
'' |
133
|
|
|
) |
134
|
|
|
) |
135
|
|
|
); |
136
|
|
|
|
137
|
|
|
if ( |
138
|
2 |
|
$components->contains('optional') && |
139
|
2 |
|
!$components->get('optional')->empty() |
140
|
|
|
) { |
141
|
2 |
|
$argument = $argument->makeOptional(); |
142
|
|
|
} |
143
|
|
|
|
144
|
2 |
|
if ($type->matches(self::ARGUMENT_DEFAULT_PATTERN)) { |
145
|
2 |
|
$argument = $argument->defaultsTo(new Name( |
146
|
|
|
(string) $type |
147
|
2 |
|
->capture(self::ARGUMENT_DEFAULT_PATTERN) |
148
|
2 |
|
->get('default') |
149
|
|
|
)); |
150
|
|
|
} |
151
|
|
|
|
152
|
2 |
|
return $argument; |
153
|
|
|
} |
154
|
|
|
|
155
|
2 |
|
private function buildDefinitions(Stream $namespace, array $definitions): Map |
156
|
|
|
{ |
157
|
2 |
|
$services = new Map('string', Service::class); |
158
|
|
|
|
159
|
2 |
|
foreach ($definitions as $key => $value) { |
160
|
2 |
|
$key = Str::of($key); |
161
|
|
|
|
162
|
2 |
|
if (!is_array($value)) { |
163
|
|
|
throw new DomainException; |
164
|
|
|
} |
165
|
|
|
|
166
|
2 |
|
if ($key->matches(self::STACK_NAME)) { |
167
|
2 |
|
$this->registerStack($namespace, $key, $value); |
168
|
|
|
|
169
|
2 |
|
continue; |
170
|
|
|
} |
171
|
|
|
|
172
|
2 |
|
if (!$key->matches(self::SERVICE_NAME)) { |
173
|
2 |
|
$services = $services->merge( |
174
|
2 |
|
$this->buildDefinitions( |
175
|
2 |
|
$namespace->add((string) $key), |
176
|
2 |
|
$value |
177
|
|
|
) |
178
|
|
|
); |
179
|
|
|
|
180
|
2 |
|
continue; |
181
|
|
|
} |
182
|
|
|
|
183
|
2 |
|
$service = $this->buildService($namespace, $key, $value); |
184
|
2 |
|
$services = $services->put( |
185
|
2 |
|
(string) $service->name(), |
|
|
|
|
186
|
2 |
|
$service |
187
|
|
|
); |
188
|
|
|
} |
189
|
|
|
|
190
|
2 |
|
return $services; |
|
|
|
|
191
|
|
|
} |
192
|
|
|
|
193
|
2 |
|
private function buildService( |
194
|
|
|
Stream $namespace, |
195
|
|
|
Str $name, |
196
|
|
|
array $arguments |
197
|
|
|
): Service { |
198
|
2 |
|
$components = $name->capture(self::SERVICE_NAME); |
199
|
|
|
|
200
|
2 |
|
foreach ($arguments as &$argument) { |
201
|
2 |
|
$argument = $this->arguments->load($argument); |
202
|
|
|
} |
203
|
|
|
|
204
|
2 |
|
return new Service( |
205
|
2 |
|
new Name( |
206
|
|
|
(string) $namespace |
207
|
2 |
|
->add((string) $components->get('name')) |
208
|
2 |
|
->join('.') |
209
|
|
|
), |
210
|
2 |
|
$this->constructors->load($components->get('constructor')->trim(' ')), //space and non breaking space |
211
|
2 |
|
...$arguments |
212
|
|
|
); |
213
|
|
|
} |
214
|
|
|
|
215
|
2 |
|
private function registerStack(Stream $namespace, Str $key, array $stack): void |
216
|
|
|
{ |
217
|
|
|
$name = (string) $namespace |
218
|
2 |
|
->add((string) $key->capture(self::STACK_NAME)->get('name')) |
219
|
2 |
|
->join('.'); |
220
|
|
|
|
221
|
2 |
|
$this->stacks = $this->stacks->put( |
222
|
2 |
|
new Name($name), |
223
|
2 |
|
Sequence::of(...$stack)->map(static function(string $name): Name { |
224
|
2 |
|
return new Name( |
225
|
2 |
|
(string) Str::of($name)->substring(1) //remove the $ sign |
226
|
|
|
); |
227
|
2 |
|
}) |
228
|
|
|
); |
229
|
2 |
|
} |
230
|
|
|
|
231
|
2 |
|
private function buildStacks(Definitions $definitions): Definitions |
232
|
|
|
{ |
233
|
2 |
|
return $this->stacks->reduce( |
234
|
2 |
|
$definitions, |
235
|
2 |
|
function(Definitions $definitions, Name $name, Sequence $stack): Definitions { |
236
|
2 |
|
return $definitions->stack($name, ...$stack); |
|
|
|
|
237
|
2 |
|
} |
238
|
|
|
); |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
|