Passed
Push — master ( 61b862...4e436f )
by Théo
02:26
created

Configuration   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 364
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 364
ccs 0
cts 159
cp 0
rs 8.2769
c 0
b 0
f 0
wmc 41
lcom 1
cbo 1

17 Methods

Rating   Name   Duplication   Size   Complexity  
B load() 0 30 3
A __construct() 0 13 1
A withPaths() 0 18 1
A withPrefix() 0 12 1
A getPath() 0 4 1
A getPrefix() 0 4 1
A getFilesWithContents() 0 4 1
A getPatchers() 0 4 1
A getWhitelist() 0 4 1
A validateConfigKeys() 0 7 1
A validateConfigKey() 0 11 2
A retrievePrefix() 0 12 4
B retrievePatchers() 0 32 5
B retrieveWhitelist() 0 32 5
B retrieveFinders() 0 34 5
B retrieveFilesFromPaths() 0 36 5
B retrieveFilesWithContents() 0 32 3

How to fix   Complexity   

Complex Class

Complex classes like Configuration often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Configuration, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the humbug/php-scoper package.
7
 *
8
 * Copyright (c) 2017 Théo FIDRY <[email protected]>,
9
 *                    Pádraic Brady <[email protected]>
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace Humbug\PhpScoper;
16
17
use Closure;
18
use InvalidArgumentException;
19
use Iterator;
20
use RuntimeException;
21
use SplFileInfo;
22
use Symfony\Component\Finder\Finder;
23
24
/**
25
 * @final
26
 * TODO: make this class as final as soon as the underlying deprecated class is removed.
27
 */
28
class Configuration
29
{
30
    private const PREFIX = 'prefix';
31
    private const FINDER_KEYWORD = 'finders';
32
    private const PATCHERS_KEYWORD = 'patchers';
33
    private const WHITELIST_KEYWORD = 'whitelist';
34
    private const GLOBAL_NAMESPACE_KEYWORD = 'global_namespace_whitelist';
35
36
    private const KEYWORDS = [
37
        self::PREFIX,
38
        self::FINDER_KEYWORD,
39
        self::PATCHERS_KEYWORD,
40
        self::WHITELIST_KEYWORD,
41
        self::GLOBAL_NAMESPACE_KEYWORD,
42
    ];
43
44
    private $path;
45
    private $prefix;
46
    private $filesWithContents;
47
    private $patchers;
48
    private $whitelist;
49
50
    /**
51
     * @param string|null $path  Absolute path to the configuration file.
52
     * @param string[]    $paths List of paths to append besides the one configured
53
     *
54
     * @return self
55
     */
56
    public static function load(string $path = null, array $paths = []): self
57
    {
58
        if (null === $path) {
59
            $config = [];
60
        } else {
61
            $config = include $path;
62
63
            if (false === is_array($config)) {
64
                throw new InvalidArgumentException(
65
                    sprintf(
66
                        'Expected configuration to be an array, found "%s" instead.',
67
                        gettype($config)
68
                    )
69
                );
70
            }
71
        }
72
73
        self::validateConfigKeys($config);
74
75
        $prefix = self::retrievePrefix($config);
76
77
        $patchers = self::retrievePatchers($config);
78
        $whitelist = self::retrieveWhitelist($config);
79
80
        $finders = self::retrieveFinders($config);
81
        $filesFromPaths = self::retrieveFilesFromPaths($paths);
82
        $filesWithContents = self::retrieveFilesWithContents(chain($filesFromPaths, ...$finders));
83
84
        return new self($path, $prefix, $filesWithContents, $patchers, $whitelist);
85
    }
86
87
    /**
88
     * @param string|null        $path                        Absolute path to the configuration file loaded.
89
     * @param string|null        $prefix                      The prefix applied.
90
     * @param [string, string][] $filesWithContents           Array of tuple with the first argument being the file path and the second its contents
91
     * @param callable[]         $patchers                    List of closures which can alter the content of the files being
92
     *                                                        scoped.
93
     * @param string[]           $whitelist                   List of classes that will not be scoped.
94
     * @param Closure            $globalNamespaceWhitelisters Closure taking a class name from the global namespace as an argument and
0 ignored issues
show
Documentation introduced by
There is no parameter named $globalNamespaceWhitelisters. Did you maybe mean $whitelist?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
95
     *                                                        returning a boolean which if `true` means the class should be scoped
96
     *                                                        (i.e. is ignored) or scoped otherwise.
97
     */
98
    private function __construct(
99
        ?string $path,
100
        ?string $prefix,
101
        array $filesWithContents,
102
        array $patchers,
103
        array $whitelist
104
    ) {
105
        $this->path = $path;
106
        $this->prefix = $prefix;
107
        $this->filesWithContents = $filesWithContents;
108
        $this->patchers = $patchers;
109
        $this->whitelist = $whitelist;
110
    }
111
112
    public function withPaths(array $paths): self
113
    {
114
        $filesWithContents = self::retrieveFilesWithContents(
115
            chain(
116
                self::retrieveFilesFromPaths(
117
                    array_unique($paths)
118
                )
119
            )
120
        );
121
122
        return new self(
123
            $this->path,
124
            $this->prefix,
125
            array_merge($this->filesWithContents, $filesWithContents),
126
            $this->patchers,
127
            $this->whitelist
128
        );
129
    }
130
131
    public function withPrefix(?string $prefix): self
132
    {
133
        $prefix = self::retrievePrefix([self::PREFIX => $prefix]);
134
135
        return new self(
136
            $this->path,
137
            $prefix,
138
            $this->filesWithContents,
139
            $this->patchers,
140
            $this->whitelist
141
        );
142
    }
143
144
    public function getPath(): string
145
    {
146
        return $this->path;
147
    }
148
149
    public function getPrefix(): ?string
150
    {
151
        return $this->prefix;
152
    }
153
154
    public function getFilesWithContents(): array
155
    {
156
        return $this->filesWithContents;
157
    }
158
159
    /**
160
     * @return callable[]
161
     */
162
    public function getPatchers(): array
163
    {
164
        return $this->patchers;
165
    }
166
167
    public function getWhitelist(): array
168
    {
169
        return $this->whitelist;
170
    }
171
172
    private static function validateConfigKeys(array $config): void
173
    {
174
        array_map(
175
            ['self', 'validateConfigKey'],
176
            array_keys($config)
177
        );
178
    }
179
180
    private static function validateConfigKey(string $key): void
181
    {
182
        if (false === in_array($key, self::KEYWORDS)) {
183
            throw new InvalidArgumentException(
184
                sprintf(
185
                    'Invalid configuration key value "%s" found.',
186
                    $key
187
                )
188
            );
189
        }
190
    }
191
192
    /**
193
     * If the prefix is set to null in the config file/argument then a random prefix is being used. However if set to
194
     * empty, the configuration will use a null prefix.
195
     *
196
     * TL:DR; setting the prefix is a big confusing because it is not properly split in "set prefix" & prefix strategy".
197
     */
198
    private static function retrievePrefix(array $config): ?string
199
    {
200
        $prefix = array_key_exists(self::PREFIX, $config) ? $config[self::PREFIX] : null;
201
202
        if (null === $prefix) {
203
            return uniqid('_PhpScoper');
204
        }
205
206
        $prefix = trim($prefix);
207
208
        return '' === $prefix ? null : $prefix;
209
    }
210
211
    private static function retrievePatchers(array $config): array
212
    {
213
        if (false === array_key_exists(self::PATCHERS_KEYWORD, $config)) {
214
            return [];
215
        }
216
217
        $patchers = $config[self::PATCHERS_KEYWORD];
218
219
        if (false === is_array($patchers)) {
220
            throw new InvalidArgumentException(
221
                sprintf(
222
                    'Expected patchers to be an array of callables, found "%s" instead.',
223
                    gettype($patchers)
224
                )
225
            );
226
        }
227
228
        foreach ($patchers as $index => $patcher) {
229
            if (is_callable($patcher)) {
230
                continue;
231
            }
232
233
            throw new InvalidArgumentException(
234
                sprintf(
235
                    'Expected patchers to be an array of callables, the "%d" element is not.',
236
                    $index
237
                )
238
            );
239
        }
240
241
        return $patchers;
242
    }
243
244
    private static function retrieveWhitelist(array $config): array
245
    {
246
        if (false === array_key_exists(self::WHITELIST_KEYWORD, $config)) {
247
            return [];
248
        }
249
250
        $whitelist = $config[self::WHITELIST_KEYWORD];
251
252
        if (false === is_array($whitelist)) {
253
            throw new InvalidArgumentException(
254
                sprintf(
255
                    'Expected whitelist to be an array of strings, found "%s" instead.',
256
                    gettype($whitelist)
257
                )
258
            );
259
        }
260
261
        foreach ($whitelist as $index => $className) {
262
            if (is_string($className)) {
263
                continue;
264
            }
265
266
            throw new InvalidArgumentException(
267
                sprintf(
268
                    'Expected whitelist to be an array of string, the "%d" element is not.',
269
                    $index
270
                )
271
            );
272
        }
273
274
        return $whitelist;
275
    }
276
277
    private static function retrieveFinders(array $config): array
278
    {
279
        if (false === array_key_exists(self::FINDER_KEYWORD, $config)) {
280
            return [];
281
        }
282
283
        $finders = $config[self::FINDER_KEYWORD];
284
285
        if (false === is_array($finders)) {
286
            throw new InvalidArgumentException(
287
                sprintf(
288
                    'Expected finders to be an array of "%s", found "%s" instead.',
289
                    Finder::class,
290
                    gettype($finders)
291
                )
292
            );
293
        }
294
295
        foreach ($finders as $index => $finder) {
296
            if ($finder instanceof Finder) {
297
                continue;
298
            }
299
300
            throw new InvalidArgumentException(
301
                sprintf(
302
                    'Expected finders to be an array of "%s", the "%d" element is not.',
303
                    Finder::class,
304
                    $index
305
                )
306
            );
307
        }
308
309
        return $finders;
310
    }
311
312
    /**
313
     * @param string[] $paths
314
     *
315
     * @return iterable
316
     */
317
    private static function retrieveFilesFromPaths(array $paths): iterable
318
    {
319
        if ([] === $paths) {
320
            return [];
321
        }
322
323
        $pathsToSearch = [];
324
        $filesToAppend = [];
325
326
        foreach ($paths as $path) {
327
            if (false === file_exists($path)) {
328
                throw new RuntimeException(
329
                    sprintf(
330
                        'Could not find the file "%s".',
331
                        $path
332
                    )
333
                );
334
            }
335
336
            if (is_dir($path)) {
337
                $pathsToSearch[] = $path;
338
            } else {
339
                $filesToAppend[] = $path;
340
            }
341
        }
342
343
        $finder = new Finder();
344
345
        $finder->files()
346
            ->in($pathsToSearch)
347
            ->append($filesToAppend)
348
            ->sortByName()
349
        ;
350
351
        return $finder;
352
    }
353
354
    /**
355
     * @param Iterator $files
356
     *
357
     * @return [string, string][] Array of tuple with the first argument being the file path and the second its contents
0 ignored issues
show
Documentation introduced by
The doc-type string,">[string, could not be parsed: Unknown type name "[" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
358
     */
359
    private static function retrieveFilesWithContents(Iterator $files): array
360
    {
361
        return array_reduce(
362
            iterator_to_array($files),
363
            function (array $files, SplFileInfo $fileInfo): array {
364
                $file = $fileInfo->getRealPath();
365
366
                if (false === $file) {
367
                    throw new RuntimeException(
368
                        sprintf(
369
                            'Could not find the file "%s".',
370
                            (string) $fileInfo
371
                        )
372
                    );
373
                }
374
375
                if (false === is_readable($file)) {
376
                    throw new RuntimeException(
377
                        sprintf(
378
                            'Could not read the file "%s".',
379
                            $file
380
                        )
381
                    );
382
                }
383
384
                $files[$fileInfo->getRealPath()] = [$fileInfo->getRealPath(), file_get_contents($file)];
385
386
                return $files;
387
            },
388
            []
389
        );
390
    }
391
}
392