|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Arthem\GraphQLMapper\Schema; |
|
4
|
|
|
|
|
5
|
|
|
use Arthem\GraphQLMapper\Mapping\Cache\CacheDriverInterface; |
|
6
|
|
|
use Arthem\GraphQLMapper\Mapping\Driver\DriverInterface; |
|
7
|
|
|
use Arthem\GraphQLMapper\Mapping\Field; |
|
8
|
|
|
use Arthem\GraphQLMapper\Mapping\FieldContainer; |
|
9
|
|
|
use Arthem\GraphQLMapper\Mapping\Guess\MappingGuesserManager; |
|
10
|
|
|
use Arthem\GraphQLMapper\Mapping\InterfaceType; |
|
11
|
|
|
use Arthem\GraphQLMapper\Mapping\MappingNormalizer; |
|
12
|
|
|
use Arthem\GraphQLMapper\Mapping\SchemaContainer; |
|
13
|
|
|
use Arthem\GraphQLMapper\Schema\Resolve\CallableResolver; |
|
14
|
|
|
use Arthem\GraphQLMapper\Schema\Resolve\ResolverInterface; |
|
15
|
|
|
use GraphQL\Schema; |
|
16
|
|
|
use GraphQL\Type\Definition as GQLDefinition; |
|
17
|
|
|
|
|
18
|
|
|
class SchemaFactory |
|
19
|
|
|
{ |
|
20
|
|
|
/** |
|
21
|
|
|
* @var string |
|
22
|
|
|
*/ |
|
23
|
|
|
protected $cacheKey = 'Arthem:GraphQL:Mapping'; |
|
24
|
|
|
|
|
25
|
|
|
/** |
|
26
|
|
|
* @var CacheDriverInterface |
|
27
|
|
|
*/ |
|
28
|
|
|
private $cacheDriver; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* @var DriverInterface |
|
32
|
|
|
*/ |
|
33
|
|
|
private $driver; |
|
34
|
|
|
|
|
35
|
|
|
/** |
|
36
|
|
|
* @var TypeResolver |
|
37
|
|
|
*/ |
|
38
|
|
|
private $typeResolver; |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* @var ResolverInterface[] |
|
42
|
|
|
*/ |
|
43
|
|
|
private $resolveFactories = []; |
|
44
|
|
|
|
|
45
|
|
|
/** |
|
46
|
|
|
* @var MappingNormalizer |
|
47
|
|
|
*/ |
|
48
|
|
|
private $normalizer; |
|
49
|
|
|
|
|
50
|
|
|
/** |
|
51
|
|
|
* @var MappingGuesserManager |
|
52
|
|
|
*/ |
|
53
|
|
|
private $guesser; |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* @param DriverInterface $driver |
|
57
|
|
|
* @param TypeResolver $typeResolver |
|
58
|
|
|
* @param MappingGuesserManager|null $guesser |
|
59
|
|
|
*/ |
|
60
|
|
|
public function __construct( |
|
61
|
|
|
DriverInterface $driver, |
|
62
|
|
|
TypeResolver $typeResolver, |
|
63
|
|
|
MappingGuesserManager $guesser = null |
|
64
|
|
|
) { |
|
65
|
|
|
$this->driver = $driver; |
|
66
|
|
|
$this->typeResolver = $typeResolver; |
|
67
|
|
|
$this->guesser = $guesser; |
|
68
|
|
|
$this->normalizer = new MappingNormalizer(); |
|
69
|
|
|
$this->addResolver(new CallableResolver()); |
|
70
|
|
|
} |
|
71
|
|
|
|
|
72
|
|
|
/** |
|
73
|
|
|
* @param CacheDriverInterface $cacheDriver |
|
74
|
|
|
*/ |
|
75
|
|
|
public function setCacheDriver(CacheDriverInterface $cacheDriver = null) |
|
76
|
|
|
{ |
|
77
|
|
|
$this->cacheDriver = $cacheDriver; |
|
78
|
|
|
} |
|
79
|
|
|
|
|
80
|
|
|
/** |
|
81
|
|
|
* @return Schema |
|
82
|
|
|
*/ |
|
83
|
|
|
public function createSchema() |
|
84
|
|
|
{ |
|
85
|
|
|
$schemaContainer = $this->getSchemaContainer(); |
|
86
|
|
|
|
|
87
|
|
|
foreach ($schemaContainer->getInterfaces() as $type) { |
|
88
|
|
|
$GQLType = $this->createInterface($type); |
|
89
|
|
|
$this->typeResolver->addType($type->getName(), $GQLType); |
|
90
|
|
|
} |
|
91
|
|
|
|
|
92
|
|
|
foreach ($schemaContainer->getTypes() as $type) { |
|
93
|
|
|
$GQLType = $this->createType($type); |
|
94
|
|
|
$this->typeResolver->addType($type->getName(), $GQLType); |
|
95
|
|
|
} |
|
96
|
|
|
|
|
97
|
|
|
$querySchema = $schemaContainer->getQuerySchema(); |
|
98
|
|
|
$mutationType = $schemaContainer->getMutationSchema(); |
|
99
|
|
|
$queryType = null !== $querySchema ? $this->createType($querySchema) : null; |
|
100
|
|
|
$mutationType = null !== $mutationType ? $this->createType($mutationType) : null; |
|
101
|
|
|
|
|
102
|
|
|
return new Schema($queryType, $mutationType); |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
/** |
|
106
|
|
|
* @return SchemaContainer |
|
107
|
|
|
*/ |
|
108
|
|
|
private function getSchemaContainer() |
|
109
|
|
|
{ |
|
110
|
|
|
if (null !== $this->cacheDriver) { |
|
111
|
|
|
$schemaContainer = $this->cacheDriver->load(); |
|
112
|
|
|
if (false !== $schemaContainer) { |
|
113
|
|
|
return $schemaContainer; |
|
114
|
|
|
} |
|
115
|
|
|
} |
|
116
|
|
|
|
|
117
|
|
|
return $this->loadSchemaContainer(); |
|
118
|
|
|
} |
|
119
|
|
|
|
|
120
|
|
|
/** |
|
121
|
|
|
* @return SchemaContainer |
|
122
|
|
|
*/ |
|
123
|
|
|
private function loadSchemaContainer() |
|
124
|
|
|
{ |
|
125
|
|
|
$schemaContainer = new SchemaContainer(); |
|
126
|
|
|
$this->driver->load($schemaContainer); |
|
127
|
|
|
if (null !== $this->guesser) { |
|
128
|
|
|
$this->guesser->guess($schemaContainer); |
|
129
|
|
|
} |
|
130
|
|
|
$this->normalizer->normalize($schemaContainer); |
|
131
|
|
|
|
|
132
|
|
|
if (null !== $this->cacheDriver) { |
|
133
|
|
|
$this->cacheDriver->save($schemaContainer); |
|
134
|
|
|
} |
|
135
|
|
|
|
|
136
|
|
|
return $schemaContainer; |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
/** |
|
140
|
|
|
* @param InterfaceType $type |
|
141
|
|
|
* @return GQLDefinition\InterfaceType |
|
142
|
|
|
*/ |
|
143
|
|
|
private function createInterface(InterfaceType $type) |
|
144
|
|
|
{ |
|
145
|
|
|
if (null !== $type->getFields()) { |
|
146
|
|
|
$this->prepareFields($type->getFields()); |
|
147
|
|
|
} |
|
148
|
|
|
$type = new GQLDefinition\InterfaceType($type->toMapping()); |
|
149
|
|
|
|
|
150
|
|
|
return $type; |
|
151
|
|
|
} |
|
152
|
|
|
|
|
153
|
|
|
/** |
|
154
|
|
|
* @param FieldContainer $type |
|
155
|
|
|
* @return GQLDefinition\ObjectType |
|
156
|
|
|
*/ |
|
157
|
|
|
private function createType(FieldContainer $type) |
|
158
|
|
|
{ |
|
159
|
|
|
if (null !== $type->getFields()) { |
|
160
|
|
|
$this->prepareFields($type->getFields()); |
|
161
|
|
|
} |
|
162
|
|
|
|
|
163
|
|
|
switch ($type->getInternalType()) { |
|
164
|
|
|
case 'ObjectType': |
|
165
|
|
|
$type = new GQLDefinition\ObjectType($type->toMapping()); |
|
166
|
|
|
break; |
|
167
|
|
|
case 'EnumType': |
|
168
|
|
|
$type = new GQLDefinition\EnumType($type->toMapping()); |
|
169
|
|
|
break; |
|
170
|
|
|
} |
|
171
|
|
|
|
|
172
|
|
|
return $type; |
|
|
|
|
|
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* @param Field[] $fields |
|
177
|
|
|
*/ |
|
178
|
|
|
private function prepareFields(array $fields) |
|
179
|
|
|
{ |
|
180
|
|
|
foreach ($fields as $field) { |
|
181
|
|
|
|
|
182
|
|
|
if (null !== $field->getArguments()) { |
|
183
|
|
|
$this->prepareFields($field->getArguments()); |
|
184
|
|
|
} |
|
185
|
|
|
|
|
186
|
|
|
$resolveConfig = $field->getResolveConfig(); |
|
187
|
|
|
if (isset($resolveConfig['handler'])) { |
|
188
|
|
|
$handler = $resolveConfig['handler']; |
|
189
|
|
|
$resolver = $this->resolveFactories[$handler]->getFunction($resolveConfig, $field); |
|
190
|
|
|
$field->setResolve($resolver); |
|
191
|
|
|
} |
|
192
|
|
|
|
|
193
|
|
|
$typeName = $field->getType(); |
|
194
|
|
|
if (empty($typeName)) { |
|
195
|
|
|
throw new \InvalidArgumentException(sprintf('Missing type for field "%s"', $field->getName())); |
|
196
|
|
|
} |
|
197
|
|
|
$field->setResolvedType(function () use ($typeName) { |
|
198
|
|
|
return $this->typeResolver->resolveType($typeName); |
|
199
|
|
|
}); |
|
200
|
|
|
} |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
|
|
/** |
|
204
|
|
|
* @param ResolverInterface $factory |
|
205
|
|
|
*/ |
|
206
|
|
|
public function addResolver(ResolverInterface $factory) |
|
207
|
|
|
{ |
|
208
|
|
|
$this->resolveFactories[$factory->getName()] = $factory; |
|
209
|
|
|
} |
|
210
|
|
|
} |
|
211
|
|
|
|
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.