Completed
Push — master ( b42d81...2af3c3 )
by Rafael
03:45
created

DefinitionRegistry::cacheFileName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
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
        @unlink($this->cacheFileName('default.raw'));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

135
        /** @scrutinizer ignore-unhandled */ @unlink($this->cacheFileName('default.raw'));

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
136 1
        foreach ($this->endpointsConfig as $name => $config) {
137 1
            unset(self::$endpoints[$name]);
138 1
            @unlink($this->cacheFileName($name));
139 1
            if ($warmUp) {
140 1
                $this->initialize($name);
141
            }
142
        }
143 1
    }
144
145
    /**
146
     * Initialize endpoint
147
     *
148
     * @param string $name
149
     */
150 1
    protected function initialize(string $name): void
151
    {
152 1
        $rawDefault = $this->loadCache('default.raw');
153 1
        if (!$rawDefault) {
154 1
            $rawDefault = new Endpoint(self::DEFAULT_ENDPOINT);
155
156 1
            foreach ($this->loaders as $loader) {
157 1
                $loader->loadDefinitions($rawDefault);
158
            }
159
160 1
            $this->saveCache('default.raw', $rawDefault);
161
        }
162
163 1
        if (!isset(self::$endpoints[$name])) {
164
            //copy from raw to specific endpoint
165 1
            self::$endpoints[$name] = new Endpoint($name);
166 1
            self::$endpoints[$name]->setTypes($rawDefault->allTypes());
167 1
            self::$endpoints[$name]->setMutations($rawDefault->allMutations());
168 1
            self::$endpoints[$name]->setQueries($rawDefault->allQueries());
169 1
            self::$endpoints[$name]->setSubscriptions($rawDefault->allSubscriptions());
170
171 1
            $this->compile(self::$endpoints[$name]);
172
        }
173
174 1
        $this->saveCache($name, self::$endpoints[$name]);
175 1
    }
176
177 2
    protected function cacheFileName($name): string
178
    {
179 2
        return sprintf('%s%sgraphql.registry_definitions_%s.meta', $this->cacheDir, DIRECTORY_SEPARATOR, Inflector::tableize($name));
180
    }
181
182 2
    protected function loadCache($name): ?Endpoint
183
    {
184 2
        if (file_exists($this->cacheFileName($name))) {
185 1
            $content = @file_get_contents($this->cacheFileName($name));
186 1
            if ($content) {
187 1
                return unserialize($content, ['allowed_classes' => true]);
188
            }
189
        }
190
191 1
        return null;
192
    }
193
194 1
    protected function saveCache($name, Endpoint $endpoint): void
195
    {
196 1
        file_put_contents($this->cacheFileName($name), serialize($endpoint));
197 1
    }
198
199
    /**
200
     * Verify endpoint definitions and do some tasks to prepare the endpoint
201
     *
202
     * @param Endpoint $endpoint
203
     */
204 1
    protected function compile(Endpoint $endpoint): void
205
    {
206
        //run all extensions for each definition
207 1
        foreach ($this->plugins as $plugin) {
208
            //run extensions recursively in all types and fields
209 1
            foreach ($endpoint->allTypes() as $type) {
210 1
                $this->configureDefinition($plugin, $type, $endpoint);
211 1
                if ($type instanceof FieldsAwareDefinitionInterface) {
212 1
                    foreach ($type->getFields() as $field) {
213 1
                        $this->configureDefinition($plugin, $field, $endpoint);
214 1
                        foreach ($field->getArguments() as $argument) {
215 1
                            $this->configureDefinition($plugin, $argument, $endpoint);
216
                        }
217
                    }
218
                }
219
            }
220
221
            //run extension in all queries
222 1
            foreach ($endpoint->allQueries() as $query) {
223 1
                $this->configureDefinition($plugin, $query, $endpoint);
224 1
                foreach ($query->getArguments() as $argument) {
225 1
                    $this->configureDefinition($plugin, $argument, $endpoint);
226
                }
227
            }
228
229
            //run extensions in all mutations
230 1
            foreach ($endpoint->allMutations() as $mutation) {
231 1
                $this->configureDefinition($plugin, $mutation, $endpoint);
232 1
                foreach ($mutation->getArguments() as $argument) {
233 1
                    $this->configureDefinition($plugin, $argument, $endpoint);
234
                }
235
            }
236
237
            //run extensions in all subscriptions
238 1
            foreach ($endpoint->allSubscriptions() as $subscription) {
239
                $this->configureDefinition($plugin, $subscription, $endpoint);
240
                foreach ($subscription->getArguments() as $argument) {
241
                    $this->configureDefinition($plugin, $argument, $endpoint);
242
                }
243
            }
244
245 1
            $plugin->configureEndpoint($endpoint);
246
        }
247 1
    }
248
249
    /**
250
     * @param DefinitionPluginInterface $plugin
251
     * @param DefinitionInterface       $definition
252
     * @param Endpoint                  $endpoint
253
     */
254 1
    protected function configureDefinition(DefinitionPluginInterface $plugin, DefinitionInterface $definition, Endpoint $endpoint)
255
    {
256 1
        $config = [];
257 1
        if ($definition instanceof MetaAwareInterface) {
0 ignored issues
show
introduced by
$definition is always a sub-type of Ynlo\GraphQLBundle\Definition\MetaAwareInterface.
Loading history...
258 1
            $treeBuilder = new TreeBuilder($plugin->getName());
259
            /** @var NodeBuilder $root */
260 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

260
            $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...
261 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

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