Issues (163)

src/Configuration/ConfigurationFactory.php (19 issues)

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\Configuration;
16
17
use Humbug\PhpScoper\Patcher\ComposerPatcher;
18
use Humbug\PhpScoper\Patcher\Patcher;
19
use Humbug\PhpScoper\Patcher\PatcherChain;
20
use Humbug\PhpScoper\Patcher\SymfonyPatcher;
21
use InvalidArgumentException;
22
use RuntimeException;
23
use SplFileInfo;
24
use Symfony\Component\Filesystem\Filesystem;
25
use Symfony\Component\Finder\Finder;
0 ignored issues
show
The type Symfony\Component\Finder\Finder was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
use function array_filter;
27
use function array_key_exists;
28
use function array_keys;
29
use function array_map;
30
use function array_merge;
31
use function array_unique;
32
use function array_unshift;
33
use function bin2hex;
34
use function dirname;
35
use function file_exists;
36
use function gettype;
37
use function Humbug\PhpScoper\chain;
38
use function in_array;
39
use function is_array;
40
use function is_callable;
41
use function is_dir;
42
use function is_file;
43
use function is_link;
44
use function is_readable;
45
use function is_string;
46
use function random_bytes;
47
use function readlink as native_readlink;
48
use function realpath;
49
use function Safe\file_get_contents;
50
use function Safe\sprintf;
51
use function trim;
52
use const DIRECTORY_SEPARATOR;
53
use const SORT_STRING;
54
55
final class ConfigurationFactory
56
{
57
    public const DEFAULT_FILE_NAME = 'scoper.inc.php';
58
59
    private Filesystem $fileSystem;
60
    private SymbolsConfigurationFactory $configurationWhitelistFactory;
61
62
    public function __construct(
63
        Filesystem $fileSystem,
64
        SymbolsConfigurationFactory $configurationWhitelistFactory
65
    ) {
66
        $this->fileSystem = $fileSystem;
67
        $this->configurationWhitelistFactory = $configurationWhitelistFactory;
68
    }
69
70
    /**
71
     * @param non-empty-string|null  $path  Absolute canonical path to the configuration file.
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string|null at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string|null.
Loading history...
72
     * @param list<non-empty-string> $paths List of absolute canonical paths to append besides the one configured
73
     */
74
    public function create(?string $path = null, array $paths = []): Configuration
75
    {
76
        if (null === $path) {
77
            $config = [];
78
        } else {
79
            $config = $this->loadConfigFile($path);
80
        }
81
82
        self::validateConfigKeys($config);
83
84
        $prefix = self::retrievePrefix($config);
85
86
        $excludedFiles = null === $path
87
            ? []
88
            : $this->retrieveExcludedFiles(
89
                dirname($path),
90
                $config,
91
            );
92
93
        $patchers = self::retrievePatchers($config);
94
95
        array_unshift($patchers, new SymfonyPatcher());
96
        array_unshift($patchers, new ComposerPatcher());
97
98
        $symbolsConfiguration = $this->configurationWhitelistFactory->createSymbolsConfiguration($config);
99
100
        $finders = self::retrieveFinders($config);
101
        $filesFromPaths = self::retrieveFilesFromPaths($paths);
102
        $filesWithContents = self::retrieveFilesWithContents(chain($filesFromPaths, ...$finders));
103
104
        return new Configuration(
105
            $path,
106
            $prefix,
107
            $filesWithContents,
108
            self::retrieveFilesWithContents($excludedFiles),
109
            new PatcherChain($patchers),
110
            $symbolsConfiguration,
111
        );
112
    }
113
114
    /**
115
     * @param string[] $paths
116
     */
117
    public function createWithPaths(Configuration $config, array $paths): Configuration
118
    {
119
        $filesWithContents = self::retrieveFilesWithContents(
120
            chain(
121
                self::retrieveFilesFromPaths(
122
                    array_unique($paths, SORT_STRING),
123
                ),
124
            ),
125
        );
126
127
        return new Configuration(
128
            $config->getPath(),
129
            $config->getPrefix(),
130
            array_merge(
131
                $config->getFilesWithContents(),
132
                $filesWithContents,
133
            ),
134
            $config->getExcludedFilesWithContents(),
135
            $config->getPatcher(),
136
            $config->getSymbolsConfiguration(),
137
        );
138
    }
139
140
    public function createWithPrefix(Configuration $config, string $prefix): Configuration
141
    {
142
        $prefix = self::retrievePrefix([ConfigurationKeys::PREFIX_KEYWORD => $prefix]);
143
144
        return new Configuration(
145
            $config->getPath(),
146
            $prefix,
147
            $config->getFilesWithContents(),
148
            $config->getExcludedFilesWithContents(),
149
            $config->getPatcher(),
150
            $config->getSymbolsConfiguration(),
151
        );
152
    }
153
154
    private function loadConfigFile(string $path): array
155
    {
156
        if (!$this->fileSystem->isAbsolutePath($path)) {
157
            throw new InvalidArgumentException(
158
                sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

158
                /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
159
                    'Expected the path of the configuration file to load to be an absolute path, got "%s" instead',
160
                    $path,
161
                ),
162
            );
163
        }
164
165
        if (!file_exists($path)) {
166
            throw new InvalidArgumentException(
167
                sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

167
                /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
168
                    'Expected the path of the configuration file to exists but the file "%s" could not be found',
169
                    $path,
170
                ),
171
            );
172
        }
173
174
        $isADirectoryLink = is_link($path)
175
            && false !== native_readlink($path)
176
            && is_file(native_readlink($path));
177
178
        if (!$isADirectoryLink && !is_file($path)) {
179
            throw new InvalidArgumentException(
180
                sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

180
                /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
181
                    'Expected the path of the configuration file to be a file but "%s" appears to be a directory.',
182
                    $path,
183
                ),
184
            );
185
        }
186
187
        $config = include $path;
188
189
        if (!is_array($config)) {
190
            throw new InvalidArgumentException(
191
                sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

191
                /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
192
                    'Expected configuration to be an array, found "%s" instead.',
193
                    gettype($config),
194
                ),
195
            );
196
        }
197
198
        return $config;
199
    }
200
201
    private static function validateConfigKeys(array $config): void
202
    {
203
        array_map(
204
            static fn (string $key) => self::validateConfigKey($key),
205
            array_keys($config),
206
        );
207
    }
208
209
    private static function validateConfigKey(string $key): void
210
    {
211
        if (in_array($key, ConfigurationKeys::KEYWORDS, true)) {
212
            return;
213
        }
214
215
        throw new InvalidArgumentException(
216
            sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

216
            /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
217
                'Invalid configuration key value "%s" found.',
218
                $key,
219
            ),
220
        );
221
    }
222
223
    /**
224
     * @return non-empty-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
225
     */
226
    private static function retrievePrefix(array $config): string
227
    {
228
        $prefix = trim((string) ($config[ConfigurationKeys::PREFIX_KEYWORD] ?? ''));
229
230
        return '' === $prefix ? self::generateRandomPrefix() : $prefix;
231
    }
232
233
    /**
234
     * @return array<(callable(string,string,string): string)|Patcher>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<(callable(string,s...ring): string)|Patcher> at position 3 could not be parsed: Expected ')' at position 3, but found 'callable'.
Loading history...
235
     */
236
    private static function retrievePatchers(array $config): array
237
    {
238
        if (!array_key_exists(ConfigurationKeys::PATCHERS_KEYWORD, $config)) {
239
            return [];
240
        }
241
242
        $patchers = $config[ConfigurationKeys::PATCHERS_KEYWORD];
243
244
        if (!is_array($patchers)) {
245
            throw new InvalidArgumentException(
246
                sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

246
                /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
247
                    'Expected patchers to be an array of callables, found "%s" instead.',
248
                    gettype($patchers),
249
                ),
250
            );
251
        }
252
253
        foreach ($patchers as $index => $patcher) {
254
            if (is_callable($patcher)) {
255
                continue;
256
            }
257
258
            throw new InvalidArgumentException(
259
                sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

259
                /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
260
                    'Expected patchers to be an array of callables, the "%d" element is not.',
261
                    $index,
262
                ),
263
            );
264
        }
265
266
        return $patchers;
267
    }
268
269
    /**
270
     * @return string[] Absolute paths
271
     */
272
    private function retrieveExcludedFiles(string $dirPath, array $config): array
273
    {
274
        if (!array_key_exists(ConfigurationKeys::EXCLUDED_FILES_KEYWORD, $config)) {
275
            return [];
276
        }
277
278
        $excludedFiles = $config[ConfigurationKeys::EXCLUDED_FILES_KEYWORD];
279
280
        if (!is_array($excludedFiles)) {
281
            throw new InvalidArgumentException(
282
                sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

282
                /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
283
                    'Expected excluded files to be an array of strings, found "%s" instead.',
284
                    gettype($excludedFiles),
285
                ),
286
            );
287
        }
288
289
        foreach ($excludedFiles as $index => $file) {
290
            if (!is_string($file)) {
291
                throw new InvalidArgumentException(
292
                    sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

292
                    /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
293
                        'Expected excluded files to be an array of string, the "%d" element is not.',
294
                        $index,
295
                    ),
296
                );
297
            }
298
299
            if (!$this->fileSystem->isAbsolutePath($file)) {
300
                $file = $dirPath.DIRECTORY_SEPARATOR.$file;
301
            }
302
303
            $excludedFiles[$index] = realpath($file);
304
        }
305
306
        return array_filter($excludedFiles);
307
    }
308
309
    /**
310
     * @return Finder[]
311
     */
312
    private static function retrieveFinders(array $config): array
313
    {
314
        if (!array_key_exists(ConfigurationKeys::FINDER_KEYWORD, $config)) {
315
            return [];
316
        }
317
318
        $finders = $config[ConfigurationKeys::FINDER_KEYWORD];
319
320
        if (!is_array($finders)) {
321
            throw new InvalidArgumentException(
322
                sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

322
                /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
323
                    'Expected finders to be an array of "%s", found "%s" instead.',
324
                    Finder::class,
325
                    gettype($finders),
326
                ),
327
            );
328
        }
329
330
        foreach ($finders as $index => $finder) {
331
            if ($finder instanceof Finder) {
332
                continue;
333
            }
334
335
            throw new InvalidArgumentException(
336
                sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

336
                /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
337
                    'Expected finders to be an array of "%s", the "%d" element is not.',
338
                    Finder::class,
339
                    $index,
340
                ),
341
            );
342
        }
343
344
        return $finders;
345
    }
346
347
    /**
348
     * @param string[] $paths
349
     *
350
     * @return iterable<SplFileInfo>
351
     */
352
    private static function retrieveFilesFromPaths(array $paths): iterable
353
    {
354
        if ([] === $paths) {
355
            return [];
356
        }
357
358
        $pathsToSearch = [];
359
        $filesToAppend = [];
360
361
        foreach ($paths as $path) {
362
            if (!file_exists($path)) {
363
                throw new RuntimeException(
364
                    sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

364
                    /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
365
                        'Could not find the file "%s".',
366
                        $path,
367
                    ),
368
                );
369
            }
370
371
            if (is_dir($path)) {
372
                $pathsToSearch[] = $path;
373
            } else {
374
                $filesToAppend[] = $path;
375
            }
376
        }
377
378
        $finder = new Finder();
379
380
        $finder->files()
381
            ->in($pathsToSearch)
382
            ->append($filesToAppend)
383
            ->filter(
384
                static fn (SplFileInfo $fileInfo) => $fileInfo->isLink() ? false : null,
385
            )
386
            ->sortByName();
387
388
        return $finder;
389
    }
390
391
    /**
392
     * @param iterable<SplFileInfo|string> $files
393
     *
394
     * @return array<string, array{string, string}> Array of tuple with the first argument being the file path and the second its contents
395
     */
396
    private static function retrieveFilesWithContents(iterable $files): array
397
    {
398
        $filesWithContents = [];
399
400
        foreach ($files as $filePathOrFileInfo) {
401
            $filePath = $filePathOrFileInfo instanceof SplFileInfo
402
                ? $filePathOrFileInfo->getRealPath()
403
                : realpath($filePathOrFileInfo);
404
405
            if (!$filePath) {
406
                throw new RuntimeException(
407
                    sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

407
                    /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
408
                        'Could not find the file "%s".',
409
                        (string) $filePathOrFileInfo,
410
                    ),
411
                );
412
            }
413
414
            if (!is_readable($filePath)) {
415
                throw new RuntimeException(
416
                    sprintf(
0 ignored issues
show
Deprecated Code introduced by
The function Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+ ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

416
                    /** @scrutinizer ignore-deprecated */ sprintf(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
417
                        'Could not read the file "%s".',
418
                        $filePath,
419
                    ),
420
                );
421
            }
422
423
            $filesWithContents[$filePath] = [$filePath, file_get_contents($filePath)];
424
        }
425
426
        return $filesWithContents;
427
    }
428
429
    /**
430
     * @return non-empty-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
431
     */
432
    private static function generateRandomPrefix(): string
433
    {
434
        return '_PhpScoper'.bin2hex(random_bytes(6));
435
    }
436
}
437