Passed
Pull Request — master (#370)
by Théo
02:22
created

Configuration::retrieveFiles()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 51
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 25
dl 0
loc 51
rs 9.52
c 0
b 0
f 0
cc 4
nc 3
nop 6

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 box project.
7
 *
8
 * (c) Kevin Herrera <[email protected]>
9
 *     Théo Fidry <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace KevinGH\Box;
16
17
use function array_diff;
18
use function array_filter;
19
use function array_flip;
20
use function array_key_exists;
21
use function array_keys;
22
use function array_map;
23
use function array_merge;
24
use function array_unique;
25
use function array_values;
26
use function array_walk;
27
use Assert\Assertion;
28
use Closure;
29
use function constant;
30
use function current;
31
use DateTimeImmutable;
32
use DateTimeZone;
33
use function defined;
34
use function dirname;
35
use const E_USER_DEPRECATED;
36
use function explode;
37
use File;
38
use function file_exists;
39
use function getcwd;
40
use Herrera\Box\Compactor\Json as LegacyJson;
41
use Herrera\Box\Compactor\Php as LegacyPhp;
42
use Humbug\PhpScoper\Configuration as PhpScoperConfiguration;
43
use Humbug\PhpScoper\Console\ApplicationFactory;
44
use Humbug\PhpScoper\Scoper;
45
use Humbug\PhpScoper\Scoper\FileWhitelistScoper;
46
use function implode;
47
use function in_array;
48
use function intval;
49
use InvalidArgumentException;
50
use function is_array;
51
use function is_bool;
52
use function is_file;
53
use function is_link;
54
use function is_object;
55
use function is_readable;
56
use function is_string;
57
use function iter\map;
58
use function iter\toArray;
59
use function iter\values;
60
use KevinGH\Box\Annotation\AnnotationDumper;
61
use KevinGH\Box\Annotation\DocblockAnnotationParser;
62
use KevinGH\Box\Annotation\DocblockParser;
63
use KevinGH\Box\Compactor\Json as JsonCompactor;
64
use KevinGH\Box\Compactor\Php as PhpCompactor;
65
use KevinGH\Box\Compactor\PhpScoper as PhpScoperCompactor;
66
use KevinGH\Box\Composer\ComposerConfiguration;
67
use KevinGH\Box\Composer\ComposerFile;
68
use KevinGH\Box\Composer\ComposerFiles;
69
use function KevinGH\Box\FileSystem\canonicalize;
70
use function KevinGH\Box\FileSystem\file_contents;
71
use function KevinGH\Box\FileSystem\is_absolute_path;
72
use function KevinGH\Box\FileSystem\longest_common_base_path;
73
use function KevinGH\Box\FileSystem\make_path_absolute;
74
use function KevinGH\Box\FileSystem\make_path_relative;
75
use KevinGH\Box\Json\Json;
76
use KevinGH\Box\PhpScoper\SimpleScoper;
77
use function krsort;
78
use Phar;
79
use function preg_match;
80
use function preg_replace;
81
use function property_exists;
82
use function realpath;
83
use RuntimeException;
84
use Seld\JsonLint\ParsingException;
85
use function sort;
86
use const SORT_STRING;
87
use SplFileInfo;
88
use function sprintf;
89
use stdClass;
90
use function strtoupper;
91
use function substr;
92
use Symfony\Component\Finder\Finder;
93
use Symfony\Component\Finder\SplFileInfo as SymfonySplFileInfo;
94
use Symfony\Component\Process\Process;
95
use Symfony\Component\VarDumper\Cloner\VarCloner;
96
use Symfony\Component\VarDumper\Dumper\CliDumper;
97
use function trigger_error;
98
use function trim;
99
100
/**
101
 * @private
102
 */
103
final class Configuration
104
{
105
    private const DEFAULT_OUTPUT_FALLBACK = 'test.phar';
106
    private const DEFAULT_MAIN_SCRIPT = 'index.php';
107
    private const DEFAULT_DATETIME_FORMAT = 'Y-m-d H:i:s T';
108
    private const DEFAULT_REPLACEMENT_SIGIL = '@';
109
    private const DEFAULT_SHEBANG = '#!/usr/bin/env php';
110
    private const DEFAULT_BANNER = <<<'BANNER'
111
Generated by Humbug Box %s.
112
113
@link https://github.com/humbug/box
114
BANNER;
115
    private const FILES_SETTINGS = [
116
        'directories',
117
        'finder',
118
    ];
119
    private const PHP_SCOPER_CONFIG = 'scoper.inc.php';
120
    private const DEFAULT_SIGNING_ALGORITHM = Phar::SHA1;
121
    private const DEFAULT_ALIAS_PREFIX = 'box-auto-generated-alias-';
122
123
    private const DEFAULT_IGNORED_ANNOTATIONS = [
124
        'abstract',
125
        'access',
126
        'annotation',
127
        'api',
128
        'attribute',
129
        'attributes',
130
        'author',
131
        'category',
132
        'code',
133
        'codecoverageignore',
134
        'codecoverageignoreend',
135
        'codecoverageignorestart',
136
        'copyright',
137
        'deprec',
138
        'deprecated',
139
        'endcode',
140
        'example',
141
        'exception',
142
        'filesource',
143
        'final',
144
        'fixme',
145
        'global',
146
        'ignore',
147
        'ingroup',
148
        'inheritdoc',
149
        'internal',
150
        'license',
151
        'link',
152
        'magic',
153
        'method',
154
        'name',
155
        'override',
156
        'package',
157
        'package_version',
158
        'param',
159
        'private',
160
        'property',
161
        'required',
162
        'return',
163
        'see',
164
        'since',
165
        'static',
166
        'staticvar',
167
        'subpackage',
168
        'suppresswarnings',
169
        'target',
170
        'throw',
171
        'throws',
172
        'todo',
173
        'tutorial',
174
        'usedby',
175
        'uses',
176
        'var',
177
        'version',
178
    ];
179
180
    private const ALGORITHM_KEY = 'algorithm';
181
    private const ALIAS_KEY = 'alias';
182
    private const ANNOTATIONS_KEY = 'annotations';
183
    private const IGNORED_ANNOTATIONS_KEY = 'ignore';
184
    private const AUTO_DISCOVERY_KEY = 'force-autodiscovery';
185
    private const BANNER_KEY = 'banner';
186
    private const BANNER_FILE_KEY = 'banner-file';
187
    private const BASE_PATH_KEY = 'base-path';
188
    private const BLACKLIST_KEY = 'blacklist';
189
    private const CHECK_REQUIREMENTS_KEY = 'check-requirements';
190
    private const CHMOD_KEY = 'chmod';
191
    private const COMPACTORS_KEY = 'compactors';
192
    private const COMPRESSION_KEY = 'compression';
193
    private const DATETIME_KEY = 'datetime';
194
    private const DATETIME_FORMAT_KEY = 'datetime-format';
195
    private const DATETIME_FORMAT_DEPRECATED_KEY = 'datetime_format';
196
    private const DIRECTORIES_KEY = 'directories';
197
    private const DIRECTORIES_BIN_KEY = 'directories-bin';
198
    private const DUMP_AUTOLOAD_KEY = 'dump-autoload';
199
    private const EXCLUDE_COMPOSER_FILES_KEY = 'exclude-composer-files';
200
    private const EXCLUDE_DEV_FILES_KEY = 'exclude-dev-files';
201
    private const FILES_KEY = 'files';
202
    private const FILES_BIN_KEY = 'files-bin';
203
    private const FINDER_KEY = 'finder';
204
    private const FINDER_BIN_KEY = 'finder-bin';
205
    private const GIT_KEY = 'git';
206
    private const GIT_COMMIT_KEY = 'git-commit';
207
    private const GIT_COMMIT_SHORT_KEY = 'git-commit-short';
208
    private const GIT_TAG_KEY = 'git-tag';
209
    private const GIT_VERSION_KEY = 'git-version';
210
    private const INTERCEPT_KEY = 'intercept';
211
    private const KEY_KEY = 'key';
212
    private const KEY_PASS_KEY = 'key-pass';
213
    private const MAIN_KEY = 'main';
214
    private const MAP_KEY = 'map';
215
    private const METADATA_KEY = 'metadata';
216
    private const OUTPUT_KEY = 'output';
217
    private const PHP_SCOPER_KEY = 'php-scoper';
218
    private const REPLACEMENT_SIGIL_KEY = 'replacement-sigil';
219
    private const REPLACEMENTS_KEY = 'replacements';
220
    private const SHEBANG_KEY = 'shebang';
221
    private const STUB_KEY = 'stub';
222
223
    private $file;
224
    private $fileMode;
225
    private $alias;
226
    private $basePath;
227
    private $composerJson;
228
    private $composerLock;
229
    private $files;
230
    private $binaryFiles;
231
    private $autodiscoveredFiles;
232
    private $dumpAutoload;
233
    private $excludeComposerFiles;
234
    private $excludeDevFiles;
235
    private $compactors;
236
    private $compressionAlgorithm;
237
    private $mainScriptPath;
238
    private $mainScriptContents;
239
    private $fileMapper;
240
    private $metadata;
241
    private $tmpOutputPath;
242
    private $outputPath;
243
    private $privateKeyPassphrase;
244
    private $privateKeyPath;
245
    private $promptForPrivateKey;
246
    private $processedReplacements;
247
    private $shebang;
248
    private $signingAlgorithm;
249
    private $stubBannerContents;
250
    private $stubBannerPath;
251
    private $stubPath;
252
    private $isInterceptFileFuncs;
253
    private $isStubGenerated;
254
    private $checkRequirements;
255
    private $warnings;
256
    private $recommendations;
257
258
    public static function create(?string $file, stdClass $raw): self
259
    {
260
        $logger = new ConfigurationLogger();
261
262
        $basePath = self::retrieveBasePath($file, $raw, $logger);
263
264
        $composerFiles = self::retrieveComposerFiles($basePath);
265
266
        $dumpAutoload = self::retrieveDumpAutoload($raw, null !== $composerFiles->getComposerJson()->getPath(), $logger);
267
268
        if ($dumpAutoload && null !== $composerFiles->getInstalledJson()->getPath() && null === $composerFiles->getComposerLock()->getPath()) {
269
            $logger->addWarning(
270
                'A vendor/composer/installed.json file has been found but its related file composer.lock could not. '
271
                .'This is likely due to the file having been removed despite being necessary. This will not break the '
272
                .'build but the dump-autoload had to be disabled.'
273
            );
274
        }
275
276
        $excludeComposerFiles = self::retrieveExcludeComposerFiles($raw, $logger);
277
278
        $mainScriptPath = self::retrieveMainScriptPath($raw, $basePath, $composerFiles->getComposerJson()->getDecodedContents(), $logger);
279
        $mainScriptContents = self::retrieveMainScriptContents($mainScriptPath);
280
281
        [$tmpOutputPath, $outputPath] = self::retrieveOutputPath($raw, $basePath, $mainScriptPath, $logger);
282
283
        $stubPath = self::retrieveStubPath($raw, $basePath, $logger);
284
        $isStubGenerated = self::retrieveIsStubGenerated($raw, $stubPath, $logger);
285
286
        $alias = self::retrieveAlias($raw, null !== $stubPath, $logger);
287
288
        $shebang = self::retrieveShebang($raw, $isStubGenerated, $logger);
289
290
        $stubBannerContents = self::retrieveStubBannerContents($raw, $isStubGenerated, $logger);
291
        $stubBannerPath = self::retrieveStubBannerPath($raw, $basePath, $isStubGenerated, $logger);
292
293
        if (null !== $stubBannerPath) {
294
            $stubBannerContents = file_contents($stubBannerPath);
295
        }
296
297
        $stubBannerContents = self::normalizeStubBannerContents($stubBannerContents);
298
299
        if (null !== $stubBannerPath && self::getDefaultBanner() === $stubBannerContents) {
300
            self::addRecommendationForDefaultValue($logger, self::BANNER_FILE_KEY);
301
        }
302
303
        $isInterceptsFileFunctions = self::retrieveInterceptsFileFunctions($raw, $isStubGenerated, $logger);
304
305
        $checkRequirements = self::retrieveCheckRequirements(
306
            $raw,
307
            null !== $composerFiles->getComposerJson()->getPath(),
308
            null !== $composerFiles->getComposerLock()->getPath(),
309
            false === $isStubGenerated && null === $stubPath,
310
            $logger
311
        );
312
313
        $excludeDevPackages = self::retrieveExcludeDevFiles($raw, $dumpAutoload, $logger);
314
315
        $devPackages = ComposerConfiguration::retrieveDevPackages(
316
            $basePath,
317
            $composerFiles->getComposerJson()->getDecodedContents(),
318
            $composerFiles->getComposerLock()->getDecodedContents(),
319
            $excludeDevPackages
320
        );
321
322
        /**
323
         * @var string[]
324
         * @var Closure  $blacklistFilter
325
         */
326
        [$excludedPaths, $blacklistFilter] = self::retrieveBlacklistFilter(
327
            $raw,
328
            $basePath,
329
            $logger,
330
            $tmpOutputPath,
331
            $outputPath,
332
            $mainScriptPath
333
        );
334
        // Excluded paths above is a bit misleading since including a file directly has precedence over the blacklist.
335
        // If you consider the following:
336
        //
337
        // {
338
        //   "files": ["file1"],
339
        //   "blacklist": ["file1"],
340
        // }
341
        //
342
        // In the end the file "file1" _will_ be included: blacklist are here to help out to exclude files for finders
343
        // and directories but the user should always have the possibility to force his way to include a file.
344
        //
345
        // The exception however, is for the following which is essential for the good functioning of Box
346
        $alwaysExcludedPaths = array_map(
347
            static function (string $excludedPath) use ($basePath): string {
348
                return self::normalizePath($excludedPath, $basePath);
349
            },
350
            array_filter([$tmpOutputPath, $outputPath, $mainScriptPath])
351
        );
352
353
        $autodiscoverFiles = self::autodiscoverFiles($file, $raw);
354
        $forceFilesAutodiscovery = self::retrieveForceFilesAutodiscovery($raw, $logger);
355
356
        $filesAggregate = self::collectFiles(
357
            $raw,
358
            $basePath,
359
            $mainScriptPath,
360
            $blacklistFilter,
361
            $excludedPaths,
362
            $alwaysExcludedPaths,
363
            $devPackages,
364
            $composerFiles,
365
            $autodiscoverFiles,
366
            $forceFilesAutodiscovery,
367
            $dumpAutoload,
368
            $logger
369
        );
370
        $binaryFilesAggregate = self::collectBinaryFiles(
371
            $raw,
372
            $basePath,
373
            $blacklistFilter,
374
            $excludedPaths,
375
            $alwaysExcludedPaths,
376
            $devPackages,
377
            $logger
378
        );
379
380
        $compactors = self::retrieveCompactors($raw, $basePath, $logger);
381
        $compressionAlgorithm = self::retrieveCompressionAlgorithm($raw, $logger);
382
383
        $fileMode = self::retrieveFileMode($raw, $logger);
384
385
        $map = self::retrieveMap($raw, $logger);
386
        $fileMapper = new MapFile($basePath, $map);
387
388
        $metadata = self::retrieveMetadata($raw, $logger);
389
390
        $signingAlgorithm = self::retrieveSigningAlgorithm($raw, $logger);
391
        $promptForPrivateKey = self::retrievePromptForPrivateKey($raw, $signingAlgorithm, $logger);
392
        $privateKeyPath = self::retrievePrivateKeyPath($raw, $basePath, $signingAlgorithm, $logger);
393
        $privateKeyPassphrase = self::retrievePrivateKeyPassphrase($raw, $signingAlgorithm, $logger);
394
395
        $replacements = self::retrieveReplacements($raw, $file, $logger);
396
397
        return new self(
398
            $file,
399
            $alias,
400
            $basePath,
401
            $composerFiles->getComposerJson(),
402
            $composerFiles->getComposerLock(),
403
            $filesAggregate,
404
            $binaryFilesAggregate,
405
            $autodiscoverFiles || $forceFilesAutodiscovery,
406
            $dumpAutoload,
407
            $excludeComposerFiles,
408
            $excludeDevPackages,
409
            $compactors,
410
            $compressionAlgorithm,
411
            $fileMode,
412
            $mainScriptPath,
413
            $mainScriptContents,
414
            $fileMapper,
415
            $metadata,
416
            $tmpOutputPath,
417
            $outputPath,
418
            $privateKeyPassphrase,
419
            $privateKeyPath,
420
            $promptForPrivateKey,
421
            $replacements,
422
            $shebang,
423
            $signingAlgorithm,
424
            $stubBannerContents,
425
            $stubBannerPath,
426
            $stubPath,
427
            $isInterceptsFileFunctions,
428
            $isStubGenerated,
429
            $checkRequirements,
430
            $logger->getWarnings(),
431
            $logger->getRecommendations()
432
        );
433
    }
434
435
    /**
436
     * @param string        $basePath             Utility to private the base path used and be able to retrieve a
437
     *                                            path relative to it (the base path)
438
     * @param array         $composerJson         The first element is the path to the `composer.json` file as a
439
     *                                            string and the second element its decoded contents as an
440
     *                                            associative array.
441
     * @param array         $composerLock         The first element is the path to the `composer.lock` file as a
442
     *                                            string and the second element its decoded contents as an
443
     *                                            associative array.
444
     * @param SplFileInfo[] $files                List of files
445
     * @param SplFileInfo[] $binaryFiles          List of binary files
446
     * @param bool          $dumpAutoload         Whether or not the Composer autoloader should be dumped
447
     * @param bool          $excludeComposerFiles Whether or not the Composer files composer.json, composer.lock and
448
     *                                            installed.json should be removed from the PHAR
449
     * @param Compactor[]   $compactors           List of file contents compactors
450
     * @param null|int      $compressionAlgorithm Compression algorithm constant value. See the \Phar class constants
451
     * @param null|int      $fileMode             File mode in octal form
452
     * @param string        $mainScriptPath       The main script file path
453
     * @param string        $mainScriptContents   The processed content of the main script file
454
     * @param MapFile       $fileMapper           Utility to map the files from outside and inside the PHAR
455
     * @param mixed         $metadata             The PHAR Metadata
456
     * @param bool          $promptForPrivateKey  If the user should be prompted for the private key passphrase
457
     * @param scalar[]      $replacements         The processed list of replacement placeholders and their values
458
     * @param null|string   $shebang              The shebang line
459
     * @param int           $signingAlgorithm     The PHAR siging algorithm. See \Phar constants
460
     * @param null|string   $stubBannerContents   The stub banner comment
461
     * @param null|string   $stubBannerPath       The path to the stub banner comment file
462
     * @param null|string   $stubPath             The PHAR stub file path
463
     * @param bool          $isInterceptFileFuncs Whether or not Phar::interceptFileFuncs() should be used
464
     * @param bool          $isStubGenerated      Whether or not if the PHAR stub should be generated
465
     * @param bool          $checkRequirements    Whether the PHAR will check the application requirements before
466
     *                                            running
467
     * @param string[]      $warnings
468
     * @param string[]      $recommendations
469
     */
470
    private function __construct(
471
        ?string $file,
472
        string $alias,
473
        string $basePath,
474
        ComposerFile $composerJson,
475
        ComposerFile $composerLock,
476
        array $files,
477
        array $binaryFiles,
478
        bool $autodiscoveredFiles,
479
        bool $dumpAutoload,
480
        bool $excludeComposerFiles,
481
        bool $excludeDevPackages,
482
        array $compactors,
483
        ?int $compressionAlgorithm,
484
        ?int $fileMode,
485
        ?string $mainScriptPath,
486
        ?string $mainScriptContents,
487
        MapFile $fileMapper,
488
        $metadata,
489
        string $tmpOutputPath,
490
        string $outputPath,
491
        ?string $privateKeyPassphrase,
492
        ?string $privateKeyPath,
493
        bool $promptForPrivateKey,
494
        array $replacements,
495
        ?string $shebang,
496
        int $signingAlgorithm,
497
        ?string $stubBannerContents,
498
        ?string $stubBannerPath,
499
        ?string $stubPath,
500
        bool $isInterceptFileFuncs,
501
        bool $isStubGenerated,
502
        bool $checkRequirements,
503
        array $warnings,
504
        array $recommendations
505
    ) {
506
        Assertion::nullOrInArray(
507
            $compressionAlgorithm,
508
            get_phar_compression_algorithms(),
509
            sprintf(
510
                'Invalid compression algorithm "%%s", use one of "%s" instead.',
511
                implode('", "', array_keys(get_phar_compression_algorithms()))
512
            )
513
        );
514
515
        if (null === $mainScriptPath) {
516
            Assertion::null($mainScriptContents);
517
        } else {
518
            Assertion::notNull($mainScriptContents);
519
        }
520
521
        $this->file = $file;
522
        $this->alias = $alias;
523
        $this->basePath = $basePath;
524
        $this->composerJson = $composerJson;
525
        $this->composerLock = $composerLock;
526
        $this->files = $files;
527
        $this->binaryFiles = $binaryFiles;
528
        $this->autodiscoveredFiles = $autodiscoveredFiles;
529
        $this->dumpAutoload = $dumpAutoload;
530
        $this->excludeComposerFiles = $excludeComposerFiles;
531
        $this->excludeDevFiles = $excludeDevPackages;
532
        $this->compactors = $compactors;
533
        $this->compressionAlgorithm = $compressionAlgorithm;
534
        $this->fileMode = $fileMode;
535
        $this->mainScriptPath = $mainScriptPath;
536
        $this->mainScriptContents = $mainScriptContents;
537
        $this->fileMapper = $fileMapper;
538
        $this->metadata = $metadata;
539
        $this->tmpOutputPath = $tmpOutputPath;
540
        $this->outputPath = $outputPath;
541
        $this->privateKeyPassphrase = $privateKeyPassphrase;
542
        $this->privateKeyPath = $privateKeyPath;
543
        $this->promptForPrivateKey = $promptForPrivateKey;
544
        $this->processedReplacements = $replacements;
545
        $this->shebang = $shebang;
546
        $this->signingAlgorithm = $signingAlgorithm;
547
        $this->stubBannerContents = $stubBannerContents;
548
        $this->stubBannerPath = $stubBannerPath;
549
        $this->stubPath = $stubPath;
550
        $this->isInterceptFileFuncs = $isInterceptFileFuncs;
551
        $this->isStubGenerated = $isStubGenerated;
552
        $this->checkRequirements = $checkRequirements;
553
        $this->warnings = $warnings;
554
        $this->recommendations = $recommendations;
555
    }
556
557
    public function export(): string
558
    {
559
        $exportedConfig = clone $this;
560
561
        $basePath = $exportedConfig->basePath;
562
563
        /**
564
         * @param null|SplFileInfo|string $path
565
         */
566
        $normalizePath = static function ($path) use ($basePath): ?string {
567
            if (null === $path) {
568
                return null;
569
            }
570
571
            if ($path instanceof SplFileInfo) {
572
                $path = $path->getPathname();
573
            }
574
575
            return make_path_relative($path, $basePath);
576
        };
577
578
        $normalizeProperty = static function (&$property) use ($normalizePath): void {
579
            $property = $normalizePath($property);
580
        };
581
582
        $normalizeFiles = static function (&$files) use ($normalizePath): void {
583
            $files = array_map($normalizePath, $files);
584
            sort($files, SORT_STRING);
585
        };
586
587
        $normalizeFiles($exportedConfig->files);
588
        $normalizeFiles($exportedConfig->binaryFiles);
589
590
        $exportedConfig->composerJson = new ComposerFile(
591
            $normalizePath($exportedConfig->composerJson->getPath()),
592
            $exportedConfig->composerJson->getDecodedContents()
593
        );
594
        $exportedConfig->composerLock = new ComposerFile(
595
            $normalizePath($exportedConfig->composerLock->getPath()),
596
            $exportedConfig->composerLock->getDecodedContents()
597
        );
598
599
        $normalizeProperty($exportedConfig->file);
600
        $normalizeProperty($exportedConfig->mainScriptPath);
601
        $normalizeProperty($exportedConfig->tmpOutputPath);
602
        $normalizeProperty($exportedConfig->outputPath);
603
        $normalizeProperty($exportedConfig->privateKeyPath);
604
        $normalizeProperty($exportedConfig->stubBannerPath);
605
        $normalizeProperty($exportedConfig->stubPath);
606
607
        $exportedConfig->compressionAlgorithm = array_flip(get_phar_compression_algorithms())[$exportedConfig->compressionAlgorithm ?? Phar::NONE];
608
        $exportedConfig->signingAlgorithm = array_flip(get_phar_signing_algorithms())[$exportedConfig->signingAlgorithm];
609
        $exportedConfig->compactors = array_map('get_class', $exportedConfig->compactors);
610
        $exportedConfig->fileMode = '0'.decoct($exportedConfig->fileMode);
611
612
        $cloner = new VarCloner();
613
        $cloner->setMaxItems(-1);
614
        $cloner->setMaxString(-1);
615
616
        $splInfoCaster = static function (SplFileInfo $fileInfo) use ($normalizePath): array {
617
            return [$normalizePath($fileInfo)];
618
        };
619
620
        $cloner->addCasters([
621
            SplFileInfo::class => $splInfoCaster,
622
            SymfonySplFileInfo::class => $splInfoCaster,
623
        ]);
624
625
        return (new CliDumper())->dump(
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Symfony\Compo...$exportedConfig), true) could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
626
            $cloner->cloneVar($exportedConfig),
627
            true
628
        );
629
    }
630
631
    public function getConfigurationFile(): ?string
632
    {
633
        return $this->file;
634
    }
635
636
    public function getAlias(): string
637
    {
638
        return $this->alias;
639
    }
640
641
    public function getBasePath(): string
642
    {
643
        return $this->basePath;
644
    }
645
646
    public function getComposerJson(): ?string
647
    {
648
        return $this->composerJson->getPath();
649
    }
650
651
    public function getDecodedComposerJsonContents(): ?array
652
    {
653
        return null === $this->composerJson->getPath() ? null : $this->composerJson->getDecodedContents();
654
    }
655
656
    public function getComposerLock(): ?string
657
    {
658
        return $this->composerLock->getPath();
659
    }
660
661
    public function getDecodedComposerLockContents(): ?array
662
    {
663
        return null === $this->composerLock->getPath() ? null : $this->composerLock->getDecodedContents();
664
    }
665
666
    /**
667
     * @return SplFileInfo[]
668
     */
669
    public function getFiles(): array
670
    {
671
        return $this->files;
672
    }
673
674
    /**
675
     * @return SplFileInfo[]
676
     */
677
    public function getBinaryFiles(): array
678
    {
679
        return $this->binaryFiles;
680
    }
681
682
    public function hasAutodiscoveredFiles(): bool
683
    {
684
        return $this->autodiscoveredFiles;
685
    }
686
687
    public function dumpAutoload(): bool
688
    {
689
        return $this->dumpAutoload;
690
    }
691
692
    public function excludeComposerFiles(): bool
693
    {
694
        return $this->excludeComposerFiles;
695
    }
696
697
    public function excludeDevFiles(): bool
698
    {
699
        return $this->excludeDevFiles;
700
    }
701
702
    /**
703
     * @return Compactor[] the list of compactors
704
     */
705
    public function getCompactors(): array
706
    {
707
        return $this->compactors;
708
    }
709
710
    public function getCompressionAlgorithm(): ?int
711
    {
712
        return $this->compressionAlgorithm;
713
    }
714
715
    public function getFileMode(): ?int
716
    {
717
        return $this->fileMode;
718
    }
719
720
    public function hasMainScript(): bool
721
    {
722
        return null !== $this->mainScriptPath;
723
    }
724
725
    public function getMainScriptPath(): string
726
    {
727
        Assertion::notNull(
728
            $this->mainScriptPath,
729
            'Cannot retrieve the main script path: no main script configured.'
730
        );
731
732
        return $this->mainScriptPath;
733
    }
734
735
    public function getMainScriptContents(): string
736
    {
737
        Assertion::notNull(
738
            $this->mainScriptPath,
739
            'Cannot retrieve the main script contents: no main script configured.'
740
        );
741
742
        return $this->mainScriptContents;
743
    }
744
745
    public function checkRequirements(): bool
746
    {
747
        return $this->checkRequirements;
748
    }
749
750
    public function getTmpOutputPath(): string
751
    {
752
        return $this->tmpOutputPath;
753
    }
754
755
    public function getOutputPath(): string
756
    {
757
        return $this->outputPath;
758
    }
759
760
    public function getFileMapper(): MapFile
761
    {
762
        return $this->fileMapper;
763
    }
764
765
    /**
766
     * @return mixed
767
     */
768
    public function getMetadata()
769
    {
770
        return $this->metadata;
771
    }
772
773
    public function getPrivateKeyPassphrase(): ?string
774
    {
775
        return $this->privateKeyPassphrase;
776
    }
777
778
    public function getPrivateKeyPath(): ?string
779
    {
780
        return $this->privateKeyPath;
781
    }
782
783
    /**
784
     * @deprecated Use promptForPrivateKey() instead
785
     */
786
    public function isPrivateKeyPrompt(): bool
787
    {
788
        return $this->promptForPrivateKey;
789
    }
790
791
    public function promptForPrivateKey(): bool
792
    {
793
        return $this->promptForPrivateKey;
794
    }
795
796
    /**
797
     * @return scalar[]
798
     */
799
    public function getReplacements(): array
800
    {
801
        return $this->processedReplacements;
802
    }
803
804
    public function getShebang(): ?string
805
    {
806
        return $this->shebang;
807
    }
808
809
    public function getSigningAlgorithm(): int
810
    {
811
        return $this->signingAlgorithm;
812
    }
813
814
    public function getStubBannerContents(): ?string
815
    {
816
        return $this->stubBannerContents;
817
    }
818
819
    public function getStubBannerPath(): ?string
820
    {
821
        return $this->stubBannerPath;
822
    }
823
824
    public function getStubPath(): ?string
825
    {
826
        return $this->stubPath;
827
    }
828
829
    public function isInterceptFileFuncs(): bool
830
    {
831
        return $this->isInterceptFileFuncs;
832
    }
833
834
    public function isStubGenerated(): bool
835
    {
836
        return $this->isStubGenerated;
837
    }
838
839
    /**
840
     * @return string[]
841
     */
842
    public function getWarnings(): array
843
    {
844
        return $this->warnings;
845
    }
846
847
    /**
848
     * @return string[]
849
     */
850
    public function getRecommendations(): array
851
    {
852
        return $this->recommendations;
853
    }
854
855
    private static function retrieveAlias(stdClass $raw, bool $userStubUsed, ConfigurationLogger $logger): string
856
    {
857
        self::checkIfDefaultValue($logger, $raw, self::ALIAS_KEY);
858
859
        if (false === isset($raw->{self::ALIAS_KEY})) {
860
            return unique_id(self::DEFAULT_ALIAS_PREFIX).'.phar';
861
        }
862
863
        $alias = trim($raw->{self::ALIAS_KEY});
864
865
        Assertion::notEmpty($alias, 'A PHAR alias cannot be empty when provided.');
866
867
        if ($userStubUsed) {
868
            $logger->addWarning(
869
                sprintf(
870
                    'The "%s" setting has been set but is ignored since a custom stub path is used',
871
                    self::ALIAS_KEY
872
                )
873
            );
874
        }
875
876
        return $alias;
877
    }
878
879
    private static function retrieveBasePath(?string $file, stdClass $raw, ConfigurationLogger $logger): string
880
    {
881
        if (null === $file) {
882
            return getcwd();
883
        }
884
885
        if (false === isset($raw->{self::BASE_PATH_KEY})) {
886
            return realpath(dirname($file));
887
        }
888
889
        $basePath = trim($raw->{self::BASE_PATH_KEY});
890
891
        Assertion::directory(
892
            $basePath,
893
            'The base path "%s" is not a directory or does not exist.'
894
        );
895
896
        $basePath = realpath($basePath);
897
        $defaultPath = realpath(dirname($file));
898
899
        if ($basePath === $defaultPath) {
900
            self::addRecommendationForDefaultValue($logger, self::BASE_PATH_KEY);
901
        }
902
903
        return $basePath;
904
    }
905
906
    /**
907
     * Checks if files should be auto-discovered. It does NOT account for the force-autodiscovery setting.
908
     */
909
    private static function autodiscoverFiles(?string $file, stdClass $raw): bool
910
    {
911
        if (null === $file) {
912
            return true;
913
        }
914
915
        $associativeRaw = (array) $raw;
916
917
        return self::FILES_SETTINGS === array_diff(self::FILES_SETTINGS, array_keys($associativeRaw));
918
    }
919
920
    private static function retrieveForceFilesAutodiscovery(stdClass $raw, ConfigurationLogger $logger): bool
921
    {
922
        self::checkIfDefaultValue($logger, $raw, self::AUTO_DISCOVERY_KEY, false);
923
924
        return $raw->{self::AUTO_DISCOVERY_KEY} ?? false;
925
    }
926
927
    private static function retrieveBlacklistFilter(
928
        stdClass $raw,
929
        string $basePath,
930
        ConfigurationLogger $logger,
931
        ?string ...$excludedPaths
932
    ): array {
933
        $blacklist = array_flip(
934
            self::retrieveBlacklist($raw, $basePath, $logger, ...$excludedPaths)
935
        );
936
937
        $blacklistFilter = static function (SplFileInfo $file) use ($blacklist): ?bool {
938
            if ($file->isLink()) {
939
                return false;
940
            }
941
942
            if (false === $file->getRealPath()) {
943
                return false;
944
            }
945
946
            if (array_key_exists($file->getRealPath(), $blacklist)) {
947
                return false;
948
            }
949
950
            return null;
951
        };
952
953
        return [array_keys($blacklist), $blacklistFilter];
954
    }
955
956
    /**
957
     * @param null[]|string[] $excludedPaths
958
     *
959
     * @return string[]
960
     */
961
    private static function retrieveBlacklist(
962
        stdClass $raw,
963
        string $basePath,
964
        ConfigurationLogger $logger,
965
        ?string ...$excludedPaths
966
    ): array {
967
        self::checkIfDefaultValue($logger, $raw, self::BLACKLIST_KEY, []);
968
969
        $normalizedBlacklist = array_map(
970
            static function (string $excludedPath) use ($basePath): string {
971
                return self::normalizePath($excludedPath, $basePath);
972
            },
973
            array_filter($excludedPaths)
974
        );
975
976
        /** @var string[] $blacklist */
977
        $blacklist = $raw->{self::BLACKLIST_KEY} ?? [];
978
979
        foreach ($blacklist as $file) {
980
            $normalizedBlacklist[] = self::normalizePath($file, $basePath);
981
            $normalizedBlacklist[] = canonicalize(make_path_relative(trim($file), $basePath));
982
        }
983
984
        return array_unique($normalizedBlacklist);
985
    }
986
987
    /**
988
     * @param string[] $excludedPaths
989
     * @param string[] $alwaysExcludedPaths
990
     * @param string[] $devPackages
991
     *
992
     * @return SplFileInfo[]
993
     */
994
    private static function collectFiles(
995
        stdClass $raw,
996
        string $basePath,
997
        ?string $mainScriptPath,
998
        Closure $blacklistFilter,
999
        array $excludedPaths,
1000
        array $alwaysExcludedPaths,
1001
        array $devPackages,
1002
        ComposerFiles $composerFiles,
1003
        bool $autodiscoverFiles,
1004
        bool $forceFilesAutodiscovery,
1005
        bool $dumpAutoload,
1006
        ConfigurationLogger $logger
1007
    ): array {
1008
        $files = [self::retrieveFiles($raw, self::FILES_KEY, $basePath, $composerFiles, $alwaysExcludedPaths, $logger)];
1009
1010
        if ($autodiscoverFiles || $forceFilesAutodiscovery) {
1011
            [$filesToAppend, $directories] = self::retrieveAllDirectoriesToInclude(
1012
                $basePath,
1013
                $composerFiles->getComposerJson()->getDecodedContents(),
1014
                $devPackages,
1015
                $composerFiles->getPaths(),
1016
                $excludedPaths
1017
            );
1018
1019
            $files[] = self::wrapInSplFileInfo($filesToAppend);
1020
1021
            $files[] = self::retrieveAllFiles(
1022
                $basePath,
1023
                $directories,
1024
                $mainScriptPath,
1025
                $blacklistFilter,
1026
                $excludedPaths,
1027
                $devPackages
1028
            );
1029
        }
1030
1031
        if (false === $autodiscoverFiles) {
1032
            $files[] = self::retrieveDirectories(
1033
                $raw,
1034
                self::DIRECTORIES_KEY,
1035
                $basePath,
1036
                $blacklistFilter,
1037
                $excludedPaths,
1038
                $logger
1039
            );
1040
1041
            $filesFromFinders = self::retrieveFilesFromFinders(
1042
                $raw,
1043
                self::FINDER_KEY,
1044
                $basePath,
1045
                $blacklistFilter,
1046
                $devPackages,
1047
                $logger
1048
            );
1049
1050
            foreach ($filesFromFinders as $filesFromFinder) {
1051
                // Avoid an array_merge here as it can be quite expansive at this stage depending of the number of files
1052
                $files[] = $filesFromFinder;
1053
            }
1054
        }
1055
1056
        $aggregate = self::retrieveFilesAggregate(...$files);
1057
1058
        if (false === $dumpAutoload) {
1059
            return $aggregate;
1060
        }
1061
1062
        if (null === $composerFiles->getComposerLock()->getPath()) {
1063
            return $aggregate;
1064
        }
1065
1066
        foreach ($aggregate as $file) {
1067
            if ($file->getRealPath() === $composerFiles->getInstalledJson()->getPath()) {
1068
                return $aggregate;
1069
            }
1070
        }
1071
1072
        $logger->addWarning(
1073
            'A composer.lock file has been found but its related file vendor/composer/installed.json could not. This '
1074
            .'could be due to either dependencies incorrectly installed or an incorrect Box configuration which is not '
1075
            .'including the installed.json file. This will not break the build but will likely result in a broken '
1076
            .'Composer classmap.'
1077
        );
1078
1079
        return $aggregate;
1080
    }
1081
1082
    /**
1083
     * @param string[] $excludedPaths
1084
     * @param string[] $alwaysExcludedPaths
1085
     * @param string[] $devPackages
1086
     *
1087
     * @return SplFileInfo[]
1088
     */
1089
    private static function collectBinaryFiles(
1090
        stdClass $raw,
1091
        string $basePath,
1092
        Closure $blacklistFilter,
1093
        array $excludedPaths,
1094
        array $alwaysExcludedPaths,
1095
        array $devPackages,
1096
        ConfigurationLogger $logger
1097
    ): array {
1098
        $binaryFiles = self::retrieveFiles($raw, self::FILES_BIN_KEY, $basePath, ComposerFiles::createEmpty(), $alwaysExcludedPaths, $logger);
1099
1100
        $binaryDirectories = self::retrieveDirectories(
1101
            $raw,
1102
            self::DIRECTORIES_BIN_KEY,
1103
            $basePath,
1104
            $blacklistFilter,
1105
            $excludedPaths,
1106
            $logger
1107
        );
1108
1109
        $binaryFilesFromFinders = self::retrieveFilesFromFinders(
1110
            $raw,
1111
            self::FINDER_BIN_KEY,
1112
            $basePath,
1113
            $blacklistFilter,
1114
            $devPackages,
1115
            $logger
1116
        );
1117
1118
        return self::retrieveFilesAggregate($binaryFiles, $binaryDirectories, ...$binaryFilesFromFinders);
1119
    }
1120
1121
    /**
1122
     * @param string[] $excludedFiles
1123
     *
1124
     * @return SplFileInfo[]
1125
     */
1126
    private static function retrieveFiles(
1127
        stdClass $raw,
1128
        string $key,
1129
        string $basePath,
1130
        ComposerFiles $composerFiles,
1131
        array $excludedFiles,
1132
        ConfigurationLogger $logger
1133
    ): array {
1134
        self::checkIfDefaultValue($logger, $raw, $key, []);
1135
1136
        $excludedFiles = array_flip($excludedFiles);
1137
        $files = array_filter([
1138
            $composerFiles->getComposerJson()->getPath(),
1139
            $composerFiles->getComposerLock()->getPath(),
1140
        ]);
1141
1142
        if (false === isset($raw->{$key})) {
1143
            return self::wrapInSplFileInfo($files);
1144
        }
1145
1146
        if ([] === (array) $raw->{$key}) {
1147
            return self::wrapInSplFileInfo($files);
1148
        }
1149
1150
        $files = array_merge((array) $raw->{$key}, $files);
1151
1152
        Assertion::allString($files);
1153
1154
        $normalizePath = static function (string $file) use ($basePath, $key, $excludedFiles): ?SplFileInfo {
1155
            $file = self::normalizePath($file, $basePath);
1156
1157
            Assertion::false(
1158
                is_link($file),
1159
                sprintf(
1160
                    'Cannot add the link "%s": links are not supported.',
1161
                    $file
1162
                )
1163
            );
1164
1165
            Assertion::file(
1166
                $file,
1167
                sprintf(
1168
                    '"%s" must contain a list of existing files. Could not find "%%s".',
1169
                    $key
1170
                )
1171
            );
1172
1173
            return array_key_exists($file, $excludedFiles) ? null : new SplFileInfo($file);
1174
        };
1175
1176
        return array_filter(array_map($normalizePath, $files));
1177
    }
1178
1179
    /**
1180
     * @param string   $key           Config property name
1181
     * @param string[] $excludedPaths
1182
     *
1183
     * @return iterable|SplFileInfo[]
1184
     */
1185
    private static function retrieveDirectories(
1186
        stdClass $raw,
1187
        string $key,
1188
        string $basePath,
1189
        Closure $blacklistFilter,
1190
        array $excludedPaths,
1191
        ConfigurationLogger $logger
1192
    ): iterable {
1193
        $directories = self::retrieveDirectoryPaths($raw, $key, $basePath, $logger);
1194
1195
        if ([] !== $directories) {
1196
            $finder = Finder::create()
1197
                ->files()
1198
                ->filter($blacklistFilter)
1199
                ->ignoreVCS(true)
1200
                ->in($directories)
1201
            ;
1202
1203
            foreach ($excludedPaths as $excludedPath) {
1204
                $finder->notPath($excludedPath);
1205
            }
1206
1207
            return $finder;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $finder returns the type Symfony\Component\Finder\Finder which is incompatible with the documented return type SplFileInfo[]|iterable.
Loading history...
1208
        }
1209
1210
        return [];
1211
    }
1212
1213
    /**
1214
     * @param string[] $devPackages
1215
     *
1216
     * @return iterable[]|SplFileInfo[][]
1217
     */
1218
    private static function retrieveFilesFromFinders(
1219
        stdClass $raw,
1220
        string $key,
1221
        string $basePath,
1222
        Closure $blacklistFilter,
1223
        array $devPackages,
1224
        ConfigurationLogger $logger
1225
    ): array {
1226
        self::checkIfDefaultValue($logger, $raw, $key, []);
1227
1228
        if (false === isset($raw->{$key})) {
1229
            return [];
1230
        }
1231
1232
        $finder = $raw->{$key};
1233
1234
        return self::processFinders($finder, $basePath, $blacklistFilter, $devPackages);
1235
    }
1236
1237
    /**
1238
     * @param iterable[]|SplFileInfo[][] $fileIterators
1239
     *
1240
     * @return SplFileInfo[]
1241
     */
1242
    private static function retrieveFilesAggregate(iterable ...$fileIterators): array
1243
    {
1244
        $files = [];
1245
1246
        foreach ($fileIterators as $fileIterator) {
1247
            foreach ($fileIterator as $file) {
1248
                $files[(string) $file] = $file;
1249
            }
1250
        }
1251
1252
        return array_values($files);
1253
    }
1254
1255
    /**
1256
     * @param string[] $devPackages
1257
     *
1258
     * @return Finder[]|SplFileInfo[][]
1259
     */
1260
    private static function processFinders(
1261
        array $findersConfig,
1262
        string $basePath,
1263
        Closure $blacklistFilter,
1264
        array $devPackages
1265
    ): array {
1266
        $processFinderConfig = static function (stdClass $config) use ($basePath, $blacklistFilter, $devPackages) {
1267
            return self::processFinder($config, $basePath, $blacklistFilter, $devPackages);
1268
        };
1269
1270
        return array_map($processFinderConfig, $findersConfig);
1271
    }
1272
1273
    /**
1274
     * @param string[] $devPackages
1275
     *
1276
     * @return Finder|SplFileInfo[]
1277
     */
1278
    private static function processFinder(
1279
        stdClass $config,
1280
        string $basePath,
1281
        Closure $blacklistFilter,
1282
        array $devPackages
1283
    ): Finder {
1284
        $finder = Finder::create()
1285
            ->files()
1286
            ->filter($blacklistFilter)
1287
            ->filter(
1288
                static function (SplFileInfo $fileInfo) use ($devPackages): bool {
1289
                    foreach ($devPackages as $devPackage) {
1290
                        if ($devPackage === longest_common_base_path([$devPackage, $fileInfo->getRealPath()])) {
1291
                            // File belongs to the dev package
1292
                            return false;
1293
                        }
1294
                    }
1295
1296
                    return true;
1297
                }
1298
            )
1299
            ->ignoreVCS(true)
1300
        ;
1301
1302
        $normalizedConfig = (static function (array $config, Finder $finder): array {
1303
            $normalizedConfig = [];
1304
1305
            foreach ($config as $method => $arguments) {
1306
                $method = trim($method);
1307
                $arguments = (array) $arguments;
1308
1309
                Assertion::methodExists(
1310
                    $method,
1311
                    $finder,
1312
                    'The method "Finder::%s" does not exist.'
1313
                );
1314
1315
                $normalizedConfig[$method] = $arguments;
1316
            }
1317
1318
            krsort($normalizedConfig);
1319
1320
            return $normalizedConfig;
1321
        })((array) $config, $finder);
1322
1323
        $createNormalizedDirectories = static function (string $directory) use ($basePath): ?string {
1324
            $directory = self::normalizePath($directory, $basePath);
1325
1326
            Assertion::false(
1327
                is_link($directory),
1328
                sprintf(
1329
                    'Cannot append the link "%s" to the Finder: links are not supported.',
1330
                    $directory
1331
                )
1332
            );
1333
1334
            Assertion::directory($directory);
1335
1336
            return $directory;
1337
        };
1338
1339
        $normalizeFileOrDirectory = static function (?string &$fileOrDirectory) use ($basePath, $blacklistFilter): void {
1340
            $fileOrDirectory = self::normalizePath($fileOrDirectory, $basePath);
0 ignored issues
show
Bug introduced by
It seems like $fileOrDirectory can also be of type null; however, parameter $file of KevinGH\Box\Configuration::normalizePath() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1340
            $fileOrDirectory = self::normalizePath(/** @scrutinizer ignore-type */ $fileOrDirectory, $basePath);
Loading history...
1341
1342
            Assertion::false(
1343
                is_link($fileOrDirectory),
1344
                sprintf(
1345
                    'Cannot append the link "%s" to the Finder: links are not supported.',
1346
                    $fileOrDirectory
1347
                )
1348
            );
1349
1350
            Assertion::true(
1351
                file_exists($fileOrDirectory),
1352
                sprintf(
1353
                    'Path "%s" was expected to be a file or directory. It may be a symlink (which are unsupported).',
1354
                    $fileOrDirectory
1355
                )
1356
            );
1357
1358
            if (false === is_file($fileOrDirectory)) {
1359
                Assertion::directory($fileOrDirectory);
1360
            } else {
1361
                Assertion::file($fileOrDirectory);
1362
            }
1363
1364
            if (false === $blacklistFilter(new SplFileInfo($fileOrDirectory))) {
1365
                $fileOrDirectory = null;
1366
            }
1367
        };
1368
1369
        foreach ($normalizedConfig as $method => $arguments) {
1370
            if ('in' === $method) {
1371
                $normalizedConfig[$method] = $arguments = array_map($createNormalizedDirectories, $arguments);
1372
            }
1373
1374
            if ('exclude' === $method) {
1375
                $arguments = array_unique(array_map('trim', $arguments));
1376
            }
1377
1378
            if ('append' === $method) {
1379
                array_walk($arguments, $normalizeFileOrDirectory);
1380
1381
                $arguments = [array_filter($arguments)];
1382
            }
1383
1384
            foreach ($arguments as $argument) {
1385
                $finder->$method($argument);
1386
            }
1387
        }
1388
1389
        return $finder;
1390
    }
1391
1392
    /**
1393
     * @param string[] $devPackages
1394
     * @param string[] $filesToAppend
1395
     *
1396
     * @return string[][]
1397
     */
1398
    private static function retrieveAllDirectoriesToInclude(
1399
        string $basePath,
1400
        ?array $decodedJsonContents,
1401
        array $devPackages,
1402
        array $filesToAppend,
1403
        array $excludedPaths
1404
    ): array {
1405
        $toString = static function ($file): string {
1406
            // @param string|SplFileInfo $file
1407
            return (string) $file;
1408
        };
1409
1410
        if (null !== $decodedJsonContents && array_key_exists('vendor-dir', $decodedJsonContents)) {
1411
            $vendorDir = self::normalizePath($decodedJsonContents['vendor-dir'], $basePath);
1412
        } else {
1413
            $vendorDir = self::normalizePath('vendor', $basePath);
1414
        }
1415
1416
        if (file_exists($vendorDir)) {
1417
            // The installed.json file is necessary for dumping the autoload correctly. Note however that it will not exists if no
1418
            // dependencies are included in the `composer.json`
1419
            $installedJsonFiles = self::normalizePath($vendorDir.'/composer/installed.json', $basePath);
1420
1421
            if (file_exists($installedJsonFiles)) {
1422
                $filesToAppend[] = $installedJsonFiles;
1423
            }
1424
1425
            $vendorPackages = toArray(values(map(
1426
                $toString,
1427
                Finder::create()
1428
                    ->in($vendorDir)
1429
                    ->directories()
1430
                    ->depth(1)
1431
                    ->ignoreUnreadableDirs()
1432
                    ->filter(
1433
                        static function (SplFileInfo $fileInfo): ?bool {
1434
                            if ($fileInfo->isLink()) {
1435
                                return false;
1436
                            }
1437
1438
                            return null;
1439
                        }
1440
                    )
1441
            )));
1442
1443
            $vendorPackages = array_diff($vendorPackages, $devPackages);
1444
1445
            if (null === $decodedJsonContents || false === array_key_exists('autoload', $decodedJsonContents)) {
1446
                $files = toArray(values(map(
1447
                    $toString,
1448
                    Finder::create()
1449
                        ->in($basePath)
1450
                        ->files()
1451
                        ->depth(0)
1452
                )));
1453
1454
                $directories = toArray(values(map(
1455
                    $toString,
1456
                    Finder::create()
1457
                        ->in($basePath)
1458
                        ->notPath('vendor')
1459
                        ->directories()
1460
                        ->depth(0)
1461
                )));
1462
1463
                return [
1464
                    array_merge($files, $filesToAppend),
1465
                    array_merge($directories, $vendorPackages),
1466
                ];
1467
            }
1468
1469
            $paths = $vendorPackages;
1470
        } else {
1471
            $paths = [];
1472
        }
1473
1474
        $autoload = $decodedJsonContents['autoload'] ?? [];
1475
1476
        if (array_key_exists('psr-4', $autoload)) {
1477
            foreach ($autoload['psr-4'] as $path) {
1478
                /** @var string|string[] $path */
1479
                $composerPaths = (array) $path;
1480
1481
                foreach ($composerPaths as $composerPath) {
1482
                    $paths[] = '' !== trim($composerPath) ? $composerPath : $basePath;
1483
                }
1484
            }
1485
        }
1486
1487
        if (array_key_exists('psr-0', $autoload)) {
1488
            foreach ($autoload['psr-0'] as $path) {
1489
                /** @var string|string[] $path */
1490
                $composerPaths = (array) $path;
1491
1492
                foreach ($composerPaths as $composerPath) {
1493
                    $paths[] = '' !== trim($composerPath) ? $composerPath : $basePath;
1494
                }
1495
            }
1496
        }
1497
1498
        if (array_key_exists('classmap', $autoload)) {
1499
            foreach ($autoload['classmap'] as $path) {
1500
                // @var string $path
1501
                $paths[] = $path;
1502
            }
1503
        }
1504
1505
        $normalizePath = static function (string $path) use ($basePath): string {
1506
            return is_absolute_path($path)
1507
                ? canonicalize($path)
1508
                : self::normalizePath(trim($path, '/ '), $basePath)
1509
            ;
1510
        };
1511
1512
        if (array_key_exists('files', $autoload)) {
1513
            foreach ($autoload['files'] as $path) {
1514
                // @var string $path
1515
                $path = $normalizePath($path);
1516
1517
                Assertion::file($path);
1518
                Assertion::false(is_link($path), 'Cannot add the link "'.$path.'": links are not supported.');
1519
1520
                $filesToAppend[] = $path;
1521
            }
1522
        }
1523
1524
        $files = $filesToAppend;
1525
        $directories = [];
1526
1527
        foreach ($paths as $path) {
1528
            $path = $normalizePath($path);
1529
1530
            Assertion::true(file_exists($path), 'File or directory "'.$path.'" was expected to exist.');
1531
            Assertion::false(is_link($path), 'Cannot add the link "'.$path.'": links are not supported.');
1532
1533
            if (is_file($path)) {
1534
                $files[] = $path;
1535
            } else {
1536
                $directories[] = $path;
1537
            }
1538
        }
1539
1540
        [$files, $directories] = [
1541
            array_unique($files),
1542
            array_unique($directories),
1543
        ];
1544
1545
        return [
1546
            array_diff($files, $excludedPaths),
1547
            array_diff($directories, $excludedPaths),
1548
        ];
1549
    }
1550
1551
    /**
1552
     * @param string[] $files
1553
     * @param string[] $directories
1554
     * @param string[] $excludedPaths
1555
     * @param string[] $devPackages
1556
     *
1557
     * @return SplFileInfo[]
1558
     */
1559
    private static function retrieveAllFiles(
1560
        string $basePath,
1561
        array $directories,
1562
        ?string $mainScriptPath,
1563
        Closure $blacklistFilter,
1564
        array $excludedPaths,
1565
        array $devPackages
1566
    ): iterable {
1567
        if ([] === $directories) {
1568
            return [];
1569
        }
1570
1571
        $relativeDevPackages = array_map(
1572
            static function (string $packagePath) use ($basePath): string {
1573
                return make_path_relative($packagePath, $basePath);
1574
            },
1575
            $devPackages
1576
        );
1577
1578
        $finder = Finder::create()
1579
            ->files()
1580
            ->filter($blacklistFilter)
1581
            ->exclude($relativeDevPackages)
1582
            ->ignoreVCS(true)
1583
            ->ignoreDotFiles(true)
1584
            // Remove build files
1585
            ->notName('composer.json')
1586
            ->notName('composer.lock')
1587
            ->notName('Makefile')
1588
            ->notName('Vagrantfile')
1589
            ->notName('phpstan*.neon*')
1590
            ->notName('infection*.json*')
1591
            ->notName('humbug*.json*')
1592
            ->notName('easy-coding-standard.neon*')
1593
            ->notName('phpbench.json*')
1594
            ->notName('phpcs.xml*')
1595
            ->notName('psalm.xml*')
1596
            ->notName('scoper.inc*')
1597
            ->notName('box*.json*')
1598
            ->notName('phpdoc*.xml*')
1599
            ->notName('codecov.yml*')
1600
            ->notName('Dockerfile')
1601
            ->exclude('build')
1602
            ->exclude('dist')
1603
            ->exclude('example')
1604
            ->exclude('examples')
1605
            // Remove documentation
1606
            ->notName('*.md')
1607
            ->notName('*.rst')
1608
            ->notName('/^readme((?!\.php)(\..*+))?$/i')
1609
            ->notName('/^upgrade((?!\.php)(\..*+))?$/i')
1610
            ->notName('/^contributing((?!\.php)(\..*+))?$/i')
1611
            ->notName('/^changelog((?!\.php)(\..*+))?$/i')
1612
            ->notName('/^authors?((?!\.php)(\..*+))?$/i')
1613
            ->notName('/^conduct((?!\.php)(\..*+))?$/i')
1614
            ->notName('/^todo((?!\.php)(\..*+))?$/i')
1615
            ->exclude('doc')
1616
            ->exclude('docs')
1617
            ->exclude('documentation')
1618
            // Remove backup files
1619
            ->notName('*~')
1620
            ->notName('*.back')
1621
            ->notName('*.swp')
1622
            // Remove tests
1623
            ->notName('*Test.php')
1624
            ->exclude('test')
1625
            ->exclude('Test')
1626
            ->exclude('tests')
1627
            ->exclude('Tests')
1628
            ->notName('/phpunit.*\.xml(.dist)?/')
1629
            ->notName('/behat.*\.yml(.dist)?/')
1630
            ->exclude('spec')
1631
            ->exclude('specs')
1632
            ->exclude('features')
1633
            // Remove CI config
1634
            ->exclude('travis')
1635
            ->notName('travis.yml')
1636
            ->notName('appveyor.yml')
1637
            ->notName('build.xml*')
1638
        ;
1639
1640
        if (null !== $mainScriptPath) {
1641
            $finder->notPath(make_path_relative($mainScriptPath, $basePath));
1642
        }
1643
1644
        $finder->in($directories);
1645
1646
        $excludedPaths = array_unique(
1647
            array_filter(
1648
                array_map(
1649
                    static function (string $path) use ($basePath): string {
1650
                        return make_path_relative($path, $basePath);
1651
                    },
1652
                    $excludedPaths
1653
                ),
1654
                static function (string $path): bool {
1655
                    return '..' !== substr($path, 0, 2);
1656
                }
1657
            )
1658
        );
1659
1660
        foreach ($excludedPaths as $excludedPath) {
1661
            $finder->notPath($excludedPath);
1662
        }
1663
1664
        return $finder;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $finder returns the type Symfony\Component\Finder\Finder which is incompatible with the documented return type SplFileInfo[].
Loading history...
1665
    }
1666
1667
    /**
1668
     * @param string $key Config property name
1669
     *
1670
     * @return string[]
1671
     */
1672
    private static function retrieveDirectoryPaths(
1673
        stdClass $raw,
1674
        string $key,
1675
        string $basePath,
1676
        ConfigurationLogger $logger
1677
    ): array {
1678
        self::checkIfDefaultValue($logger, $raw, $key, []);
1679
1680
        if (false === isset($raw->{$key})) {
1681
            return [];
1682
        }
1683
1684
        $directories = $raw->{$key};
1685
1686
        $normalizeDirectory = static function (string $directory) use ($basePath, $key): string {
1687
            $directory = self::normalizePath($directory, $basePath);
1688
1689
            Assertion::false(
1690
                is_link($directory),
1691
                sprintf(
1692
                    'Cannot add the link "%s": links are not supported.',
1693
                    $directory
1694
                )
1695
            );
1696
1697
            Assertion::directory(
1698
                $directory,
1699
                sprintf(
1700
                    '"%s" must contain a list of existing directories. Could not find "%%s".',
1701
                    $key
1702
                )
1703
            );
1704
1705
            return $directory;
1706
        };
1707
1708
        return array_map($normalizeDirectory, $directories);
1709
    }
1710
1711
    private static function normalizePath(string $file, string $basePath): string
1712
    {
1713
        return make_path_absolute(trim($file), $basePath);
1714
    }
1715
1716
    /**
1717
     * @param string[] $files
1718
     *
1719
     * @return SplFileInfo[]
1720
     */
1721
    private static function wrapInSplFileInfo(array $files): array
1722
    {
1723
        return array_map(
1724
            static function (string $file): SplFileInfo {
1725
                return new SplFileInfo($file);
1726
            },
1727
            $files
1728
        );
1729
    }
1730
1731
    private static function retrieveDumpAutoload(stdClass $raw, bool $composerJson, ConfigurationLogger $logger): bool
1732
    {
1733
        self::checkIfDefaultValue($logger, $raw, self::DUMP_AUTOLOAD_KEY, true);
1734
1735
        if (false === property_exists($raw, self::DUMP_AUTOLOAD_KEY)) {
1736
            return $composerJson;
1737
        }
1738
1739
        $dumpAutoload = $raw->{self::DUMP_AUTOLOAD_KEY} ?? true;
1740
1741
        if (false === $composerJson && $dumpAutoload) {
1742
            // TODO: use sprintf there; check other occurrences as well
1743
            $logger->addWarning(
1744
                'The "dump-autoload" setting has been set but has been ignored because the composer.json file necessary'
1745
                .' for it could not be found'
1746
            );
1747
1748
            return false;
1749
        }
1750
1751
        return $composerJson && false !== $dumpAutoload;
1752
    }
1753
1754
    private static function retrieveExcludeDevFiles(stdClass $raw, bool $dumpAutoload, ConfigurationLogger $logger): bool
1755
    {
1756
        self::checkIfDefaultValue($logger, $raw, self::EXCLUDE_DEV_FILES_KEY, $dumpAutoload);
1757
1758
        if (false === property_exists($raw, self::EXCLUDE_DEV_FILES_KEY)) {
1759
            return $dumpAutoload;
1760
        }
1761
1762
        $excludeDevFiles = $raw->{self::EXCLUDE_DEV_FILES_KEY} ?? $dumpAutoload;
1763
1764
        if (true === $excludeDevFiles && false === $dumpAutoload) {
1765
            $logger->addWarning(sprintf(
1766
                'The "%s" setting has been set but has been ignored because the Composer autoloader is not dumped',
1767
                self::EXCLUDE_DEV_FILES_KEY
1768
            ));
1769
1770
            return false;
1771
        }
1772
1773
        return $excludeDevFiles;
1774
    }
1775
1776
    private static function retrieveExcludeComposerFiles(stdClass $raw, ConfigurationLogger $logger): bool
1777
    {
1778
        self::checkIfDefaultValue($logger, $raw, self::EXCLUDE_COMPOSER_FILES_KEY, true);
1779
1780
        return $raw->{self::EXCLUDE_COMPOSER_FILES_KEY} ?? true;
1781
    }
1782
1783
    /**
1784
     * @return Compactor[]
1785
     */
1786
    private static function retrieveCompactors(stdClass $raw, string $basePath, ConfigurationLogger $logger): array
1787
    {
1788
        self::checkIfDefaultValue($logger, $raw, self::COMPACTORS_KEY, []);
1789
1790
        $compactorClasses = array_unique((array) ($raw->{self::COMPACTORS_KEY} ?? []));
1791
1792
        $ignoredAnnotations = self::retrievePhpCompactorIgnoredAnnotations($raw, $compactorClasses, $logger);
1793
1794
        if (false === isset($raw->{self::COMPACTORS_KEY})) {
1795
            return [];
1796
        }
1797
1798
        $compactors = array_map(
1799
            static function (string $class) use ($raw, $basePath, $logger, $ignoredAnnotations): Compactor {
1800
                Assertion::classExists($class, 'The compactor class "%s" does not exist.');
1801
                Assertion::implementsInterface($class, Compactor::class, 'The class "%s" is not a compactor class.');
1802
1803
                if (LegacyPhp::class === $class) {
1804
                    $logger->addRecommendation(
1805
                        sprintf(
1806
                            'The compactor "%s" has been deprecated, use "%s" instead.',
1807
                            LegacyPhp::class,
1808
                            PhpCompactor::class
1809
                        )
1810
                    );
1811
                }
1812
1813
                if (LegacyJson::class === $class) {
1814
                    $logger->addRecommendation(
1815
                        sprintf(
1816
                            'The compactor "%s" has been deprecated, use "%s" instead.',
1817
                            LegacyJson::class,
1818
                            JsonCompactor::class
1819
                        )
1820
                    );
1821
                }
1822
1823
                if (PhpCompactor::class === $class || LegacyPhp::class === $class) {
1824
                    return self::createPhpCompactor($ignoredAnnotations);
1825
                }
1826
1827
                if (PhpScoperCompactor::class === $class) {
1828
                    return self::createPhpScoperCompactor($raw, $basePath, $logger);
1829
                }
1830
1831
                return new $class();
1832
            },
1833
            $compactorClasses
1834
        );
1835
1836
        $scoperCompactor = false;
1837
1838
        foreach ($compactors as $compactor) {
1839
            if ($compactor instanceof PhpScoperCompactor) {
1840
                $scoperCompactor = true;
1841
            }
1842
1843
            if ($compactor instanceof PhpCompactor) {
1844
                if (true === $scoperCompactor) {
1845
                    $logger->addRecommendation(
1846
                        sprintf(
1847
                            'The PHP compactor has been registered after the PhpScoper compactor. It is '
1848
                            .'recommended to register the PHP compactor before for a clearer code and faster processing.'
1849
                        )
1850
                    );
1851
                }
1852
1853
                break;
1854
            }
1855
        }
1856
1857
        return $compactors;
1858
    }
1859
1860
    private static function retrieveCompressionAlgorithm(stdClass $raw, ConfigurationLogger $logger): ?int
1861
    {
1862
        self::checkIfDefaultValue($logger, $raw, self::COMPRESSION_KEY, 'NONE');
1863
1864
        if (false === isset($raw->{self::COMPRESSION_KEY})) {
1865
            return null;
1866
        }
1867
1868
        $knownAlgorithmNames = array_keys(get_phar_compression_algorithms());
1869
1870
        Assertion::inArray(
1871
            $raw->{self::COMPRESSION_KEY},
1872
            $knownAlgorithmNames,
1873
            sprintf(
1874
                'Invalid compression algorithm "%%s", use one of "%s" instead.',
1875
                implode('", "', $knownAlgorithmNames)
1876
            )
1877
        );
1878
1879
        $value = get_phar_compression_algorithms()[$raw->{self::COMPRESSION_KEY}];
1880
1881
        // Phar::NONE is not valid for compressFiles()
1882
        if (Phar::NONE === $value) {
1883
            return null;
1884
        }
1885
1886
        return $value;
1887
    }
1888
1889
    private static function retrieveFileMode(stdClass $raw, ConfigurationLogger $logger): ?int
1890
    {
1891
        if (property_exists($raw, self::CHMOD_KEY) && null === $raw->{self::CHMOD_KEY}) {
1892
            self::addRecommendationForDefaultValue($logger, self::CHMOD_KEY);
1893
        }
1894
1895
        $defaultChmod = intval(0755, 8);
1896
1897
        if (isset($raw->{self::CHMOD_KEY})) {
1898
            $chmod = intval($raw->{self::CHMOD_KEY}, 8);
1899
1900
            if ($defaultChmod === $chmod) {
1901
                self::addRecommendationForDefaultValue($logger, self::CHMOD_KEY);
1902
            }
1903
1904
            return $chmod;
1905
        }
1906
1907
        return $defaultChmod;
1908
    }
1909
1910
    private static function retrieveMainScriptPath(
1911
        stdClass $raw,
1912
        string $basePath,
1913
        ?array $decodedJsonContents,
1914
        ConfigurationLogger $logger
1915
    ): ?string {
1916
        $firstBin = false;
1917
1918
        if (null !== $decodedJsonContents && array_key_exists('bin', $decodedJsonContents)) {
1919
            /** @var false|string $firstBin */
1920
            $firstBin = current((array) $decodedJsonContents['bin']);
1921
1922
            if (false !== $firstBin) {
1923
                $firstBin = self::normalizePath($firstBin, $basePath);
1924
            }
1925
        }
1926
1927
        if (isset($raw->{self::MAIN_KEY})) {
1928
            $main = $raw->{self::MAIN_KEY};
1929
1930
            if (is_string($main)) {
1931
                $main = self::normalizePath($main, $basePath);
1932
1933
                if ($main === $firstBin) {
1934
                    $logger->addRecommendation('The "main" setting can be omitted since is set to its default value');
1935
                }
1936
            }
1937
        } else {
1938
            $main = false !== $firstBin ? $firstBin : self::normalizePath(self::DEFAULT_MAIN_SCRIPT, $basePath);
1939
        }
1940
1941
        if (is_bool($main)) {
1942
            Assertion::false(
1943
                $main,
1944
                'Cannot "enable" a main script: either disable it with `false` or give the main script file path.'
1945
            );
1946
1947
            return null;
1948
        }
1949
1950
        Assertion::file($main);
1951
1952
        return $main;
1953
    }
1954
1955
    private static function retrieveMainScriptContents(?string $mainScriptPath): ?string
1956
    {
1957
        if (null === $mainScriptPath) {
1958
            return null;
1959
        }
1960
1961
        $contents = file_contents($mainScriptPath);
1962
1963
        // Remove the shebang line: the shebang line in a PHAR should be located in the stub file which is the real
1964
        // PHAR entry point file.
1965
        // If one needs the shebang, then the main file should act as the stub and be registered as such and in which
1966
        // case the main script can be ignored or disabled.
1967
        return preg_replace('/^#!.*\s*/', '', $contents);
1968
    }
1969
1970
    private static function retrieveComposerFiles(string $basePath): ComposerFiles
1971
    {
1972
        $retrieveFileAndContents = static function (string $file): ?ComposerFile {
1973
            $json = new Json();
1974
1975
            if (false === file_exists($file) || false === is_file($file) || false === is_readable($file)) {
1976
                return ComposerFile::createEmpty();
1977
            }
1978
1979
            try {
1980
                $contents = (array) $json->decodeFile($file, true);
1981
            } catch (ParsingException $exception) {
1982
                throw new InvalidArgumentException(
1983
                    sprintf(
1984
                        'Expected the file "%s" to be a valid composer.json file but an error has been found: %s',
1985
                        $file,
1986
                        $exception->getMessage()
1987
                    ),
1988
                    0,
1989
                    $exception
1990
                );
1991
            }
1992
1993
            return new ComposerFile($file, $contents);
1994
        };
1995
1996
        return new ComposerFiles(
1997
            $retrieveFileAndContents(canonicalize($basePath.'/composer.json')),
1998
            $retrieveFileAndContents(canonicalize($basePath.'/composer.lock')),
1999
            $retrieveFileAndContents(canonicalize($basePath.'/vendor/composer/installed.json'))
2000
        );
2001
    }
2002
2003
    /**
2004
     * @return string[][]
2005
     */
2006
    private static function retrieveMap(stdClass $raw, ConfigurationLogger $logger): array
2007
    {
2008
        self::checkIfDefaultValue($logger, $raw, self::MAP_KEY, []);
2009
2010
        if (false === isset($raw->{self::MAP_KEY})) {
2011
            return [];
2012
        }
2013
2014
        $map = [];
2015
        $rawMap = (array) $raw->{self::MAP_KEY};
2016
2017
        foreach ($rawMap as $item) {
2018
            $processed = [];
2019
2020
            foreach ($item as $match => $replace) {
2021
                $processed[canonicalize(trim($match))] = canonicalize(trim($replace));
2022
            }
2023
2024
            if (isset($processed['_empty_'])) {
2025
                $processed[''] = $processed['_empty_'];
2026
2027
                unset($processed['_empty_']);
2028
            }
2029
2030
            $map[] = $processed;
2031
        }
2032
2033
        return $map;
2034
    }
2035
2036
    /**
2037
     * @return mixed
2038
     */
2039
    private static function retrieveMetadata(stdClass $raw, ConfigurationLogger $logger)
2040
    {
2041
        self::checkIfDefaultValue($logger, $raw, self::METADATA_KEY);
2042
2043
        if (false === isset($raw->{self::METADATA_KEY})) {
2044
            return null;
2045
        }
2046
2047
        $metadata = $raw->{self::METADATA_KEY};
2048
2049
        return is_object($metadata) ? (array) $metadata : $metadata;
2050
    }
2051
2052
    /**
2053
     * @return string[] The first element is the temporary output path and the second the final one
2054
     */
2055
    private static function retrieveOutputPath(
2056
        stdClass $raw,
2057
        string $basePath,
2058
        ?string $mainScriptPath,
2059
        ConfigurationLogger $logger
2060
    ): array {
2061
        $defaultPath = null;
2062
2063
        if (null !== $mainScriptPath
2064
            && 1 === preg_match('/^(?<main>.*?)(?:\.[\p{L}\d]+)?$/u', $mainScriptPath, $matches)
2065
        ) {
2066
            $defaultPath = $matches['main'].'.phar';
2067
        }
2068
2069
        if (isset($raw->{self::OUTPUT_KEY})) {
2070
            $path = self::normalizePath($raw->{self::OUTPUT_KEY}, $basePath);
2071
2072
            if ($path === $defaultPath) {
2073
                self::addRecommendationForDefaultValue($logger, self::OUTPUT_KEY);
2074
            }
2075
        } elseif (null !== $defaultPath) {
2076
            $path = $defaultPath;
2077
        } else {
2078
            // Last resort, should not happen
2079
            $path = self::normalizePath(self::DEFAULT_OUTPUT_FALLBACK, $basePath);
2080
        }
2081
2082
        $tmp = $real = $path;
2083
2084
        if ('.phar' !== substr($real, -5)) {
2085
            $tmp .= '.phar';
2086
        }
2087
2088
        return [$tmp, $real];
2089
    }
2090
2091
    private static function retrievePrivateKeyPath(
2092
        stdClass $raw,
2093
        string $basePath,
2094
        int $signingAlgorithm,
2095
        ConfigurationLogger $logger
2096
    ): ?string {
2097
        if (property_exists($raw, self::KEY_KEY) && Phar::OPENSSL !== $signingAlgorithm) {
2098
            if (null === $raw->{self::KEY_KEY}) {
2099
                $logger->addRecommendation(
2100
                    'The setting "key" has been set but is unnecessary since the signing algorithm is not "OPENSSL".'
2101
                );
2102
            } else {
2103
                $logger->addWarning(
2104
                    'The setting "key" has been set but is ignored since the signing algorithm is not "OPENSSL".'
2105
                );
2106
            }
2107
2108
            return null;
2109
        }
2110
2111
        if (!isset($raw->{self::KEY_KEY})) {
2112
            Assertion::true(
2113
                Phar::OPENSSL !== $signingAlgorithm,
2114
                'Expected to have a private key for OpenSSL signing but none have been provided.'
2115
            );
2116
2117
            return null;
2118
        }
2119
2120
        $path = self::normalizePath($raw->{self::KEY_KEY}, $basePath);
2121
2122
        Assertion::file($path);
2123
2124
        return $path;
2125
    }
2126
2127
    private static function retrievePrivateKeyPassphrase(
2128
        stdClass $raw,
2129
        int $algorithm,
2130
        ConfigurationLogger $logger
2131
    ): ?string {
2132
        self::checkIfDefaultValue($logger, $raw, self::KEY_PASS_KEY);
2133
2134
        if (false === property_exists($raw, self::KEY_PASS_KEY)) {
2135
            return null;
2136
        }
2137
2138
        /** @var null|false|string $keyPass */
2139
        $keyPass = $raw->{self::KEY_PASS_KEY};
2140
2141
        if (Phar::OPENSSL !== $algorithm) {
2142
            if (false === $keyPass || null === $keyPass) {
2143
                $logger->addRecommendation(
2144
                    sprintf(
2145
                        'The setting "%s" has been set but is unnecessary since the signing algorithm is '
2146
                        .'not "OPENSSL".',
2147
                        self::KEY_PASS_KEY
2148
                    )
2149
                );
2150
            } else {
2151
                $logger->addWarning(
2152
                    sprintf(
2153
                        'The setting "%s" has been set but ignored the signing algorithm is not "OPENSSL".',
2154
                        self::KEY_PASS_KEY
2155
                    )
2156
                );
2157
            }
2158
2159
            return null;
2160
        }
2161
2162
        return is_string($keyPass) ? $keyPass : null;
2163
    }
2164
2165
    /**
2166
     * @return scalar[]
2167
     */
2168
    private static function retrieveReplacements(stdClass $raw, ?string $file, ConfigurationLogger $logger): array
2169
    {
2170
        self::checkIfDefaultValue($logger, $raw, self::REPLACEMENTS_KEY, new stdClass());
2171
2172
        if (null === $file) {
2173
            return [];
2174
        }
2175
2176
        $replacements = isset($raw->{self::REPLACEMENTS_KEY}) ? (array) $raw->{self::REPLACEMENTS_KEY} : [];
2177
2178
        if (null !== ($git = self::retrievePrettyGitPlaceholder($raw, $logger))) {
2179
            $replacements[$git] = self::retrievePrettyGitTag($file);
2180
        }
2181
2182
        if (null !== ($git = self::retrieveGitHashPlaceholder($raw, $logger))) {
2183
            $replacements[$git] = self::retrieveGitHash($file);
2184
        }
2185
2186
        if (null !== ($git = self::retrieveGitShortHashPlaceholder($raw, $logger))) {
2187
            $replacements[$git] = self::retrieveGitHash($file, true);
2188
        }
2189
2190
        if (null !== ($git = self::retrieveGitTagPlaceholder($raw, $logger))) {
2191
            $replacements[$git] = self::retrieveGitTag($file);
2192
        }
2193
2194
        if (null !== ($git = self::retrieveGitVersionPlaceholder($raw, $logger))) {
2195
            $replacements[$git] = self::retrieveGitVersion($file);
2196
        }
2197
2198
        /**
2199
         * @var string
2200
         * @var bool   $valueSetByUser
2201
         */
2202
        [$datetimeFormat, $valueSetByUser] = self::retrieveDatetimeFormat($raw, $logger);
2203
2204
        if (null !== ($date = self::retrieveDatetimeNowPlaceHolder($raw, $logger))) {
2205
            $replacements[$date] = self::retrieveDatetimeNow($datetimeFormat);
2206
        } elseif ($valueSetByUser) {
2207
            $logger->addRecommendation(
2208
                sprintf(
2209
                    'The setting "%s" has been set but is unnecessary because the setting "%s" is not set.',
2210
                    self::DATETIME_FORMAT_KEY,
2211
                    self::DATETIME_KEY
2212
                )
2213
            );
2214
        }
2215
2216
        $sigil = self::retrieveReplacementSigil($raw, $logger);
2217
2218
        foreach ($replacements as $key => $value) {
2219
            unset($replacements[$key]);
2220
            $replacements[$sigil.$key.$sigil] = $value;
2221
        }
2222
2223
        return $replacements;
2224
    }
2225
2226
    private static function retrievePrettyGitPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
2227
    {
2228
        return self::retrievePlaceholder($raw, $logger, self::GIT_KEY);
2229
    }
2230
2231
    private static function retrieveGitHashPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
2232
    {
2233
        return self::retrievePlaceholder($raw, $logger, self::GIT_COMMIT_KEY);
2234
    }
2235
2236
    /**
2237
     * @param bool $short Use the short version
2238
     *
2239
     * @return string the commit hash
2240
     */
2241
    private static function retrieveGitHash(string $file, bool $short = false): string
2242
    {
2243
        return self::runGitCommand(
2244
            sprintf(
2245
                'git log --pretty="%s" -n1 HEAD',
2246
                $short ? '%h' : '%H'
2247
            ),
2248
            $file
2249
        );
2250
    }
2251
2252
    private static function retrieveGitShortHashPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
2253
    {
2254
        return self::retrievePlaceholder($raw, $logger, self::GIT_COMMIT_SHORT_KEY);
2255
    }
2256
2257
    private static function retrieveGitTagPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
2258
    {
2259
        return self::retrievePlaceholder($raw, $logger, self::GIT_TAG_KEY);
2260
    }
2261
2262
    private static function retrievePlaceholder(stdClass $raw, ConfigurationLogger $logger, string $key): ?string
2263
    {
2264
        self::checkIfDefaultValue($logger, $raw, $key);
2265
2266
        return $raw->{$key} ?? null;
2267
    }
2268
2269
    private static function retrieveGitTag(string $file): string
2270
    {
2271
        return self::runGitCommand('git describe --tags HEAD', $file);
2272
    }
2273
2274
    private static function retrievePrettyGitTag(string $file): string
2275
    {
2276
        $version = self::retrieveGitTag($file);
2277
2278
        if (preg_match('/^(?<tag>.+)-\d+-g(?<hash>[a-f0-9]{7})$/', $version, $matches)) {
2279
            return sprintf('%s@%s', $matches['tag'], $matches['hash']);
2280
        }
2281
2282
        return $version;
2283
    }
2284
2285
    private static function retrieveGitVersionPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
2286
    {
2287
        return self::retrievePlaceholder($raw, $logger, self::GIT_VERSION_KEY);
2288
    }
2289
2290
    private static function retrieveGitVersion(string $file): ?string
2291
    {
2292
        try {
2293
            return self::retrieveGitTag($file);
2294
        } catch (RuntimeException $exception) {
2295
            try {
2296
                return self::retrieveGitHash($file, true);
2297
            } catch (RuntimeException $exception) {
2298
                throw new RuntimeException(
2299
                    sprintf(
2300
                        'The tag or commit hash could not be retrieved from "%s": %s',
2301
                        dirname($file),
2302
                        $exception->getMessage()
2303
                    ),
2304
                    0,
2305
                    $exception
2306
                );
2307
            }
2308
        }
2309
    }
2310
2311
    private static function retrieveDatetimeNowPlaceHolder(stdClass $raw, ConfigurationLogger $logger): ?string
2312
    {
2313
        return self::retrievePlaceholder($raw, $logger, self::DATETIME_KEY);
2314
    }
2315
2316
    private static function retrieveDatetimeNow(string $format): string
2317
    {
2318
        $now = new DateTimeImmutable('now', new DateTimeZone('UTC'));
2319
2320
        return $now->format($format);
2321
    }
2322
2323
    private static function retrieveDatetimeFormat(stdClass $raw, ConfigurationLogger $logger): array
2324
    {
2325
        self::checkIfDefaultValue($logger, $raw, self::DATETIME_FORMAT_KEY, self::DEFAULT_DATETIME_FORMAT);
2326
        self::checkIfDefaultValue($logger, $raw, self::DATETIME_FORMAT_KEY, self::DATETIME_FORMAT_DEPRECATED_KEY);
2327
2328
        if (isset($raw->{self::DATETIME_FORMAT_KEY})) {
2329
            $format = $raw->{self::DATETIME_FORMAT_KEY};
2330
        } elseif (isset($raw->{self::DATETIME_FORMAT_DEPRECATED_KEY})) {
2331
            @trigger_error(
2332
                'The "datetime_format" is deprecated, use "datetime-format" setting instead.',
2333
                E_USER_DEPRECATED
2334
            );
2335
            $logger->addWarning('The "datetime_format" is deprecated, use "datetime-format" setting instead.');
2336
2337
            $format = $raw->{self::DATETIME_FORMAT_DEPRECATED_KEY};
2338
        } else {
2339
            $format = null;
2340
        }
2341
2342
        if (null !== $format) {
2343
            $formattedDate = (new DateTimeImmutable())->format($format);
2344
2345
            Assertion::false(
2346
                false === $formattedDate || $formattedDate === $format,
2347
                sprintf(
2348
                    'Expected the datetime format to be a valid format: "%s" is not',
2349
                    $format
2350
                )
2351
            );
2352
2353
            return [$format, true];
2354
        }
2355
2356
        return [self::DEFAULT_DATETIME_FORMAT, false];
2357
    }
2358
2359
    private static function retrieveReplacementSigil(stdClass $raw, ConfigurationLogger $logger): string
2360
    {
2361
        return self::retrievePlaceholder($raw, $logger, self::REPLACEMENT_SIGIL_KEY) ?? self::DEFAULT_REPLACEMENT_SIGIL;
2362
    }
2363
2364
    private static function retrieveShebang(stdClass $raw, bool $stubIsGenerated, ConfigurationLogger $logger): ?string
2365
    {
2366
        self::checkIfDefaultValue($logger, $raw, self::SHEBANG_KEY, self::DEFAULT_SHEBANG);
2367
2368
        if (false === isset($raw->{self::SHEBANG_KEY})) {
2369
            return self::DEFAULT_SHEBANG;
2370
        }
2371
2372
        $shebang = $raw->{self::SHEBANG_KEY};
2373
2374
        if (false === $shebang) {
2375
            if (false === $stubIsGenerated) {
2376
                $logger->addRecommendation(
2377
                    sprintf(
2378
                        'The "%s" has been set to `false` but is unnecessary since the Box built-in stub is not'
2379
                        .' being used',
2380
                        self::SHEBANG_KEY
2381
                    )
2382
                );
2383
            }
2384
2385
            return null;
2386
        }
2387
2388
        Assertion::string($shebang, 'Expected shebang to be either a string, false or null, found true');
2389
2390
        $shebang = trim($shebang);
2391
2392
        Assertion::notEmpty($shebang, 'The shebang should not be empty.');
2393
        Assertion::true(
2394
            '#!' === substr($shebang, 0, 2),
2395
            sprintf(
2396
                'The shebang line must start with "#!". Got "%s" instead',
2397
                $shebang
2398
            )
2399
        );
2400
2401
        if (false === $stubIsGenerated) {
2402
            $logger->addWarning(
2403
                sprintf(
2404
                    'The "%s" has been set but ignored since it is used only with the Box built-in stub which is not'
2405
                    .' used',
2406
                    self::SHEBANG_KEY
2407
                )
2408
            );
2409
        }
2410
2411
        return $shebang;
2412
    }
2413
2414
    private static function retrieveSigningAlgorithm(stdClass $raw, ConfigurationLogger $logger): int
2415
    {
2416
        if (property_exists($raw, self::ALGORITHM_KEY) && null === $raw->{self::ALGORITHM_KEY}) {
2417
            self::addRecommendationForDefaultValue($logger, self::ALGORITHM_KEY);
2418
        }
2419
2420
        if (false === isset($raw->{self::ALGORITHM_KEY})) {
2421
            return self::DEFAULT_SIGNING_ALGORITHM;
2422
        }
2423
2424
        $algorithm = strtoupper($raw->{self::ALGORITHM_KEY});
2425
2426
        Assertion::inArray($algorithm, array_keys(get_phar_signing_algorithms()));
2427
2428
        Assertion::true(
2429
            defined('Phar::'.$algorithm),
2430
            sprintf(
2431
                'The signing algorithm "%s" is not supported by your current PHAR version.',
2432
                $algorithm
2433
            )
2434
        );
2435
2436
        $algorithm = constant('Phar::'.$algorithm);
2437
2438
        if (self::DEFAULT_SIGNING_ALGORITHM === $algorithm) {
2439
            self::addRecommendationForDefaultValue($logger, self::ALGORITHM_KEY);
2440
        }
2441
2442
        return $algorithm;
2443
    }
2444
2445
    private static function retrieveStubBannerContents(stdClass $raw, bool $stubIsGenerated, ConfigurationLogger $logger): ?string
2446
    {
2447
        self::checkIfDefaultValue($logger, $raw, self::BANNER_KEY, self::getDefaultBanner());
2448
2449
        if (false === isset($raw->{self::BANNER_KEY})) {
2450
            return self::getDefaultBanner();
2451
        }
2452
2453
        $banner = $raw->{self::BANNER_KEY};
2454
2455
        if (false === $banner) {
2456
            if (false === $stubIsGenerated) {
2457
                $logger->addRecommendation(
2458
                    sprintf(
2459
                        'The "%s" setting has been set but is unnecessary since the Box built-in stub is not '
2460
                        .'being used',
2461
                        self::BANNER_KEY
2462
                    )
2463
                );
2464
            }
2465
2466
            return null;
2467
        }
2468
2469
        Assertion::true(is_string($banner) || is_array($banner), 'The banner cannot accept true as a value');
2470
2471
        if (is_array($banner)) {
2472
            $banner = implode("\n", $banner);
2473
        }
2474
2475
        if (false === $stubIsGenerated) {
2476
            $logger->addWarning(
2477
                sprintf(
2478
                    'The "%s" setting has been set but is ignored since the Box built-in stub is not being used',
2479
                    self::BANNER_KEY
2480
                )
2481
            );
2482
        }
2483
2484
        return $banner;
2485
    }
2486
2487
    private static function getDefaultBanner(): string
2488
    {
2489
        return sprintf(self::DEFAULT_BANNER, get_box_version());
2490
    }
2491
2492
    private static function retrieveStubBannerPath(
2493
        stdClass $raw,
2494
        string $basePath,
2495
        bool $stubIsGenerated,
2496
        ConfigurationLogger $logger
2497
    ): ?string {
2498
        self::checkIfDefaultValue($logger, $raw, self::BANNER_FILE_KEY);
2499
2500
        if (false === isset($raw->{self::BANNER_FILE_KEY})) {
2501
            return null;
2502
        }
2503
2504
        $bannerFile = make_path_absolute($raw->{self::BANNER_FILE_KEY}, $basePath);
2505
2506
        Assertion::file($bannerFile);
2507
2508
        if (false === $stubIsGenerated) {
2509
            $logger->addWarning(
2510
                sprintf(
2511
                    'The "%s" setting has been set but is ignored since the Box built-in stub is not being used',
2512
                    self::BANNER_FILE_KEY
2513
                )
2514
            );
2515
        }
2516
2517
        return $bannerFile;
2518
    }
2519
2520
    private static function normalizeStubBannerContents(?string $contents): ?string
2521
    {
2522
        if (null === $contents) {
2523
            return null;
2524
        }
2525
2526
        $banner = explode("\n", $contents);
2527
        $banner = array_map('trim', $banner);
2528
2529
        return implode("\n", $banner);
2530
    }
2531
2532
    private static function retrieveStubPath(stdClass $raw, string $basePath, ConfigurationLogger $logger): ?string
2533
    {
2534
        self::checkIfDefaultValue($logger, $raw, self::STUB_KEY);
2535
2536
        if (isset($raw->{self::STUB_KEY}) && is_string($raw->{self::STUB_KEY})) {
2537
            $stubPath = make_path_absolute($raw->{self::STUB_KEY}, $basePath);
2538
2539
            Assertion::file($stubPath);
2540
2541
            return $stubPath;
2542
        }
2543
2544
        return null;
2545
    }
2546
2547
    private static function retrieveInterceptsFileFunctions(
2548
        stdClass $raw,
2549
        bool $stubIsGenerated,
2550
        ConfigurationLogger $logger
2551
    ): bool {
2552
        self::checkIfDefaultValue($logger, $raw, self::INTERCEPT_KEY, false);
2553
2554
        if (false === isset($raw->{self::INTERCEPT_KEY})) {
2555
            return false;
2556
        }
2557
2558
        $intercept = $raw->{self::INTERCEPT_KEY};
2559
2560
        if ($intercept && false === $stubIsGenerated) {
2561
            $logger->addWarning(
2562
                sprintf(
2563
                    'The "%s" setting has been set but is ignored since the Box built-in stub is not being used',
2564
                    self::INTERCEPT_KEY
2565
                )
2566
            );
2567
        }
2568
2569
        return $intercept;
2570
    }
2571
2572
    private static function retrievePromptForPrivateKey(
2573
        stdClass $raw,
2574
        int $signingAlgorithm,
2575
        ConfigurationLogger $logger
2576
    ): bool {
2577
        if (isset($raw->{self::KEY_PASS_KEY}) && true === $raw->{self::KEY_PASS_KEY}) {
2578
            if (Phar::OPENSSL !== $signingAlgorithm) {
2579
                $logger->addWarning(
2580
                    'A prompt for password for the private key has been requested but ignored since the signing '
2581
                    .'algorithm used is not "OPENSSL.'
2582
                );
2583
2584
                return false;
2585
            }
2586
2587
            return true;
2588
        }
2589
2590
        return false;
2591
    }
2592
2593
    private static function retrieveIsStubGenerated(stdClass $raw, ?string $stubPath, ConfigurationLogger $logger): bool
2594
    {
2595
        self::checkIfDefaultValue($logger, $raw, self::STUB_KEY, true);
2596
2597
        return null === $stubPath && (false === isset($raw->{self::STUB_KEY}) || false !== $raw->{self::STUB_KEY});
2598
    }
2599
2600
    private static function retrieveCheckRequirements(
2601
        stdClass $raw,
2602
        bool $hasComposerJson,
2603
        bool $hasComposerLock,
2604
        bool $pharStubUsed,
2605
        ConfigurationLogger $logger
2606
    ): bool {
2607
        self::checkIfDefaultValue($logger, $raw, self::CHECK_REQUIREMENTS_KEY, true);
2608
2609
        if (false === property_exists($raw, self::CHECK_REQUIREMENTS_KEY)) {
2610
            return $hasComposerJson || $hasComposerLock;
2611
        }
2612
2613
        /** @var bool $checkRequirements */
2614
        $checkRequirements = $raw->{self::CHECK_REQUIREMENTS_KEY} ?? true;
2615
2616
        if ($checkRequirements && false === $hasComposerJson && false === $hasComposerLock) {
2617
            $logger->addWarning(
2618
                'The requirement checker could not be used because the composer.json and composer.lock file could not '
2619
                .'be found.'
2620
            );
2621
2622
            return false;
2623
        }
2624
2625
        if ($checkRequirements && $pharStubUsed) {
2626
            $logger->addWarning(
2627
                sprintf(
2628
                    'The "%s" setting has been set but has been ignored since the PHAR built-in stub is being '
2629
                    .'used.',
2630
                    self::CHECK_REQUIREMENTS_KEY
2631
                )
2632
            );
2633
        }
2634
2635
        return $checkRequirements;
2636
    }
2637
2638
    private static function retrievePhpScoperConfig(stdClass $raw, string $basePath, ConfigurationLogger $logger): PhpScoperConfiguration
2639
    {
2640
        self::checkIfDefaultValue($logger, $raw, self::PHP_SCOPER_KEY, self::PHP_SCOPER_CONFIG);
2641
2642
        if (!isset($raw->{self::PHP_SCOPER_KEY})) {
2643
            $configFilePath = make_path_absolute(self::PHP_SCOPER_CONFIG, $basePath);
2644
2645
            return file_exists($configFilePath)
2646
                ? PhpScoperConfiguration::load($configFilePath)
2647
                : PhpScoperConfiguration::load()
2648
             ;
2649
        }
2650
2651
        $configFile = $raw->{self::PHP_SCOPER_KEY};
2652
2653
        Assertion::string($configFile);
2654
2655
        $configFilePath = make_path_absolute($configFile, $basePath);
2656
2657
        Assertion::file($configFilePath);
2658
        Assertion::readable($configFilePath);
2659
2660
        return PhpScoperConfiguration::load($configFilePath);
2661
    }
2662
2663
    /**
2664
     * Runs a Git command on the repository.
2665
     *
2666
     * @return string The trimmed output from the command
2667
     */
2668
    private static function runGitCommand(string $command, string $file): string
2669
    {
2670
        $path = dirname($file);
2671
2672
        $process = Process::fromShellCommandline($command, $path);
2673
2674
        if (0 === $process->run()) {
2675
            return trim($process->getOutput());
2676
        }
2677
2678
        throw new RuntimeException(
2679
            sprintf(
2680
                'The tag or commit hash could not be retrieved from "%s": %s',
2681
                $path,
2682
                $process->getErrorOutput()
2683
            )
2684
        );
2685
    }
2686
2687
    /**
2688
     * @param string[] $compactorClasses
2689
     *
2690
     * @return string[]
2691
     */
2692
    private static function retrievePhpCompactorIgnoredAnnotations(
2693
        stdClass $raw,
2694
        array $compactorClasses,
2695
        ConfigurationLogger $logger
2696
    ): array {
2697
        $hasPhpCompactor = in_array(PhpCompactor::class, $compactorClasses, true) || in_array(LegacyPhp::class, $compactorClasses, true);
2698
2699
        self::checkIfDefaultValue($logger, $raw, self::ANNOTATIONS_KEY, true);
2700
        self::checkIfDefaultValue($logger, $raw, self::ANNOTATIONS_KEY, null);
2701
2702
        if (false === property_exists($raw, self::ANNOTATIONS_KEY)) {
2703
            return self::DEFAULT_IGNORED_ANNOTATIONS;
2704
        }
2705
2706
        if (false === $hasPhpCompactor) {
2707
            $logger->addWarning(
2708
                sprintf(
2709
                    'The "%s" setting has been set but is ignored since no PHP compactor has been configured',
2710
                    self::ANNOTATIONS_KEY
2711
                )
2712
            );
2713
        }
2714
2715
        /** @var null|bool|stdClass $annotations */
2716
        $annotations = $raw->{self::ANNOTATIONS_KEY};
2717
2718
        if (true === $annotations || null === $annotations) {
2719
            return self::DEFAULT_IGNORED_ANNOTATIONS;
2720
        }
2721
2722
        if (false === $annotations) {
0 ignored issues
show
introduced by
The condition false === $annotations is always true.
Loading history...
2723
            return [];
2724
        }
2725
2726
        if (false === property_exists($annotations, self::IGNORED_ANNOTATIONS_KEY)) {
2727
            $logger->addWarning(
2728
                sprintf(
2729
                    'The "%s" setting has been set but no "%s" setting has been found, hence "%s" is treated as'
2730
                    .' if it is set to `false`',
2731
                    self::ANNOTATIONS_KEY,
2732
                    self::IGNORED_ANNOTATIONS_KEY,
2733
                    self::ANNOTATIONS_KEY
2734
                )
2735
            );
2736
2737
            return [];
2738
        }
2739
2740
        $ignored = [];
2741
2742
        if (property_exists($annotations, self::IGNORED_ANNOTATIONS_KEY)
2743
            && in_array($ignored = $annotations->{self::IGNORED_ANNOTATIONS_KEY}, [null, []], true)
2744
        ) {
2745
            self::addRecommendationForDefaultValue($logger, self::ANNOTATIONS_KEY.'#'.self::IGNORED_ANNOTATIONS_KEY);
2746
2747
            return (array) $ignored;
2748
        }
2749
2750
        return $ignored;
2751
    }
2752
2753
    private static function createPhpCompactor(array $ignoredAnnotations): Compactor
2754
    {
2755
        $ignoredAnnotations = array_values(
2756
            array_filter(
2757
                array_map(
2758
                    static function (string $annotation): ?string {
2759
                        return strtolower(trim($annotation));
2760
                    },
2761
                    $ignoredAnnotations
2762
                )
2763
            )
2764
        );
2765
2766
        return new PhpCompactor(
2767
            new DocblockAnnotationParser(
2768
                new DocblockParser(),
2769
                new AnnotationDumper(),
2770
                $ignoredAnnotations
2771
            )
2772
        );
2773
    }
2774
2775
    private static function createPhpScoperCompactor(stdClass $raw, string $basePath, ConfigurationLogger $logger): Compactor
2776
    {
2777
        $phpScoperConfig = self::retrievePhpScoperConfig($raw, $basePath, $logger);
2778
2779
        $phpScoper = (new class() extends ApplicationFactory {
2780
            public static function createScoper(): Scoper
2781
            {
2782
                return parent::createScoper();
2783
            }
2784
        })::createScoper();
2785
2786
        if ([] !== $phpScoperConfig->getWhitelistedFiles()) {
2787
            $whitelistedFiles = array_values(
2788
                array_unique(
2789
                    array_map(
2790
                        static function (string $path) use ($basePath): string {
2791
                            return make_path_relative($path, $basePath);
2792
                        },
2793
                        $phpScoperConfig->getWhitelistedFiles()
2794
                    )
2795
                )
2796
            );
2797
2798
            $phpScoper = new FileWhitelistScoper($phpScoper, ...$whitelistedFiles);
2799
        }
2800
2801
        $prefix = $phpScoperConfig->getPrefix() ?? unique_id('_HumbugBox');
2802
2803
        return new PhpScoperCompactor(
2804
            new SimpleScoper(
2805
                $phpScoper,
2806
                $prefix,
2807
                $phpScoperConfig->getWhitelist(),
2808
                $phpScoperConfig->getPatchers()
2809
            )
2810
        );
2811
    }
2812
2813
    private static function checkIfDefaultValue(
2814
        ConfigurationLogger $logger,
2815
        stdClass $raw,
2816
        string $key,
2817
        $defaultValue = null
2818
    ): void {
2819
        if (false === property_exists($raw, $key)) {
2820
            return;
2821
        }
2822
2823
        $value = $raw->{$key};
2824
2825
        if (null === $value
2826
            || (false === is_object($defaultValue) && $defaultValue === $value)
2827
            || (is_object($defaultValue) && $defaultValue == $value)
2828
        ) {
2829
            $logger->addRecommendation(
2830
                sprintf(
2831
                    'The "%s" setting can be omitted since is set to its default value',
2832
                    $key
2833
                )
2834
            );
2835
        }
2836
    }
2837
2838
    private static function addRecommendationForDefaultValue(ConfigurationLogger $logger, string $key): void
2839
    {
2840
        $logger->addRecommendation(
2841
            sprintf(
2842
                'The "%s" setting can be omitted since is set to its default value',
2843
                $key
2844
            )
2845
        );
2846
    }
2847
}
2848