Passed
Push — master ( 2b3765...b8576e )
by Rafael
05:42
created

DefinitionRegistry::compile()   C

Complexity

Conditions 12
Paths 136

Size

Total Lines 42
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 12.3654

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 21
c 1
b 1
f 0
dl 0
loc 42
ccs 19
cts 22
cp 0.8636
rs 6.6666
cc 12
nc 136
nop 1
crap 12.3654

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*******************************************************************************
3
 *  This file is part of the GraphQL Bundle package.
4
 *
5
 *  (c) YnloUltratech <[email protected]>
6
 *
7
 *  For the full copyright and license information, please view the LICENSE
8
 *  file that was distributed with this source code.
9
 ******************************************************************************/
10
11
namespace Ynlo\GraphQLBundle\Definition\Registry;
12
13
use Doctrine\Common\Inflector\Inflector;
14
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
15
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
16
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
17
use Symfony\Component\Config\Definition\Processor;
18
use Ynlo\GraphQLBundle\Definition\DefinitionInterface;
19
use Ynlo\GraphQLBundle\Definition\FieldsAwareDefinitionInterface;
20
use Ynlo\GraphQLBundle\Definition\Loader\DefinitionLoaderInterface;
21
use Ynlo\GraphQLBundle\Definition\MetaAwareInterface;
22
use Ynlo\GraphQLBundle\Definition\Plugin\DefinitionPluginInterface;
23
use Ynlo\GraphQLBundle\Extension\EndpointNotValidException;
24
25
/**
26
 * DefinitionRegistry
27
 */
28
class DefinitionRegistry
29
{
30
    /**
31
     * This endpoint is used as default endpoint
32
     */
33
    public const DEFAULT_ENDPOINT = 'default';
34
35
    /**
36
     * @var iterable|DefinitionLoaderInterface[]
37
     */
38
    private $loaders;
39
40
    /**
41
     * @var iterable|DefinitionPluginInterface[]
42
     */
43
    private $plugins;
44
45
    /**
46
     * @var array|Endpoint[]
47
     */
48
    private static $endpoints = [];
49
50
    /**
51
     * @var array
52
     */
53
    protected $endpointsConfig = [];
54
55
    /**
56
     * @var string
57
     */
58
    private $cacheDir;
59
60
    /**
61
     * DefinitionRegistry constructor.
62
     *
63
     * Configured endpoints array should have the following format:
64
     *
65
     * [
66
     * 'endpoints' => [
67
     *   'name' => [
68
     *      'roles'=> [],
69
     *      'host' => '',
70
     *      'path' => ''
71
     *    ]
72
     *  ]
73
     * ]
74
     *
75
     * @param iterable|DefinitionLoaderInterface[] $loaders
76
     * @param iterable|DefinitionPluginInterface[] $plugins
77
     * @param null|string                          $cacheDir
78
     * @param array                                $endpointsConfig array of configured endpoints
79
     *
80
     */
81 4
    public function __construct(iterable $loaders = [], iterable $plugins = [], ?string $cacheDir = null, array $endpointsConfig = [])
82
    {
83 4
        $this->loaders = $loaders;
84 4
        $this->plugins = $plugins;
85 4
        $this->cacheDir = $cacheDir ?? sys_get_temp_dir();
86 4
        $this->endpointsConfig = array_merge($endpointsConfig['endpoints'] ?? [], [self::DEFAULT_ENDPOINT => []]);
87 4
    }
88
89
    /**
90
     * Get endpoint schema
91
     *
92
     * For internal use can get the default endpoint (contains all definitions)
93
     * For consumers must get the endpoint for current consumer.
94
     *
95
     * @param string $name
96
     *
97
     * @return Endpoint
98
     *
99
     * @throws EndpointNotValidException
100
     */
101 4
    public function getEndpoint($name = self::DEFAULT_ENDPOINT): Endpoint
102
    {
103 4
        $endpoints = $this->endpointsConfig;
104 4
        unset($endpoints[self::DEFAULT_ENDPOINT]);
105 4
        $endpointsNames = array_keys($endpoints);
106 4
        if (self::DEFAULT_ENDPOINT !== $name && !\in_array($name, $endpointsNames)) {
107 1
            throw new EndpointNotValidException($name, $endpointsNames);
108
        }
109
110
        //use first static cache
111 3
        if (isset(self::$endpoints[$name])) {
112 1
            return self::$endpoints[$name];
113
        }
114
115
        //use file cache
116 2
        self::$endpoints[$name] = $this->loadCache($name);
117
118
        //retry after load from file
119 2
        if (isset(self::$endpoints[$name]) && self::$endpoints[$name] instanceof Endpoint) {
120 1
            return self::$endpoints[$name];
121
        }
122
123 1
        $this->initialize($name);
124
125 1
        return self::$endpoints[$name];
126
    }
127
128
    /**
129
     * remove the specification cache
130
     *
131
     * @param bool $warmUp recreate the cache after clear
132
     */
133 1
    public function clearCache($warmUp = false)
134
    {
135 1
        if (file_exists($this->cacheFileName('default.raw')) && is_writable($this->cacheFileName('default.raw'))) {
136
            unlink($this->cacheFileName('default.raw'));
137
        }
138
139 1
        foreach ($this->endpointsConfig as $name => $config) {
140 1
            unset(self::$endpoints[$name]);
141 1
            if (file_exists($this->cacheFileName($name)) && is_writable($this->cacheFileName($name))) {
142
                unlink($this->cacheFileName($name));
143
            }
144 1
            if ($warmUp) {
145 1
                $this->initialize($name);
146
            }
147
        }
148 1
    }
149
150
    /**
151
     * Initialize endpoint
152
     *
153
     * @param string $name
154
     */
155 1
    protected function initialize(string $name): void
156
    {
157 1
        $rawDefault = $this->loadCache('default.raw');
158 1
        if (!$rawDefault) {
159 1
            $rawDefault = new Endpoint(self::DEFAULT_ENDPOINT);
160
161 1
            foreach ($this->loaders as $loader) {
162 1
                $loader->loadDefinitions($rawDefault);
163
            }
164
165 1
            $this->saveCache('default.raw', $rawDefault);
166
        }
167
168 1
        if (!isset(self::$endpoints[$name])) {
169
            //copy from raw to specific endpoint
170 1
            self::$endpoints[$name] = new Endpoint($name);
171 1
            self::$endpoints[$name]->setTypes($rawDefault->allTypes());
172 1
            self::$endpoints[$name]->setMutations($rawDefault->allMutations());
173 1
            self::$endpoints[$name]->setQueries($rawDefault->allQueries());
174 1
            self::$endpoints[$name]->setSubscriptions($rawDefault->allSubscriptions());
175
176 1
            $this->compile(self::$endpoints[$name]);
177
        }
178
179 1
        $this->saveCache($name, self::$endpoints[$name]);
180 1
    }
181
182 2
    protected function cacheFileName($name): string
183
    {
184 2
        return sprintf('%s%sgraphql.registry_definitions_%s.meta', $this->cacheDir, DIRECTORY_SEPARATOR, Inflector::tableize($name));
185
    }
186
187 2
    protected function loadCache($name): ?Endpoint
188
    {
189 2
        if (file_exists($this->cacheFileName($name))) {
190 1
            $content = @file_get_contents($this->cacheFileName($name));
191 1
            if ($content) {
192 1
                return unserialize($content, ['allowed_classes' => true]);
193
            }
194
        }
195
196 1
        return null;
197
    }
198
199 1
    protected function saveCache($name, Endpoint $endpoint): void
200
    {
201 1
        file_put_contents($this->cacheFileName($name), serialize($endpoint));
202 1
    }
203
204
    /**
205
     * Verify endpoint definitions and do some tasks to prepare the endpoint
206
     *
207
     * @param Endpoint $endpoint
208
     */
209 1
    protected function compile(Endpoint $endpoint): void
210
    {
211
        //run all extensions for each definition
212 1
        foreach ($this->plugins as $plugin) {
213
            //run extensions recursively in all types and fields
214 1
            foreach ($endpoint->allTypes() as $type) {
215 1
                $this->configureDefinition($plugin, $type, $endpoint);
216 1
                if ($type instanceof FieldsAwareDefinitionInterface) {
217 1
                    foreach ($type->getFields() as $field) {
218 1
                        $this->configureDefinition($plugin, $field, $endpoint);
219 1
                        foreach ($field->getArguments() as $argument) {
220 1
                            $this->configureDefinition($plugin, $argument, $endpoint);
221
                        }
222
                    }
223
                }
224
            }
225
226
            //run extension in all queries
227 1
            foreach ($endpoint->allQueries() as $query) {
228 1
                $this->configureDefinition($plugin, $query, $endpoint);
229 1
                foreach ($query->getArguments() as $argument) {
230 1
                    $this->configureDefinition($plugin, $argument, $endpoint);
231
                }
232
            }
233
234
            //run extensions in all mutations
235 1
            foreach ($endpoint->allMutations() as $mutation) {
236 1
                $this->configureDefinition($plugin, $mutation, $endpoint);
237 1
                foreach ($mutation->getArguments() as $argument) {
238 1
                    $this->configureDefinition($plugin, $argument, $endpoint);
239
                }
240
            }
241
242
            //run extensions in all subscriptions
243 1
            foreach ($endpoint->allSubscriptions() as $subscription) {
244
                $this->configureDefinition($plugin, $subscription, $endpoint);
245
                foreach ($subscription->getArguments() as $argument) {
246
                    $this->configureDefinition($plugin, $argument, $endpoint);
247
                }
248
            }
249
250 1
            $plugin->configureEndpoint($endpoint);
251
        }
252 1
    }
253
254
    /**
255
     * @param DefinitionPluginInterface $plugin
256
     * @param DefinitionInterface       $definition
257
     * @param Endpoint                  $endpoint
258
     */
259 1
    protected function configureDefinition(DefinitionPluginInterface $plugin, DefinitionInterface $definition, Endpoint $endpoint)
260
    {
261 1
        $config = [];
262 1
        if ($definition instanceof MetaAwareInterface) {
0 ignored issues
show
introduced by
$definition is always a sub-type of Ynlo\GraphQLBundle\Definition\MetaAwareInterface.
Loading history...
263 1
            $treeBuilder = new TreeBuilder($plugin->getName());
264 1
            $root = $treeBuilder->getRootNode();
265 1
            $plugin->buildConfig($root);
266
267 1
            if ($definition->hasMeta($plugin->getName())) {
268 1
                $options = $definition->getMeta($plugin->getName());
269 1
                $processor = new Processor();
270
271
                try {
272 1
                    $options = $plugin->normalizeConfig($definition, $options);
273 1
                    $config = $processor->process($treeBuilder->buildTree(), [$options]);
274
                } catch (InvalidConfigurationException $exception) {
275
                    $error = sprintf('Error compiling schema definition "%s", %s', $definition->getName(), $exception->getMessage());
276
                    throw new \RuntimeException($error, 0, $exception);
277
                }
278
            }
279
        }
280 1
        $plugin->configure($definition, $endpoint, $config);
281 1
    }
282
}
283