Passed
Pull Request — master (#400)
by Théo
03:09
created

Configuration::retrieveCompactors()   B

Complexity

Conditions 11
Paths 8

Size

Total Lines 72
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 39
dl 0
loc 72
rs 7.3166
c 0
b 0
f 0
cc 11
nc 8
nop 3

How to fix   Long Method    Complexity   

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\Configuration;
16
17
use function array_diff;
18
use function array_filter;
19
use function array_flip;
20
use function array_key_exists;
21
use function array_keys;
22
use function array_map;
23
use function array_merge;
24
use function array_unique;
25
use function array_values;
26
use function array_walk;
27
use Assert\Assertion;
28
use Closure;
29
use function constant;
30
use function current;
31
use DateTimeImmutable;
32
use DateTimeZone;
33
use function defined;
34
use function dirname;
35
use const E_USER_DEPRECATED;
36
use function explode;
37
use function file_exists;
38
use function getcwd;
39
use Herrera\Box\Compactor\Json as LegacyJson;
40
use Herrera\Box\Compactor\Php as LegacyPhp;
41
use Humbug\PhpScoper\Configuration as PhpScoperConfiguration;
42
use Humbug\PhpScoper\Console\ApplicationFactory;
43
use Humbug\PhpScoper\Scoper;
44
use Humbug\PhpScoper\Scoper\FileWhitelistScoper;
45
use function implode;
46
use function in_array;
47
use function intval;
48
use InvalidArgumentException;
49
use function is_array;
50
use function is_bool;
51
use function is_file;
52
use function is_link;
53
use function is_object;
54
use function is_readable;
55
use function is_string;
56
use function iter\map;
57
use function iter\toArray;
58
use function iter\values;
59
use KevinGH\Box\Annotation\AnnotationDumper;
60
use KevinGH\Box\Annotation\DocblockAnnotationParser;
61
use KevinGH\Box\Annotation\DocblockParser;
62
use KevinGH\Box\Compactor\Compactor;
63
use KevinGH\Box\Compactor\Json as JsonCompactor;
64
use KevinGH\Box\Compactor\Php as PhpCompactor;
65
use KevinGH\Box\Compactor\PhpScoper as PhpScoperCompactor;
66
use KevinGH\Box\Composer\ComposerConfiguration;
67
use KevinGH\Box\Composer\ComposerFile;
68
use KevinGH\Box\Composer\ComposerFiles;
69
use function KevinGH\Box\FileSystem\canonicalize;
70
use function KevinGH\Box\FileSystem\file_contents;
71
use function KevinGH\Box\FileSystem\is_absolute_path;
72
use function KevinGH\Box\FileSystem\longest_common_base_path;
73
use function KevinGH\Box\FileSystem\make_path_absolute;
74
use function KevinGH\Box\FileSystem\make_path_relative;
75
use function KevinGH\Box\get_box_version;
76
use function KevinGH\Box\get_phar_compression_algorithms;
77
use function KevinGH\Box\get_phar_signing_algorithms;
78
use KevinGH\Box\Json\Json;
79
use KevinGH\Box\MapFile;
80
use KevinGH\Box\PhpScoper\SimpleScoper;
81
use KevinGH\Box\scalar;
0 ignored issues
show
Bug introduced by
The type KevinGH\Box\scalar was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

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

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