Passed
Push — master ( fecc04...e5efab )
by Théo
03:04
created

Configuration::load()   B

Complexity

Conditions 9
Paths 8

Size

Total Lines 65

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 17.4419

Importance

Changes 0
Metric Value
cc 9
nc 8
nop 2
dl 0
loc 65
ccs 18
cts 34
cp 0.5294
crap 17.4419
rs 7.208
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Humbug\PhpScoper\Patcher\SymfonyPatcher;
18
use InvalidArgumentException;
19
use Iterator;
20
use RuntimeException;
21
use SplFileInfo;
22
use Symfony\Component\Filesystem\Filesystem;
23
use Symfony\Component\Finder\Finder;
24
use const DIRECTORY_SEPARATOR;
25
use function array_key_exists;
26
use function array_unshift;
27
use function dirname;
28
use function file_exists;
29
use function gettype;
30
use function in_array;
31
use function is_array;
32
use function is_bool;
33
use function is_file;
34
use function is_link;
35
use function is_string;
36
use function readlink;
37
use function realpath;
38
use function sprintf;
39
40
/**
41
 * @final
42
 * TODO: make this class as final as soon as the underlying deprecated class is removed.
43
 */
44
class Configuration
45
{
46
    private const PREFIX_KEYWORD = 'prefix';
47
    private const WHITELISTED_FILES_KEYWORD = 'files-whitelist';
48
    private const FINDER_KEYWORD = 'finders';
49
    private const PATCHERS_KEYWORD = 'patchers';
50
    private const WHITELIST_KEYWORD = 'whitelist';
51
    private const WHITELIST_GLOBAL_CONSTANTS_KEYWORD = 'whitelist-global-constants';
52
    private const WHITELIST_GLOBAL_CLASSES_KEYWORD = 'whitelist-global-classes';
53
    private const WHITELIST_GLOBAL_FUNCTIONS_KEYWORD = 'whitelist-global-functions';
54
55
    private const KEYWORDS = [
56
        self::PREFIX_KEYWORD,
57
        self::WHITELISTED_FILES_KEYWORD,
58
        self::FINDER_KEYWORD,
59
        self::PATCHERS_KEYWORD,
60
        self::WHITELIST_KEYWORD,
61
        self::WHITELIST_GLOBAL_CONSTANTS_KEYWORD,
62
        self::WHITELIST_GLOBAL_CLASSES_KEYWORD,
63
        self::WHITELIST_GLOBAL_FUNCTIONS_KEYWORD,
64
    ];
65
66
    private $path;
67
    private $prefix;
68
    private $filesWithContents;
69
    private $patchers;
70
    private $whitelist;
71
    private $whitelistedFiles;
72
73
    /**
74
     * @param string|null $path  Absolute path to the configuration file.
75
     * @param string[]    $paths List of paths to append besides the one configured
76
     *
77
     * @return self
78
     */
79 3
    public static function load(string $path = null, array $paths = []): self
80
    {
81 3
        if (null === $path) {
82 1
            $config = [];
83
        } else {
84 2
            if (false === (new Filesystem())->isAbsolutePath($path)) {
85
                throw new InvalidArgumentException(
86
                    sprintf(
87
                        'Expected the path of the configuration file to load to be an absolute path, got "%s" '
88
                        .'instead',
89
                        $path
90
                    )
91
                );
92
            }
93
94 2
            if (false === file_exists($path)) {
95
                throw new InvalidArgumentException(
96
                    sprintf(
97
                        'Expected the path of the configuration file to exists but the file "%s" could not be '
98
                        .'found',
99
                        $path
100
                    )
101
                );
102
            }
103
104 2
            if (false === is_file($path) && false === (is_link($path) && is_file(readlink($path)))) {
105
                throw new InvalidArgumentException(
106
                    sprintf(
107
                        'Expected the path of the configuration file to be a file but "%s" appears to be a '
108
                        .'directory.',
109
                        $path
110
                    )
111
                );
112
            }
113
114 2
            $config = include $path;
115
116 2
            if (false === is_array($config)) {
117
                throw new InvalidArgumentException(
118
                    sprintf(
119
                        'Expected configuration to be an array, found "%s" instead.',
120
                        gettype($config)
121
                    )
122
                );
123
            }
124
        }
125
126 3
        self::validateConfigKeys($config);
127
128 2
        $prefix = self::retrievePrefix($config);
129
130 2
        $whitelistedFiles = null === $path ? [] : self::retrieveWhitelistedFiles(dirname($path), $config);
131
132 2
        $patchers = self::retrievePatchers($config);
133
134 2
        array_unshift($patchers, new SymfonyPatcher());
135
136 2
        $whitelist = self::retrieveWhitelist($config);
137
138 2
        $finders = self::retrieveFinders($config);
139 2
        $filesFromPaths = self::retrieveFilesFromPaths($paths);
140 2
        $filesWithContents = self::retrieveFilesWithContents(chain($filesFromPaths, ...$finders));
141
142 2
        return new self($path, $prefix, $filesWithContents, $patchers, $whitelist, $whitelistedFiles);
143
    }
144
145
    /**
146
     * @param string|null $path              Absolute path to the configuration file loaded.
147
     * @param string|null $prefix            The prefix applied.
148
     * @param string[][]  $filesWithContents Array of tuple with the first argument being the file path and the second its contents
149
     * @param callable[]  $patchers          List of closures which can alter the content of the files being
150
     *                                       scoped.
151
     * @param Whitelist   $whitelist         List of classes that will not be scoped.
152
     *                                       returning a boolean which if `true` means the class should be scoped
153
     *                                       (i.e. is ignored) or scoped otherwise.
154
     * @param string[]    $whitelistedFiles  List of absolute paths of files to completely ignore
155
     */
156 2
    private function __construct(
157
        ?string $path,
158
        ?string $prefix,
159
        array $filesWithContents,
160
        array $patchers,
161
        Whitelist $whitelist,
162
        array $whitelistedFiles
163
    ) {
164 2
        $this->path = $path;
165 2
        $this->prefix = $prefix;
166 2
        $this->filesWithContents = $filesWithContents;
167 2
        $this->patchers = $patchers;
168 2
        $this->whitelist = $whitelist;
169 2
        $this->whitelistedFiles = $whitelistedFiles;
170
    }
171
172
    public function withPaths(array $paths): self
173
    {
174
        $filesWithContents = self::retrieveFilesWithContents(
175
            chain(
176
                self::retrieveFilesFromPaths(
177
                    array_unique($paths)
178
                )
179
            )
180
        );
181
182
        return new self(
183
            $this->path,
184
            $this->prefix,
185
            array_merge($this->filesWithContents, $filesWithContents),
186
            $this->patchers,
187
            $this->whitelist,
188
            $this->whitelistedFiles
189
        );
190
    }
191
192
    public function withPrefix(?string $prefix): self
193
    {
194
        $prefix = self::retrievePrefix([self::PREFIX_KEYWORD => $prefix]);
195
196
        return new self(
197
            $this->path,
198
            $prefix,
199
            $this->filesWithContents,
200
            $this->patchers,
201
            $this->whitelist,
202
            $this->whitelistedFiles
203
        );
204
    }
205
206 2
    public function getPath(): ?string
207
    {
208 2
        return $this->path;
209
    }
210
211 2
    public function getPrefix(): ?string
212
    {
213 2
        return $this->prefix;
214
    }
215
216 2
    public function getFilesWithContents(): array
217
    {
218 2
        return $this->filesWithContents;
219
    }
220
221
    /**
222
     * @return callable[]
223
     */
224 2
    public function getPatchers(): array
225
    {
226 2
        return $this->patchers;
227
    }
228
229 2
    public function getWhitelist(): Whitelist
230
    {
231 2
        return $this->whitelist;
232
    }
233
234
    /**
235
     * @return string[]
236
     */
237 2
    public function getWhitelistedFiles(): array
238
    {
239 2
        return $this->whitelistedFiles;
240
    }
241
242 3
    private static function validateConfigKeys(array $config): void
243
    {
244 3
        array_map(
245 3
            self::class.'::validateConfigKey',
246 3
            array_keys($config)
247
        );
248
    }
249
250 2
    private static function validateConfigKey(string $key): void
251
    {
252 2
        if (false === in_array($key, self::KEYWORDS, true)) {
253 1
            throw new InvalidArgumentException(
254 1
                sprintf(
255 1
                    'Invalid configuration key value "%s" found.',
256 1
                    $key
257
                )
258
            );
259
        }
260
    }
261
262
    /**
263
     * If the prefix is set to null in the config file/argument then a random prefix is being used. However if set to
264
     * empty, the configuration will use a null prefix.
265
     *
266
     * TL:DR; setting the prefix is a big confusing because it is not properly split in "set prefix" & prefix strategy".
267
     */
268 2
    private static function retrievePrefix(array $config): ?string
269
    {
270 2
        $prefix = $config[self::PREFIX_KEYWORD] ?? null;
271
272 2
        if (null === $prefix) {
273 1
            return null;
274
        }
275
276 1
        $prefix = trim($prefix);
277
278 1
        return '' === $prefix ? null : $prefix;
279
    }
280
281 2
    private static function retrievePatchers(array $config): array
282
    {
283 2
        if (false === array_key_exists(self::PATCHERS_KEYWORD, $config)) {
284 2
            return [];
285
        }
286
287
        $patchers = $config[self::PATCHERS_KEYWORD];
288
289
        if (false === is_array($patchers)) {
290
            throw new InvalidArgumentException(
291
                sprintf(
292
                    'Expected patchers to be an array of callables, found "%s" instead.',
293
                    gettype($patchers)
294
                )
295
            );
296
        }
297
298
        foreach ($patchers as $index => $patcher) {
299
            if (is_callable($patcher)) {
300
                continue;
301
            }
302
303
            throw new InvalidArgumentException(
304
                sprintf(
305
                    'Expected patchers to be an array of callables, the "%d" element is not.',
306
                    $index
307
                )
308
            );
309
        }
310
311
        return $patchers;
312
    }
313
314 2
    private static function retrieveWhitelist(array $config): Whitelist
315
    {
316 2
        if (false === array_key_exists(self::WHITELIST_KEYWORD, $config)) {
317 1
            $whitelist = [];
318
        } else {
319 1
            $whitelist = $config[self::WHITELIST_KEYWORD];
320
321 1
            if (false === is_array($whitelist)) {
322
                throw new InvalidArgumentException(
323
                    sprintf(
324
                        'Expected whitelist to be an array of strings, found "%s" instead.',
325
                        gettype($whitelist)
326
                    )
327
                );
328
            }
329
330 1
            foreach ($whitelist as $index => $className) {
331 1
                if (is_string($className)) {
332 1
                    continue;
333
                }
334
335
                throw new InvalidArgumentException(
336
                    sprintf(
337
                        'Expected whitelist to be an array of string, the "%d" element is not.',
338
                        $index
339
                    )
340
                );
341
            }
342
        }
343
344 2
        if (false === array_key_exists(self::WHITELIST_GLOBAL_CONSTANTS_KEYWORD, $config)) {
345 1
            $whitelistGlobalConstants = true;
346
        } else {
347 1
            $whitelistGlobalConstants = $config[self::WHITELIST_GLOBAL_CONSTANTS_KEYWORD];
348
349 1
            if (false === is_bool($whitelistGlobalConstants)) {
350
                throw new InvalidArgumentException(
351
                    sprintf(
352
                        'Expected %s to be a boolean, found "%s" instead.',
353
                        self::WHITELIST_GLOBAL_CONSTANTS_KEYWORD,
354
                        gettype($whitelistGlobalConstants)
355
                    )
356
                );
357
            }
358
        }
359
360 2
        if (false === array_key_exists(self::WHITELIST_GLOBAL_CLASSES_KEYWORD, $config)) {
361 1
            $whitelistGlobalClasses = true;
362
        } else {
363 1
            $whitelistGlobalClasses = $config[self::WHITELIST_GLOBAL_CLASSES_KEYWORD];
364
365 1
            if (false === is_bool($whitelistGlobalClasses)) {
366
                throw new InvalidArgumentException(
367
                    sprintf(
368
                        'Expected %s to be a boolean, found "%s" instead.',
369
                        self::WHITELIST_GLOBAL_CLASSES_KEYWORD,
370
                        gettype($whitelistGlobalClasses)
371
                    )
372
                );
373
            }
374
        }
375
376 2
        if (false === array_key_exists(self::WHITELIST_GLOBAL_FUNCTIONS_KEYWORD, $config)) {
377 1
            $whitelistGlobalFunctions = true;
378
        } else {
379 1
            $whitelistGlobalFunctions = $config[self::WHITELIST_GLOBAL_FUNCTIONS_KEYWORD];
380
381 1
            if (false === is_bool($whitelistGlobalFunctions)) {
382
                throw new InvalidArgumentException(
383
                    sprintf(
384
                        'Expected %s to be a boolean, found "%s" instead.',
385
                        self::WHITELIST_GLOBAL_FUNCTIONS_KEYWORD,
386
                        gettype($whitelistGlobalFunctions)
387
                    )
388
                );
389
            }
390
        }
391
392 2
        return Whitelist::create($whitelistGlobalConstants, $whitelistGlobalClasses, $whitelistGlobalFunctions, ...$whitelist);
393
    }
394
395
    /**
396
     * @return string[] Absolute paths
397
     */
398 1
    private static function retrieveWhitelistedFiles(string $dirPath, array $config): array
399
    {
400 1
        if (false === array_key_exists(self::WHITELISTED_FILES_KEYWORD, $config)) {
401
            return [];
402
        }
403
404 1
        $whitelistedFiles = $config[self::WHITELISTED_FILES_KEYWORD];
405
406 1
        if (false === is_array($whitelistedFiles)) {
407
            throw new InvalidArgumentException(
408
                sprintf(
409
                    'Expected whitelisted files to be an array of strings, found "%s" instead.',
410
                    gettype($whitelistedFiles)
411
                )
412
            );
413
        }
414
415 1
        foreach ($whitelistedFiles as $index => $file) {
416 1
            if (false === is_string($file)) {
417
                throw new InvalidArgumentException(
418
                    sprintf(
419
                        'Expected whitelisted files to be an array of string, the "%d" element is not.',
420
                        $index
421
                    )
422
                );
423
            }
424
425 1
            if (false === (new Filesystem())->isAbsolutePath($file)) {
426 1
                $file = $dirPath.DIRECTORY_SEPARATOR.$file;
427
            }
428
429 1
            $whitelistedFiles[$index] = realpath($file);
430
        }
431
432 1
        return array_filter($whitelistedFiles);
433
    }
434
435 2
    private static function retrieveFinders(array $config): array
436
    {
437 2
        if (false === array_key_exists(self::FINDER_KEYWORD, $config)) {
438 2
            return [];
439
        }
440
441
        $finders = $config[self::FINDER_KEYWORD];
442
443
        if (false === is_array($finders)) {
444
            throw new InvalidArgumentException(
445
                sprintf(
446
                    'Expected finders to be an array of "%s", found "%s" instead.',
447
                    Finder::class,
448
                    gettype($finders)
449
                )
450
            );
451
        }
452
453
        foreach ($finders as $index => $finder) {
454
            if ($finder instanceof Finder) {
455
                continue;
456
            }
457
458
            throw new InvalidArgumentException(
459
                sprintf(
460
                    'Expected finders to be an array of "%s", the "%d" element is not.',
461
                    Finder::class,
462
                    $index
463
                )
464
            );
465
        }
466
467
        return $finders;
468
    }
469
470
    /**
471
     * @param string[] $paths
472
     *
473
     * @return iterable
474
     */
475 2
    private static function retrieveFilesFromPaths(array $paths): iterable
476
    {
477 2
        if ([] === $paths) {
478 2
            return [];
479
        }
480
481
        $pathsToSearch = [];
482
        $filesToAppend = [];
483
484
        foreach ($paths as $path) {
485
            if (false === file_exists($path)) {
486
                throw new RuntimeException(
487
                    sprintf(
488
                        'Could not find the file "%s".',
489
                        $path
490
                    )
491
                );
492
            }
493
494
            if (is_dir($path)) {
495
                $pathsToSearch[] = $path;
496
            } else {
497
                $filesToAppend[] = $path;
498
            }
499
        }
500
501
        $finder = new Finder();
502
503
        $finder->files()
504
            ->in($pathsToSearch)
505
            ->append($filesToAppend)
1 ignored issue
show
Documentation introduced by
$filesToAppend is of type array, but the function expects a object<Symfony\Component\Finder\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
506
            ->filter(static function (SplFileInfo $fileInfo): ?bool {
507
                if ($fileInfo->isLink()) {
508
                    return false;
509
                }
510
511
                return null;
512
            })
513
            ->sortByName()
514
        ;
515
516
        return $finder;
517
    }
518
519
    /**
520
     * @param Iterator $files
521
     *
522
     * @return string[][] Array of tuple with the first argument being the file path and the second its contents
523
     */
524 2
    private static function retrieveFilesWithContents(Iterator $files): array
525
    {
526 2
        return array_reduce(
527 2
            iterator_to_array($files),
528
            static function (array $files, SplFileInfo $fileInfo): array {
529
                $file = $fileInfo->getRealPath();
530
531
                if (false === $file) {
532
                    throw new RuntimeException(
533
                        sprintf(
534
                            'Could not find the file "%s".',
535
                            (string) $fileInfo
536
                        )
537
                    );
538
                }
539
540
                if (false === is_readable($file)) {
541
                    throw new RuntimeException(
542
                        sprintf(
543
                            'Could not read the file "%s".',
544
                            $file
545
                        )
546
                    );
547
                }
548
549
                $files[$fileInfo->getRealPath()] = [$fileInfo->getRealPath(), file_get_contents($file)];
550
551
                return $files;
552 2
            },
553 2
            []
554
        );
555
    }
556
}
557