Completed
Push — master ( 2af3c3...c29116 )
by Rafael
04:17
created

DefinitionRegistry::clearCache()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 8.8142

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 8
c 2
b 0
f 0
dl 0
loc 13
ccs 6
cts 9
cp 0.6667
rs 8.8333
cc 7
nc 10
nop 1
crap 8.8142
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
                $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
                    $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
                    $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
            /** @var NodeBuilder $root */
265 1
            $root = $treeBuilder->root($plugin->getName());
0 ignored issues
show
Deprecated Code introduced by
The function Symfony\Component\Config...der\TreeBuilder::root() has been deprecated: since Symfony 4.3, pass the root name to the constructor instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

265
            $root = /** @scrutinizer ignore-deprecated */ $treeBuilder->root($plugin->getName());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
266 1
            $plugin->buildConfig($root);
0 ignored issues
show
Bug introduced by
$root of type Symfony\Component\Config...ion\Builder\NodeBuilder is incompatible with the type Symfony\Component\Config...der\ArrayNodeDefinition expected by parameter $root of Ynlo\GraphQLBundle\Defin...nterface::buildConfig(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

266
            $plugin->buildConfig(/** @scrutinizer ignore-type */ $root);
Loading history...
267
268 1
            if ($definition->hasMeta($plugin->getName())) {
269 1
                $options = $definition->getMeta($plugin->getName());
270 1
                $processor = new Processor();
271
272
                try {
273 1
                    $options = $plugin->normalizeConfig($definition, $options);
274 1
                    $config = $processor->process($treeBuilder->buildTree(), [$options]);
275
                } catch (InvalidConfigurationException $exception) {
276
                    $error = sprintf('Error compiling schema definition "%s", %s', $definition->getName(), $exception->getMessage());
277
                    throw new \RuntimeException($error, 0, $exception);
278
                }
279
            }
280
        }
281 1
        $plugin->configure($definition, $endpoint, $config);
282 1
    }
283
}
284