1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Arthem\GraphQLMapper\Schema; |
4
|
|
|
|
5
|
|
|
use Arthem\GraphQLMapper\Mapping\AbstractType; |
6
|
|
|
use Arthem\GraphQLMapper\Mapping\Cache\CacheDriverInterface; |
7
|
|
|
use Arthem\GraphQLMapper\Mapping\Driver\DriverInterface; |
8
|
|
|
use Arthem\GraphQLMapper\Mapping\Field; |
9
|
|
|
use Arthem\GraphQLMapper\Mapping\FieldContainer; |
10
|
|
|
use Arthem\GraphQLMapper\Mapping\Guesser\MappingGuesserManager; |
11
|
|
|
use Arthem\GraphQLMapper\Mapping\InterfaceType; |
12
|
|
|
use Arthem\GraphQLMapper\Mapping\MappingNormalizer; |
13
|
|
|
use Arthem\GraphQLMapper\Mapping\SchemaContainer; |
14
|
|
|
use Arthem\GraphQLMapper\Mapping\Type; |
15
|
|
|
use Arthem\GraphQLMapper\Schema\Resolve\CallableResolver; |
16
|
|
|
use Arthem\GraphQLMapper\Schema\Resolve\ResolverInterface; |
17
|
|
|
use GraphQL\Schema; |
18
|
|
|
use GraphQL\Type\Definition as GQLDefinition; |
19
|
|
|
|
20
|
|
|
class SchemaFactory |
21
|
|
|
{ |
22
|
|
|
/** |
23
|
|
|
* @var string |
24
|
|
|
*/ |
25
|
|
|
protected $cacheKey = 'Arthem:GraphQL:Mapping'; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var CacheDriverInterface |
29
|
|
|
*/ |
30
|
|
|
private $cacheDriver; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var DriverInterface |
34
|
|
|
*/ |
35
|
|
|
private $driver; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var TypeResolver |
39
|
|
|
*/ |
40
|
|
|
private $typeResolver; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var ResolverInterface[] |
44
|
|
|
*/ |
45
|
|
|
private $resolveFactories = []; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var MappingNormalizer |
49
|
|
|
*/ |
50
|
|
|
private $normalizer; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @var MappingGuesserManager |
54
|
|
|
*/ |
55
|
|
|
private $guesser; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @param DriverInterface $driver |
59
|
|
|
* @param TypeResolver $typeResolver |
60
|
|
|
* @param MappingGuesserManager|null $guesser |
61
|
|
|
*/ |
62
|
|
|
public function __construct( |
63
|
|
|
DriverInterface $driver, |
64
|
|
|
TypeResolver $typeResolver, |
65
|
|
|
MappingGuesserManager $guesser = null |
66
|
|
|
) { |
67
|
|
|
$this->driver = $driver; |
68
|
|
|
$this->typeResolver = $typeResolver; |
69
|
|
|
$this->guesser = $guesser; |
70
|
|
|
$this->normalizer = new MappingNormalizer(); |
71
|
|
|
$this->addResolver(new CallableResolver()); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* @param CacheDriverInterface $cacheDriver |
76
|
|
|
*/ |
77
|
|
|
public function setCacheDriver(CacheDriverInterface $cacheDriver = null) |
78
|
|
|
{ |
79
|
|
|
$this->cacheDriver = $cacheDriver; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* @return Schema |
84
|
|
|
*/ |
85
|
|
|
public function createSchema() |
86
|
|
|
{ |
87
|
|
|
$schemaContainer = $this->getSchemaContainer(); |
88
|
|
|
|
89
|
|
|
foreach ($schemaContainer->getInterfaces() as $interface) { |
90
|
|
|
$GQLType = $this->createInterface($interface); |
91
|
|
|
$this->typeResolver->addType($interface->getName(), $GQLType); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
foreach ($schemaContainer->getTypes() as $type) { |
95
|
|
|
$GQLType = $this->createType($type); |
96
|
|
|
$this->typeResolver->addType($type->getName(), $GQLType); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
$querySchema = $schemaContainer->getQuerySchema(); |
100
|
|
|
$mutationType = $schemaContainer->getMutationSchema(); |
101
|
|
|
$queryType = null !== $querySchema ? $this->createType($querySchema) : null; |
102
|
|
|
$mutationType = null !== $mutationType ? $this->createType($mutationType) : null; |
103
|
|
|
|
104
|
|
|
return new Schema($queryType, $mutationType); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* @return SchemaContainer |
109
|
|
|
*/ |
110
|
|
|
private function getSchemaContainer() |
111
|
|
|
{ |
112
|
|
|
if (null !== $this->cacheDriver) { |
113
|
|
|
$schemaContainer = $this->cacheDriver->load(); |
114
|
|
|
if (false !== $schemaContainer) { |
115
|
|
|
return $schemaContainer; |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
return $this->loadSchemaContainer(); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* @return SchemaContainer |
124
|
|
|
*/ |
125
|
|
|
private function loadSchemaContainer() |
126
|
|
|
{ |
127
|
|
|
$schemaContainer = new SchemaContainer(); |
128
|
|
|
$this->driver->load($schemaContainer); |
129
|
|
|
if (null !== $this->guesser) { |
130
|
|
|
$this->guesser->guess($schemaContainer); |
131
|
|
|
} |
132
|
|
|
$this->normalizer->normalize($schemaContainer); |
133
|
|
|
|
134
|
|
|
$this->defineInteracesChildren($schemaContainer); |
135
|
|
|
|
136
|
|
|
if (null !== $this->cacheDriver) { |
137
|
|
|
$this->cacheDriver->save($schemaContainer); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
return $schemaContainer; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* @param SchemaContainer $schemaContainer |
145
|
|
|
*/ |
146
|
|
|
private function defineInteracesChildren(SchemaContainer $schemaContainer) |
147
|
|
|
{ |
148
|
|
|
foreach ($schemaContainer->getTypes() as $type) { |
149
|
|
|
if (null === $type->getModel()) { |
150
|
|
|
continue; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
foreach ($type->getInterfaces() as $interfaceName) { |
154
|
|
|
$interface = $schemaContainer->getInterface($interfaceName); |
155
|
|
|
$interface->setChildClass($type->getName(), $type->getModel()); |
156
|
|
|
} |
157
|
|
|
} |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* @param InterfaceType $interface |
162
|
|
|
* @return GQLDefinition\InterfaceType |
163
|
|
|
*/ |
164
|
|
|
private function createInterface(InterfaceType $interface) |
165
|
|
|
{ |
166
|
|
|
if (null !== $interface->getFields()) { |
167
|
|
|
$this->prepareFields($interface->getFields(), $interface); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
$mapping = $interface->getChildrenClassMapping(); |
171
|
|
|
|
172
|
|
|
if (!empty($mapping)) { |
173
|
|
|
$resolveType = function ($object) use ($mapping) { |
174
|
|
|
foreach ($mapping as $class => $typeName) { |
175
|
|
|
if ($object instanceof $class) { |
176
|
|
|
return $this->typeResolver->getType($typeName); |
177
|
|
|
} |
178
|
|
|
} |
179
|
|
|
}; |
180
|
|
|
$interface->setResolveType($resolveType); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
$interface = new GQLDefinition\InterfaceType($interface->toMapping()); |
184
|
|
|
|
185
|
|
|
return $interface; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* @param FieldContainer $type |
190
|
|
|
* @return GQLDefinition\Type |
191
|
|
|
*/ |
192
|
|
|
private function createType(FieldContainer $type) |
193
|
|
|
{ |
194
|
|
|
if (null !== $type->getFields()) { |
195
|
|
|
$this->prepareFields($type->getFields(), $type); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
if ($type instanceof Type) { |
199
|
|
|
$this->resolveInterfaces($type); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
$internalType = $type->getInternalType(); |
203
|
|
|
|
204
|
|
|
switch ($internalType) { |
205
|
|
|
case 'ObjectType': |
206
|
|
|
return new GQLDefinition\ObjectType($type->toMapping()); |
207
|
|
|
case 'EnumType': |
208
|
|
|
return new GQLDefinition\EnumType($type->toMapping()); |
209
|
|
|
default: |
210
|
|
|
throw new \InvalidArgumentException(sprintf('Undefined internal type "%s"', $internalType)); |
211
|
|
|
} |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* @param Type $type |
216
|
|
|
*/ |
217
|
|
|
private function resolveInterfaces(Type $type) |
218
|
|
|
{ |
219
|
|
|
foreach ($type->getInterfaces() as &$interface) { |
|
|
|
|
220
|
|
|
$interface = $this->typeResolver->getType($interface); |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* @param Field[] $fields |
226
|
|
|
* @param AbstractType|FieldContainer[]|Field $parent |
227
|
|
|
*/ |
228
|
|
|
private function prepareFields(array $fields, AbstractType $parent) |
229
|
|
|
{ |
230
|
|
|
foreach ($fields as $field) { |
231
|
|
|
if (null !== $field->getArguments()) { |
232
|
|
|
$this->prepareFields($field->getArguments(), $field); |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
$this->prepareResolver($field); |
236
|
|
|
|
237
|
|
|
$typeName = $field->getType(); |
238
|
|
|
if (empty($typeName)) { |
239
|
|
|
throw new \InvalidArgumentException( |
240
|
|
|
sprintf('Missing type for field "%s" in "%s"', $field->getName(), $parent->getName()) |
241
|
|
|
); |
242
|
|
|
} |
243
|
|
|
$field->setResolvedType( |
244
|
|
|
function () use ($typeName) { |
245
|
|
|
return $this->typeResolver->resolveType($typeName); |
246
|
|
|
} |
247
|
|
|
); |
248
|
|
|
} |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/** |
252
|
|
|
* @param Field $field |
253
|
|
|
*/ |
254
|
|
|
private function prepareResolver(Field $field) |
255
|
|
|
{ |
256
|
|
|
$resolveConfig = $field->getResolveConfig(); |
257
|
|
|
if (isset($resolveConfig['handler'])) { |
258
|
|
|
|
259
|
|
|
$handler = $resolveConfig['handler']; |
260
|
|
|
if (!isset($this->resolveFactories[$handler])) { |
261
|
|
|
throw new \Exception(sprintf('Handle named "%s" does not exist', $resolveConfig['handler'])); |
262
|
|
|
} |
263
|
|
|
$resolver = $this->resolveFactories[$handler]->getFunction($resolveConfig, $field); |
264
|
|
|
$field->setResolve($resolver); |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* @param ResolverInterface $factory |
270
|
|
|
*/ |
271
|
|
|
public function addResolver(ResolverInterface $factory) |
272
|
|
|
{ |
273
|
|
|
$this->resolveFactories[$factory->getName()] = $factory; |
274
|
|
|
} |
275
|
|
|
} |
276
|
|
|
|
Let?s assume that you have the following
foreach
statement:$itemValue
is assigned by reference. This is possible because the expression (in the example$array
) can be used as a reference target.However, if we were to replace
$array
with something different like the result of a function call as inthen assigning by reference is not possible anymore as there is no target that could be modified.
Available Fixes
1. Do not assign by reference
2. Assign to a local variable first
3. Return a reference