Passed
Push — master ( f59561...f05f0a )
by Théo
03:05
created

Configuration::withPaths()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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