Passed
Push — master ( 724c6f...9da6fd )
by Rafael
06:22
created

ControlledErrorsManager::loadFromCache()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 0
cts 7
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 3
nop 0
crap 12
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\Error;
12
13
use Symfony\Component\Finder\Finder;
14
use Symfony\Component\Finder\SplFileInfo;
15
use Symfony\Component\HttpKernel\Kernel;
16
use Ynlo\GraphQLBundle\Exception\ControlledErrorInterface;
17
18
class ControlledErrorsManager
19
{
20
    protected $kernel;
21
22
    /**
23
     * @var array
24
     */
25
    protected $config = [];
26
27
    /**
28
     * @var bool
29
     */
30
    protected $loaded = false;
31
32
    /**
33
     * @var array|MappedControlledError[]
34
     */
35
    protected $errors = [];
36
37
    /**
38
     * ControlledErrorsManager constructor.
39
     *
40
     * @param Kernel $kernel
41
     * @param array  $config
42
     */
43
    public function __construct(Kernel $kernel, $config = [])
44
    {
45
        $this->kernel = $kernel;
46
        $this->config = $config;
47
    }
48
49
    /**
50
     * @return array|MappedControlledError[]
51
     *
52
     * @throws \ReflectionException
53
     */
54
    public function all(): array
55
    {
56
        if (!$this->loaded) {
57
            $this->loadAllErrors();
58
        }
59
60
        return $this->errors;
61
    }
62
63
    /**
64
     * @param MappedControlledError $error
65
     *
66
     * @throws \ReflectionException
67
     */
68
    public function add(MappedControlledError $error)
69
    {
70
        if ($this->has($error->getCode())) {
71
            $message = sprintf(
72
                'Duplicate error definition, the error code "%s" can\'t be used for "%s" because this code is already used in: "%s"',
73
                $error->getCode(),
74
                $error->getClass(),
75
                $this->all()[$error->getCode()]->getClass()
76
            );
77
            throw new \LogicException($message);
78
        }
79
80
        $this->errors[$error->getCode()] = $error;
81
    }
82
83
    /**
84
     * @param string $code
85
     *
86
     * @return bool
87
     *
88
     * @throws \ReflectionException
89
     */
90
    public function has($code)
91
    {
92
        return isset($this->all()[$code]);
93
    }
94
95
    /**
96
     * Clear errors and cache
97
     */
98
    public function clear(): void
99
    {
100
        $this->errors = [];
101
        if (file_exists($this->cacheFileName())) {
102
            @unlink($this->cacheFileName());
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

102
            /** @scrutinizer ignore-unhandled */ @unlink($this->cacheFileName());

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...
103
        }
104
    }
105
106
    /**
107
     * @throws \ReflectionException
108
     *
109
     * @return void
110
     */
111
    private function loadAllErrors(): void
112
    {
113
        $this->loadFromCache();
114
        if ($this->loaded) {
115
            return;
116
        }
117
118
        $loadedErrors = [];
119
        $paths = [];
120
        if (Kernel::VERSION_ID >= 40000) {
121
            foreach ($this->config['locations'] ?? [] as $location) {
122
                $path = $this->kernel->getRootDir().'/'.$location;
123
                if (file_exists($path)) {
124
                    $paths[$path] = 'App\\'.$location;
125
                }
126
            }
127
        }
128
129
        foreach ($this->kernel->getBundles() as $bundle) {
130
            foreach ($this->config['locations'] ?? [] as $location) {
131
                $path = $bundle->getPath().'/'.$location;
132
                if (file_exists($path)) {
133
                    $paths[$path] = $bundle->getNamespace().'\\'.$location;
134
                }
135
            }
136
        }
137
138
        foreach ($paths as $path => $namespace) {
139
            $finder = new Finder();
140
            $finder
141
                ->in($path)
142
                ->name('*.php');
143
144
            /** @var SplFileInfo[] $files */
145
            $files = $finder->files();
146
            foreach ($files as $file) {
147
                $className = sprintf(
148
                    '%s\%s',
149
                    $namespace,
150
                    preg_replace(
151
                        '/.php$/',
152
                        null,
153
                        str_replace('/', '\\', $file->getRelativePathname())
154
                    )
155
                );
156
                if (class_exists($className)) {
157
                    $allowed = false;
158
                    if ($whitelist = $this->config['whitelist'] ?? []) {
159
                        foreach ($whitelist as $exp) {
160
                            if (preg_match($exp, $className)) {
161
                                $allowed = true;
162
                                continue;
163
                            }
164
                        }
165
                    }
166
167
                    if ($blackList = $this->config['blacklist'] ?? []) {
168
                        foreach ($blackList as $exp) {
169
                            if (preg_match($exp, $className)) {
170
                                $allowed = false;
171
                                continue;
172
                            }
173
                        }
174
                    }
175
176
                    if (!$allowed) {
177
                        continue;
178
                    }
179
180
                    $ref = new \ReflectionClass($className);
181
                    if ($ref->implementsInterface(ControlledErrorInterface::class) && $ref->isInstantiable()) {
182
                        /** @var ControlledErrorInterface $error */
183
                        $error = $ref->newInstanceWithoutConstructor();
184
                        $loadedErrors[] = new MappedControlledError(
185
                            $className,
186
                            $error->getMessage(),
187
                            $error->getCode(),
188
                            $error->getDescription()
189
                        );
190
                    }
191
                }
192
            }
193
        }
194
195
        $this->loaded = true;
196
        foreach ($loadedErrors as $error) {
197
            $this->add($error);
198
        }
199
200
        ksort($this->errors, SORT_NATURAL);
201
202
        $this->saveCache();
203
    }
204
205
    /**
206
     * @return string
207
     */
208
    private function cacheFileName(): string
209
    {
210
        return $this->kernel->getCacheDir().DIRECTORY_SEPARATOR.'graphql.controlled_errors.meta';
211
    }
212
213
    /**
214
     * Load cache
215
     */
216
    private function loadFromCache(): void
217
    {
218
        if (file_exists($this->cacheFileName())) {
219
            $content = @file_get_contents($this->cacheFileName());
220
            if ($content) {
221
                $this->loaded = true;
222
                $this->errors = unserialize($content, ['allowed_classes' => [MappedControlledError::class]]);
223
            }
224
        }
225
    }
226
227
    /**
228
     * Save cache
229
     */
230
    private function saveCache(): void
231
    {
232
        file_put_contents($this->cacheFileName(), serialize($this->errors));
233
    }
234
}
235