SchemaFactory   B
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 256
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 0
Metric Value
wmc 37
lcom 1
cbo 16
dl 0
loc 256
c 0
b 0
f 0
rs 7.2769

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A setCacheDriver() 0 4 1
B createSchema() 0 21 5
A getSchemaContainer() 0 11 3
A loadSchemaContainer() 0 17 3
A defineInteracesChildren() 0 13 4
B createInterface() 0 23 5
B createType() 0 21 5
A resolveInterfaces() 0 6 2
B prepareFields() 0 22 4
A prepareResolver() 0 13 3
A addResolver() 0 4 1
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) {
0 ignored issues
show
Bug introduced by
The expression $type->getInterfaces() cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$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 in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
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