Completed
Pull Request — master (#13)
by Rafael
06:46
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 0
Metric Value
dl 0
loc 3
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 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\Util\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
                $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
                    $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
                    $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();
259
            /** @var NodeBuilder $root */
260 1
            $root = $treeBuilder->root($plugin->getName());
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