Passed
Push — master ( d88387...296880 )
by Théo
02:52
created

Configuration::export()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 64
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 39
dl 0
loc 64
rs 9.296
c 0
b 0
f 0
cc 3
nc 1
nop 0

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

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