Completed
Push — master ( 32a903...cb8294 )
by Théo
02:52 queued 22s
created

Configuration::getPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
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\Console;
16
17
use Closure;
18
use InvalidArgumentException;
19
use Iterator;
20
use RuntimeException;
21
use Symfony\Component\Finder\Finder;
22
use function Humbug\PhpScoper\iterables_to_iterator;
23
24
final class Configuration
25
{
26
    private const FINDER_KEYWORD = 'finders';
27
    private const PATCHERS_KEYWORD = 'patchers';
28
    private const WHITELIST_KEYWORD = 'whitelist';
29
    private const GLOBAL_NAMESPACE_KEYWORD = 'global_namespace_whitelist';
30
31
    private const KEYWORDS = [
32
        self::FINDER_KEYWORD,
33
        self::PATCHERS_KEYWORD,
34
        self::WHITELIST_KEYWORD,
35
        self::GLOBAL_NAMESPACE_KEYWORD,
36
    ];
37
38
    private $path;
39
    private $filesWithContents;
40
    private $patchers;
41
    private $whitelist;
42
    private $globalNamespaceWhitelister;
43
44
    /**
45
     * @param string|null $path  Absolute path to the configuration file.
46
     * @param string[]    $paths List of paths to append besides the one configured
47
     *
48
     * @return self
49
     */
50
    public static function load(string $path = null, array $paths = []): self
51
    {
52
        if (null === $path) {
53
            $config = [];
54
        } else {
55
            $config = include $path;
56
57
            if (false === is_array($config)) {
58
                throw new InvalidArgumentException(
59
                    sprintf(
60
                        'Expected configuration to be an array, found "%s" instead.',
61
                        gettype($config)
62
                    )
63
                );
64
            }
65
        }
66
67
        self::validateConfigKeys($config);
68
69
        $patchers = self::retrievePatchers($config);
70
        $whitelist = self::retrieveWhitelist($config);
71
72
        $globalNamespace = self::retrieveGlobalNamespaceWhitelisters($config);
73
        $globalWhitelister = self::createGlobalWhitelister($globalNamespace);
74
75
        $finders = self::retrieveFinders($config);
76
        $filesFromPaths = self::retrieveFilesFromPaths($paths);
77
78
        $filesWithContents = self::retrieveFilesWithContents(iterables_to_iterator($filesFromPaths, ...$finders));
79
80
        return new self($path, $filesWithContents, $patchers, $whitelist, $globalWhitelister);
81
    }
82
83
    /**
84
     * @param string|null        $path                        Absolute path to the configuration file loaded.
85
     * @param [string, string][] $filesWithContents           Array of tuple with the first argument being the file path and the second its contents
86
     * @param callable[]         $patchers                    List of closures which can alter the content of the files being
87
     *                                                        scoped.
88
     * @param string[]           $whitelist                   List of classes that will not be scoped.
89
     * @param Closure            $globalNamespaceWhitelisters Closure taking a class name from the global namespace as an argument and
90
     *                                                        returning a boolean which if `true` means the class should be scoped
91
     *                                                        (i.e. is ignored) or scoped otherwise.
92
     */
93
    private function __construct(
94
        ?string $path,
95
        array $filesWithContents,
96
        array $patchers,
97
        array $whitelist,
98
        Closure $globalNamespaceWhitelisters
99
    ) {
100
        $this->path = $path;
101
        $this->filesWithContents = $filesWithContents;
102
        $this->patchers = $patchers;
103
        $this->whitelist = $whitelist;
104
        $this->globalNamespaceWhitelister = $globalNamespaceWhitelisters;
105
    }
106
107
    public function withPaths(array $paths): self
108
    {
109
        $filesWithContents = self::retrieveFilesWithContents(
110
            iterables_to_iterator(
111
                self::retrieveFilesFromPaths(
112
                    array_unique($paths)
113
                )
114
            )
115
        );
116
117
        return new self(
118
            $this->path,
119
            array_merge($this->filesWithContents, $filesWithContents),
120
            $this->patchers,
121
            $this->whitelist,
122
            $this->globalNamespaceWhitelister
123
        );
124
    }
125
126
    public function getPath(): string
127
    {
128
        return $this->path;
129
    }
130
131
    public function getFilesWithContents(): array
132
    {
133
        return $this->filesWithContents;
134
    }
135
136
    /**
137
     * @return callable[]
138
     */
139
    public function getPatchers(): array
140
    {
141
        return $this->patchers;
142
    }
143
144
    public function getWhitelist(): array
145
    {
146
        return $this->whitelist;
147
    }
148
149
    public function getGlobalNamespaceWhitelister(): Closure
150
    {
151
        return $this->globalNamespaceWhitelister;
152
    }
153
154
    private static function validateConfigKeys(array $config): void
155
    {
156
        array_map(
157
            ['self', 'validateConfigKey'],
158
            array_keys($config)
159
        );
160
    }
161
162
    private static function validateConfigKey(string $key): void
163
    {
164
        if (false === in_array($key, self::KEYWORDS)) {
165
            throw new InvalidArgumentException(
166
                sprintf(
167
                    'Invalid configuration key value "%s" found.',
168
                    $key
169
                )
170
            );
171
        }
172
    }
173
174
    private static function retrievePatchers(array $config): array
175
    {
176
        if (false === array_key_exists(self::PATCHERS_KEYWORD, $config)) {
177
            return [];
178
        }
179
180
        $patchers = $config[self::PATCHERS_KEYWORD];
181
182
        if (false === is_array($patchers)) {
183
            throw new InvalidArgumentException(
184
                sprintf(
185
                    'Expected patchers to be an array of callables, found "%s" instead.',
186
                    gettype($patchers)
187
                )
188
            );
189
        }
190
191
        foreach ($patchers as $index => $patcher) {
192
            if (is_callable($patcher)) {
193
                continue;
194
            }
195
196
            throw new InvalidArgumentException(
197
                sprintf(
198
                    'Expected patchers to be an array of callables, the "%d" element is not.',
199
                    $index
200
                )
201
            );
202
        }
203
204
        return $patchers;
205
    }
206
207
    private static function retrieveWhitelist(array $config): array
208
    {
209
        if (false === array_key_exists(self::WHITELIST_KEYWORD, $config)) {
210
            return [];
211
        }
212
213
        $whitelist = $config[self::WHITELIST_KEYWORD];
214
215
        if (false === is_array($whitelist)) {
216
            throw new InvalidArgumentException(
217
                sprintf(
218
                    'Expected whitelist to be an array of strings, found "%s" instead.',
219
                    gettype($whitelist)
220
                )
221
            );
222
        }
223
224
        foreach ($whitelist as $index => $className) {
225
            if (is_string($className)) {
226
                continue;
227
            }
228
229
            throw new InvalidArgumentException(
230
                sprintf(
231
                    'Expected whitelist to be an array of string, the "%d" element is not.',
232
                    $index
233
                )
234
            );
235
        }
236
237
        return $whitelist;
238
    }
239
240
    private static function retrieveGlobalNamespaceWhitelisters(array $config): array
241
    {
242
        if (false === array_key_exists(self::GLOBAL_NAMESPACE_KEYWORD, $config)) {
243
            return [];
244
        }
245
246
        $globalNamespace = $config[self::GLOBAL_NAMESPACE_KEYWORD];
247
248
        if (false === is_array($globalNamespace)) {
249
            throw new InvalidArgumentException(
250
                sprintf(
251
                    'Expected "global_namespace" to be an array, found "%s" instead.',
252
                    gettype($globalNamespace)
253
                )
254
            );
255
        }
256
257
        foreach ($globalNamespace as $index => $className) {
258
            if (is_string($className) || is_callable($className)) {
259
                continue;
260
            }
261
262
            throw new InvalidArgumentException(
263
                sprintf(
264
                    'Expected "global_namespace" to be an array of callables or strings, the "%d" element '
265
                    .'is not.',
266
                    $index
267
                )
268
            );
269
        }
270
271
        return $globalNamespace;
272
    }
273
274
    /**
275
     * @param string[]|callable[] $globalNamespaceWhitelist
276
     *
277
     * @return Closure
278
     */
279
    private static function createGlobalWhitelister(array $globalNamespaceWhitelist): Closure
280
    {
281
        return function (string $className) use ($globalNamespaceWhitelist): bool {
282
            foreach ($globalNamespaceWhitelist as $whitelister) {
283
                if (is_string($whitelister)) {
284
                    if ($className === $whitelister) {
285
                        return true;
286
                    } else {
287
                        continue;
288
                    }
289
                }
290
291
                /** @var callable $whitelister */
292
                if (true === $whitelister($className)) {
293
                    return true;
294
                }
295
            }
296
297
            return false;
298
        };
299
    }
300
301
    private static function retrieveFinders(array $config): array
302
    {
303
        if (false === array_key_exists(self::FINDER_KEYWORD, $config)) {
304
            return [];
305
        }
306
307
        $finders = $config[self::FINDER_KEYWORD];
308
309
        if (false === is_array($finders)) {
310
            throw new InvalidArgumentException(
311
                sprintf(
312
                    'Expected finders to be an array of "%s", found "%s" instead.',
313
                    Finder::class,
314
                    gettype($finders)
315
                )
316
            );
317
        }
318
319
        foreach ($finders as $index => $finder) {
320
            if ($finder instanceof Finder) {
321
                continue;
322
            }
323
324
            throw new InvalidArgumentException(
325
                sprintf(
326
                    'Expected finders to be an array of "%s", the "%d" element is not.',
327
                    Finder::class,
328
                    $index
329
                )
330
            );
331
        }
332
333
        return $finders;
334
    }
335
336
    /**
337
     * @param string[] $paths
338
     *
339
     * @return iterable
340
     */
341
    private static function retrieveFilesFromPaths(array $paths): iterable
342
    {
343
        if ([] === $paths) {
344
            return [];
345
        }
346
347
        $pathsToSearch = [];
348
        $filesToAppend = [];
349
350
        foreach ($paths as $path) {
351
            if (false === file_exists($path)) {
352
                throw new RuntimeException(
353
                    sprintf(
354
                        'Could not find the file "%s".',
355
                        $path
356
                    )
357
                );
358
            }
359
360
            if (is_dir($path)) {
361
                $pathsToSearch[] = $path;
362
            } else {
363
                $filesToAppend[] = $path;
364
            }
365
        }
366
367
        $finder = new Finder();
368
369
        $finder->files()
370
            ->in($pathsToSearch)
371
            ->append($filesToAppend)
372
            ->sortByName()
373
        ;
374
375
        return $finder;
376
    }
377
378
    /**
379
     * @param Iterator $files
380
     *
381
     * @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...
382
     */
383
    private static function retrieveFilesWithContents(Iterator $files): array
384
    {
385
        return array_reduce(
386
            iterator_to_array($files),
387
            function (array $files, $fileInfo): array {
388
                $file = (string) $fileInfo;
389
390
                if (false === file_exists($file)) {
391
                    throw new RuntimeException(
392
                        sprintf(
393
                            'Could not find the file "%s".',
394
                            $file
395
                        )
396
                    );
397
                }
398
399
                if (false === is_readable($file)) {
400
                    throw new RuntimeException(
401
                        sprintf(
402
                            'Could not read the file "%s".',
403
                            $file
404
                        )
405
                    );
406
                }
407
408
                $files[$fileInfo->getRealPath()] = [$fileInfo->getRealPath(), file_get_contents($file)];
409
410
                return $files;
411
            },
412
            []
413
        );
414
    }
415
}
416