Passed
Push — master ( 85c65e...8d63d9 )
by Théo
02:46
created

Configuration::getPatchers()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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