Completed
Push — master ( 895aee...c5d013 )
by Arthur
8s
created

SchemaFactory::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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