|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* This file is part of Railt package. |
|
4
|
|
|
* |
|
5
|
|
|
* For the full copyright and license information, please view the LICENSE |
|
6
|
|
|
* file that was distributed with this source code. |
|
7
|
|
|
*/ |
|
8
|
|
|
declare(strict_types=1); |
|
9
|
|
|
|
|
10
|
|
|
namespace Railt\SDL; |
|
11
|
|
|
|
|
12
|
|
|
use Railt\Io\Readable; |
|
13
|
|
|
use Railt\Parser\ParserInterface; |
|
14
|
|
|
use Railt\SDL\Contracts\Definitions\Definition; |
|
15
|
|
|
use Railt\SDL\Contracts\Definitions\TypeDefinition; |
|
16
|
|
|
use Railt\SDL\Contracts\Document; |
|
17
|
|
|
use Railt\SDL\Exceptions\CompilerException; |
|
18
|
|
|
use Railt\SDL\Parser\Parser; |
|
19
|
|
|
use Railt\SDL\Reflection\Builder\DocumentBuilder; |
|
20
|
|
|
use Railt\SDL\Reflection\Builder\Process\Compilable; |
|
21
|
|
|
use Railt\SDL\Reflection\Coercion\Factory; |
|
22
|
|
|
use Railt\SDL\Reflection\Coercion\TypeCoercion; |
|
23
|
|
|
use Railt\SDL\Reflection\Dictionary; |
|
24
|
|
|
use Railt\SDL\Reflection\Loader; |
|
25
|
|
|
use Railt\SDL\Reflection\Validation\Base\ValidatorInterface; |
|
26
|
|
|
use Railt\SDL\Reflection\Validation\Definitions; |
|
27
|
|
|
use Railt\SDL\Reflection\Validation\Validator; |
|
28
|
|
|
use Railt\SDL\Runtime\CallStack; |
|
29
|
|
|
use Railt\SDL\Runtime\CallStackInterface; |
|
30
|
|
|
use Railt\SDL\Schema\CompilerInterface; |
|
31
|
|
|
use Railt\SDL\Schema\Configuration; |
|
32
|
|
|
use Railt\SDL\Standard\GraphQLDocument; |
|
33
|
|
|
use Railt\SDL\Standard\StandardType; |
|
34
|
|
|
use Railt\Storage\Drivers\ArrayStorage; |
|
35
|
|
|
use Railt\Storage\Proxy; |
|
36
|
|
|
use Railt\Storage\Storage; |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* Class Compiler |
|
40
|
|
|
*/ |
|
41
|
|
|
class Compiler implements CompilerInterface, Configuration |
|
42
|
|
|
{ |
|
43
|
|
|
use Support; |
|
44
|
|
|
|
|
45
|
|
|
/** |
|
46
|
|
|
* @var Dictionary |
|
47
|
|
|
*/ |
|
48
|
|
|
private $loader; |
|
49
|
|
|
|
|
50
|
|
|
/** |
|
51
|
|
|
* @var ParserInterface |
|
52
|
|
|
*/ |
|
53
|
|
|
private $parser; |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* @var Storage|ArrayStorage |
|
57
|
|
|
*/ |
|
58
|
|
|
private $storage; |
|
59
|
|
|
|
|
60
|
|
|
/** |
|
61
|
|
|
* @var Validator |
|
62
|
|
|
*/ |
|
63
|
|
|
private $typeValidator; |
|
64
|
|
|
|
|
65
|
|
|
/** |
|
66
|
|
|
* @var Factory|TypeCoercion |
|
67
|
|
|
*/ |
|
68
|
|
|
private $typeCoercion; |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* @var CallStack |
|
72
|
|
|
*/ |
|
73
|
|
|
private $stack; |
|
74
|
|
|
|
|
75
|
|
|
/** |
|
76
|
|
|
* Compiler constructor. |
|
77
|
|
|
* @param Storage|null $storage |
|
78
|
|
|
* @throws CompilerException |
|
79
|
|
|
* @throws Exceptions\TypeConflictException |
|
80
|
|
|
*/ |
|
81
|
283 |
|
public function __construct(Storage $storage = null) |
|
82
|
|
|
{ |
|
83
|
283 |
|
$this->parser = new Parser(); |
|
84
|
283 |
|
$this->stack = new CallStack(); |
|
85
|
283 |
|
$this->loader = new Loader($this, $this->stack); |
|
86
|
283 |
|
$this->typeValidator = new Validator($this->stack); |
|
87
|
283 |
|
$this->typeCoercion = new Factory(); |
|
88
|
|
|
|
|
89
|
283 |
|
$this->storage = $this->bootStorage($storage); |
|
90
|
|
|
|
|
91
|
283 |
|
$this->add($this->getStandardLibrary()); |
|
92
|
283 |
|
} |
|
93
|
|
|
|
|
94
|
|
|
/** |
|
95
|
|
|
* @param Document $document |
|
96
|
|
|
* @return CompilerInterface |
|
97
|
|
|
* @throws \Railt\SDL\Exceptions\CompilerException |
|
98
|
|
|
* @throws Exceptions\TypeConflictException |
|
99
|
|
|
*/ |
|
100
|
283 |
|
public function add(Document $document): CompilerInterface |
|
101
|
|
|
{ |
|
102
|
|
|
try { |
|
103
|
283 |
|
$this->complete($document); |
|
104
|
|
|
} catch (\OutOfBoundsException $fatal) { |
|
105
|
|
|
throw CompilerException::wrap($fatal); |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
283 |
|
return $this; |
|
|
|
|
|
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
/** |
|
112
|
|
|
* @param null|Storage $storage |
|
113
|
|
|
* @return Storage |
|
114
|
|
|
*/ |
|
115
|
283 |
|
private function bootStorage(?Storage $storage): Storage |
|
116
|
|
|
{ |
|
117
|
283 |
|
if ($storage === null) { |
|
118
|
283 |
|
return new ArrayStorage(); |
|
119
|
|
|
} |
|
120
|
|
|
|
|
121
|
283 |
|
if ($storage instanceof Proxy || $storage instanceof ArrayStorage) { |
|
122
|
283 |
|
return $storage; |
|
123
|
|
|
} |
|
124
|
|
|
|
|
125
|
283 |
|
return new Proxy(new ArrayStorage(), $storage); |
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
/** |
|
129
|
|
|
* @param array $extensions |
|
130
|
|
|
* @return GraphQLDocument |
|
131
|
|
|
*/ |
|
132
|
283 |
|
private function getStandardLibrary(array $extensions = []): GraphQLDocument |
|
133
|
|
|
{ |
|
134
|
283 |
|
return new GraphQLDocument($this->getDictionary(), $extensions); |
|
135
|
|
|
} |
|
136
|
|
|
|
|
137
|
|
|
/** |
|
138
|
|
|
* @param Document $document |
|
139
|
|
|
* @return Document |
|
140
|
|
|
* @throws Exceptions\TypeConflictException |
|
141
|
|
|
*/ |
|
142
|
6577 |
|
private function complete(Document $document): Document |
|
143
|
|
|
{ |
|
144
|
6577 |
|
$this->load($document); |
|
145
|
|
|
|
|
146
|
6577 |
|
$build = function (Definition $definition): void { |
|
147
|
6577 |
|
$this->stack->push($definition); |
|
148
|
|
|
|
|
149
|
6577 |
|
if ($definition instanceof Compilable) { |
|
150
|
6576 |
|
$definition->compile(); |
|
151
|
|
|
} |
|
152
|
|
|
|
|
153
|
6571 |
|
if ($definition instanceof TypeDefinition) { |
|
154
|
6571 |
|
$this->typeCoercion->apply($definition); |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
6571 |
|
if (! ($definition instanceof StandardType)) { |
|
158
|
6567 |
|
$this->typeValidator->group(Definitions::class)->validate($definition); |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
6547 |
|
$this->stack->pop(); |
|
162
|
6577 |
|
}; |
|
163
|
|
|
|
|
164
|
6577 |
|
foreach ($document->getDefinitions() as $definition) { |
|
165
|
6577 |
|
$build($definition); |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
4327 |
|
if ($document instanceof DocumentBuilder) { |
|
169
|
4173 |
|
foreach ($document->getInvocableTypes() as $definition) { |
|
170
|
1170 |
|
$build($definition); |
|
171
|
|
|
} |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
3439 |
|
return $document; |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
|
|
/** |
|
178
|
|
|
* @param Document $document |
|
179
|
|
|
* @return Document|DocumentBuilder |
|
180
|
|
|
* @throws Exceptions\TypeConflictException |
|
181
|
|
|
*/ |
|
182
|
6577 |
|
private function load(Document $document): Document |
|
183
|
|
|
{ |
|
184
|
6577 |
|
foreach ($document->getTypeDefinitions() as $type) { |
|
185
|
6577 |
|
$this->stack->push($type); |
|
186
|
6577 |
|
$this->loader->register($type); |
|
187
|
6577 |
|
$this->stack->pop(); |
|
188
|
|
|
} |
|
189
|
|
|
|
|
190
|
6577 |
|
return $document; |
|
191
|
|
|
} |
|
192
|
|
|
|
|
193
|
|
|
/** |
|
194
|
|
|
* @param \Closure $then |
|
195
|
|
|
* @return CompilerInterface |
|
196
|
|
|
*/ |
|
197
|
6 |
|
public function autoload(\Closure $then): CompilerInterface |
|
198
|
|
|
{ |
|
199
|
6 |
|
$this->loader->autoload($then); |
|
|
|
|
|
|
200
|
|
|
|
|
201
|
6 |
|
return $this; |
|
|
|
|
|
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
|
/** |
|
205
|
|
|
* @param Readable $readable |
|
206
|
|
|
* @return Document |
|
207
|
|
|
*/ |
|
208
|
6576 |
|
public function compile(Readable $readable): Document |
|
209
|
|
|
{ |
|
210
|
|
|
/** @var DocumentBuilder $document */ |
|
211
|
6576 |
|
$document = $this->storage->remember($readable, $this->onCompile()); |
|
212
|
|
|
|
|
213
|
3285 |
|
return $document->withCompiler($this); |
|
214
|
|
|
} |
|
215
|
|
|
|
|
216
|
|
|
/** |
|
217
|
|
|
* @return \Closure |
|
218
|
|
|
*/ |
|
219
|
|
|
private function onCompile(): \Closure |
|
220
|
|
|
{ |
|
221
|
6576 |
|
return function (Readable $readable): Document { |
|
222
|
6576 |
|
$ast = $this->parser->parse($readable); |
|
223
|
|
|
|
|
224
|
6576 |
|
return $this->complete(new DocumentBuilder($ast, $readable, $this)); |
|
225
|
6576 |
|
}; |
|
226
|
|
|
} |
|
227
|
|
|
|
|
228
|
|
|
/** |
|
229
|
|
|
* @param string $group |
|
230
|
|
|
* @return ValidatorInterface |
|
231
|
|
|
* @throws \OutOfBoundsException |
|
232
|
|
|
*/ |
|
233
|
6576 |
|
public function getValidator(string $group): ValidatorInterface |
|
234
|
|
|
{ |
|
235
|
6576 |
|
return $this->typeValidator->group($group); |
|
236
|
|
|
} |
|
237
|
|
|
|
|
238
|
|
|
/** |
|
239
|
|
|
* @return ParserInterface |
|
240
|
|
|
*/ |
|
241
|
|
|
public function getParser(): ParserInterface |
|
242
|
|
|
{ |
|
243
|
|
|
return $this->parser; |
|
244
|
|
|
} |
|
245
|
|
|
|
|
246
|
|
|
/** |
|
247
|
|
|
* @return TypeCoercion |
|
248
|
|
|
*/ |
|
249
|
5975 |
|
public function getTypeCoercion(): TypeCoercion |
|
250
|
|
|
{ |
|
251
|
5975 |
|
return $this->typeCoercion; |
|
252
|
|
|
} |
|
253
|
|
|
|
|
254
|
|
|
/** |
|
255
|
|
|
* @return Storage |
|
256
|
|
|
*/ |
|
257
|
|
|
public function getStorage(): Storage |
|
258
|
|
|
{ |
|
259
|
|
|
return $this->storage; |
|
260
|
|
|
} |
|
261
|
|
|
|
|
262
|
|
|
/** |
|
263
|
|
|
* @return Dictionary |
|
264
|
|
|
*/ |
|
265
|
6565 |
|
public function getDictionary(): Dictionary |
|
266
|
|
|
{ |
|
267
|
6565 |
|
return $this->loader; |
|
268
|
|
|
} |
|
269
|
|
|
|
|
270
|
|
|
/** |
|
271
|
|
|
* @return CallStackInterface |
|
272
|
|
|
*/ |
|
273
|
5975 |
|
public function getCallStack(): CallStackInterface |
|
274
|
|
|
{ |
|
275
|
5975 |
|
return $this->stack; |
|
276
|
|
|
} |
|
277
|
|
|
} |
|
278
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.