Passed
Push — master ( 4c3962...96d1ab )
by Théo
03:03
created

Configuration::retrieveAllDirectoriesToInclude()   F

Complexity

Conditions 23
Paths 292

Size

Total Lines 156
Code Lines 87

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 87
dl 0
loc 156
rs 2.1833
c 0
b 0
f 0
cc 23
nc 292
nop 5

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 function KevinGH\Box\unique_id;
82
use function krsort;
83
use Phar;
84
use function preg_match;
85
use function preg_replace;
86
use function property_exists;
87
use function realpath;
88
use RuntimeException;
89
use Seld\JsonLint\ParsingException;
90
use function sort;
91
use const SORT_STRING;
92
use SplFileInfo;
93
use function sprintf;
94
use stdClass;
95
use function strtoupper;
96
use function substr;
97
use Symfony\Component\Finder\Finder;
98
use Symfony\Component\Finder\SplFileInfo as SymfonySplFileInfo;
99
use Symfony\Component\Process\Process;
100
use Symfony\Component\VarDumper\Cloner\VarCloner;
101
use Symfony\Component\VarDumper\Dumper\CliDumper;
102
use function trigger_error;
103
use function trim;
104
105
/**
106
 * @private
107
 */
108
final class Configuration
109
{
110
    private const DEFAULT_OUTPUT_FALLBACK = 'test.phar';
111
    private const DEFAULT_MAIN_SCRIPT = 'index.php';
112
    private const DEFAULT_DATETIME_FORMAT = 'Y-m-d H:i:s T';
113
    private const DEFAULT_REPLACEMENT_SIGIL = '@';
114
    private const DEFAULT_SHEBANG = '#!/usr/bin/env php';
115
    private const DEFAULT_BANNER = <<<'BANNER'
116
Generated by Humbug Box %s.
117
118
@link https://github.com/humbug/box
119
BANNER;
120
    private const FILES_SETTINGS = [
121
        'directories',
122
        'finder',
123
    ];
124
    private const PHP_SCOPER_CONFIG = 'scoper.inc.php';
125
    private const DEFAULT_SIGNING_ALGORITHM = Phar::SHA1;
126
    private const DEFAULT_ALIAS_PREFIX = 'box-auto-generated-alias-';
127
128
    private const DEFAULT_IGNORED_ANNOTATIONS = [
129
        'abstract',
130
        'access',
131
        'annotation',
132
        'api',
133
        'attribute',
134
        'attributes',
135
        'author',
136
        'category',
137
        'code',
138
        'codecoverageignore',
139
        'codecoverageignoreend',
140
        'codecoverageignorestart',
141
        'copyright',
142
        'deprec',
143
        'deprecated',
144
        'endcode',
145
        'example',
146
        'exception',
147
        'filesource',
148
        'final',
149
        'fixme',
150
        'global',
151
        'ignore',
152
        'ingroup',
153
        'inheritdoc',
154
        'internal',
155
        'license',
156
        'link',
157
        'magic',
158
        'method',
159
        'name',
160
        'override',
161
        'package',
162
        'package_version',
163
        'param',
164
        'private',
165
        'property',
166
        'required',
167
        'return',
168
        'see',
169
        'since',
170
        'static',
171
        'staticvar',
172
        'subpackage',
173
        'suppresswarnings',
174
        'target',
175
        'throw',
176
        'throws',
177
        'todo',
178
        'tutorial',
179
        'usedby',
180
        'uses',
181
        'var',
182
        'version',
183
    ];
184
185
    private const ALGORITHM_KEY = 'algorithm';
186
    private const ALIAS_KEY = 'alias';
187
    private const ANNOTATIONS_KEY = 'annotations';
188
    private const IGNORED_ANNOTATIONS_KEY = 'ignore';
189
    private const AUTO_DISCOVERY_KEY = 'force-autodiscovery';
190
    private const BANNER_KEY = 'banner';
191
    private const BANNER_FILE_KEY = 'banner-file';
192
    private const BASE_PATH_KEY = 'base-path';
193
    private const BLACKLIST_KEY = 'blacklist';
194
    private const CHECK_REQUIREMENTS_KEY = 'check-requirements';
195
    private const CHMOD_KEY = 'chmod';
196
    private const COMPACTORS_KEY = 'compactors';
197
    private const COMPRESSION_KEY = 'compression';
198
    private const DATETIME_KEY = 'datetime';
199
    private const DATETIME_FORMAT_KEY = 'datetime-format';
200
    private const DATETIME_FORMAT_DEPRECATED_KEY = 'datetime_format';
201
    private const DIRECTORIES_KEY = 'directories';
202
    private const DIRECTORIES_BIN_KEY = 'directories-bin';
203
    private const DUMP_AUTOLOAD_KEY = 'dump-autoload';
204
    private const EXCLUDE_COMPOSER_FILES_KEY = 'exclude-composer-files';
205
    private const EXCLUDE_DEV_FILES_KEY = 'exclude-dev-files';
206
    private const FILES_KEY = 'files';
207
    private const FILES_BIN_KEY = 'files-bin';
208
    private const FINDER_KEY = 'finder';
209
    private const FINDER_BIN_KEY = 'finder-bin';
210
    private const GIT_KEY = 'git';
211
    private const GIT_COMMIT_KEY = 'git-commit';
212
    private const GIT_COMMIT_SHORT_KEY = 'git-commit-short';
213
    private const GIT_TAG_KEY = 'git-tag';
214
    private const GIT_VERSION_KEY = 'git-version';
215
    private const INTERCEPT_KEY = 'intercept';
216
    private const KEY_KEY = 'key';
217
    private const KEY_PASS_KEY = 'key-pass';
218
    private const MAIN_KEY = 'main';
219
    private const MAP_KEY = 'map';
220
    private const METADATA_KEY = 'metadata';
221
    private const OUTPUT_KEY = 'output';
222
    private const PHP_SCOPER_KEY = 'php-scoper';
223
    private const REPLACEMENT_SIGIL_KEY = 'replacement-sigil';
224
    private const REPLACEMENTS_KEY = 'replacements';
225
    private const SHEBANG_KEY = 'shebang';
226
    private const STUB_KEY = 'stub';
227
228
    private $file;
229
    private $fileMode;
230
    private $alias;
231
    private $basePath;
232
    private $composerJson;
233
    private $composerLock;
234
    private $files;
235
    private $binaryFiles;
236
    private $autodiscoveredFiles;
237
    private $dumpAutoload;
238
    private $excludeComposerFiles;
239
    private $excludeDevFiles;
240
    private $compactors;
241
    private $compressionAlgorithm;
242
    private $mainScriptPath;
243
    private $mainScriptContents;
244
    private $fileMapper;
245
    private $metadata;
246
    private $tmpOutputPath;
247
    private $outputPath;
248
    private $privateKeyPassphrase;
249
    private $privateKeyPath;
250
    private $promptForPrivateKey;
251
    private $processedReplacements;
252
    private $shebang;
253
    private $signingAlgorithm;
254
    private $stubBannerContents;
255
    private $stubBannerPath;
256
    private $stubPath;
257
    private $isInterceptFileFuncs;
258
    private $isStubGenerated;
259
    private $checkRequirements;
260
    private $warnings;
261
    private $recommendations;
262
263
    public static function create(?string $file, stdClass $raw): self
264
    {
265
        $logger = new ConfigurationLogger();
266
267
        $basePath = self::retrieveBasePath($file, $raw, $logger);
268
269
        $composerFiles = self::retrieveComposerFiles($basePath);
270
271
        $dumpAutoload = self::retrieveDumpAutoload($raw, $composerFiles, $logger);
272
273
        $excludeComposerFiles = self::retrieveExcludeComposerFiles($raw, $logger);
274
275
        $mainScriptPath = self::retrieveMainScriptPath($raw, $basePath, $composerFiles->getComposerJson()->getDecodedContents(), $logger);
276
        $mainScriptContents = self::retrieveMainScriptContents($mainScriptPath);
277
278
        [$tmpOutputPath, $outputPath] = self::retrieveOutputPath($raw, $basePath, $mainScriptPath, $logger);
279
280
        $stubPath = self::retrieveStubPath($raw, $basePath, $logger);
281
        $isStubGenerated = self::retrieveIsStubGenerated($raw, $stubPath, $logger);
282
283
        $alias = self::retrieveAlias($raw, null !== $stubPath, $logger);
284
285
        $shebang = self::retrieveShebang($raw, $isStubGenerated, $logger);
286
287
        $stubBannerContents = self::retrieveStubBannerContents($raw, $isStubGenerated, $logger);
288
        $stubBannerPath = self::retrieveStubBannerPath($raw, $basePath, $isStubGenerated, $logger);
289
290
        if (null !== $stubBannerPath) {
291
            $stubBannerContents = file_contents($stubBannerPath);
292
        }
293
294
        $stubBannerContents = self::normalizeStubBannerContents($stubBannerContents);
295
296
        if (null !== $stubBannerPath && self::getDefaultBanner() === $stubBannerContents) {
297
            self::addRecommendationForDefaultValue($logger, self::BANNER_FILE_KEY);
298
        }
299
300
        $isInterceptsFileFunctions = self::retrieveInterceptsFileFunctions($raw, $isStubGenerated, $logger);
301
302
        $checkRequirements = self::retrieveCheckRequirements(
303
            $raw,
304
            null !== $composerFiles->getComposerJson()->getPath(),
305
            null !== $composerFiles->getComposerLock()->getPath(),
306
            false === $isStubGenerated && null === $stubPath,
307
            $logger
308
        );
309
310
        $excludeDevPackages = self::retrieveExcludeDevFiles($raw, $dumpAutoload, $logger);
311
312
        $devPackages = ComposerConfiguration::retrieveDevPackages(
313
            $basePath,
314
            $composerFiles->getComposerJson()->getDecodedContents(),
315
            $composerFiles->getComposerLock()->getDecodedContents(),
316
            $excludeDevPackages
317
        );
318
319
        /**
320
         * @var string[]
321
         * @var Closure  $blacklistFilter
322
         */
323
        [$excludedPaths, $blacklistFilter] = self::retrieveBlacklistFilter(
324
            $raw,
325
            $basePath,
326
            $logger,
327
            $tmpOutputPath,
328
            $outputPath,
329
            $mainScriptPath
330
        );
331
        // Excluded paths above is a bit misleading since including a file directly has precedence over the blacklist.
332
        // If you consider the following:
333
        //
334
        // {
335
        //   "files": ["file1"],
336
        //   "blacklist": ["file1"],
337
        // }
338
        //
339
        // In the end the file "file1" _will_ be included: blacklist are here to help out to exclude files for finders
340
        // and directories but the user should always have the possibility to force his way to include a file.
341
        //
342
        // The exception however, is for the following which is essential for the good functioning of Box
343
        $alwaysExcludedPaths = array_map(
344
            static function (string $excludedPath) use ($basePath): string {
345
                return self::normalizePath($excludedPath, $basePath);
346
            },
347
            array_filter([$tmpOutputPath, $outputPath, $mainScriptPath])
348
        );
349
350
        $autodiscoverFiles = self::autodiscoverFiles($file, $raw);
351
        $forceFilesAutodiscovery = self::retrieveForceFilesAutodiscovery($raw, $logger);
352
353
        $filesAggregate = self::collectFiles(
354
            $raw,
355
            $basePath,
356
            $mainScriptPath,
357
            $blacklistFilter,
358
            $excludedPaths,
359
            $alwaysExcludedPaths,
360
            $devPackages,
361
            $composerFiles,
362
            $autodiscoverFiles,
363
            $forceFilesAutodiscovery,
364
            $logger
365
        );
366
        $binaryFilesAggregate = self::collectBinaryFiles(
367
            $raw,
368
            $basePath,
369
            $blacklistFilter,
370
            $excludedPaths,
371
            $alwaysExcludedPaths,
372
            $devPackages,
373
            $logger
374
        );
375
376
        $compactors = self::retrieveCompactors($raw, $basePath, $logger);
377
        $compressionAlgorithm = self::retrieveCompressionAlgorithm($raw, $logger);
378
379
        $fileMode = self::retrieveFileMode($raw, $logger);
380
381
        $map = self::retrieveMap($raw, $logger);
382
        $fileMapper = new MapFile($basePath, $map);
383
384
        $metadata = self::retrieveMetadata($raw, $logger);
385
386
        $signingAlgorithm = self::retrieveSigningAlgorithm($raw, $logger);
387
        $promptForPrivateKey = self::retrievePromptForPrivateKey($raw, $signingAlgorithm, $logger);
388
        $privateKeyPath = self::retrievePrivateKeyPath($raw, $basePath, $signingAlgorithm, $logger);
389
        $privateKeyPassphrase = self::retrievePrivateKeyPassphrase($raw, $signingAlgorithm, $logger);
390
391
        $replacements = self::retrieveReplacements($raw, $file, $logger);
392
393
        return new self(
394
            $file,
395
            $alias,
396
            $basePath,
397
            $composerFiles->getComposerJson(),
398
            $composerFiles->getComposerLock(),
399
            $filesAggregate,
400
            $binaryFilesAggregate,
401
            $autodiscoverFiles || $forceFilesAutodiscovery,
402
            $dumpAutoload,
403
            $excludeComposerFiles,
404
            $excludeDevPackages,
405
            $compactors,
406
            $compressionAlgorithm,
407
            $fileMode,
408
            $mainScriptPath,
409
            $mainScriptContents,
410
            $fileMapper,
411
            $metadata,
412
            $tmpOutputPath,
413
            $outputPath,
414
            $privateKeyPassphrase,
415
            $privateKeyPath,
416
            $promptForPrivateKey,
417
            $replacements,
418
            $shebang,
419
            $signingAlgorithm,
420
            $stubBannerContents,
421
            $stubBannerPath,
422
            $stubPath,
423
            $isInterceptsFileFunctions,
424
            $isStubGenerated,
425
            $checkRequirements,
426
            $logger->getWarnings(),
427
            $logger->getRecommendations()
428
        );
429
    }
430
431
    /**
432
     * @param string        $basePath             Utility to private the base path used and be able to retrieve a
433
     *                                            path relative to it (the base path)
434
     * @param array         $composerJson         The first element is the path to the `composer.json` file as a
435
     *                                            string and the second element its decoded contents as an
436
     *                                            associative array.
437
     * @param array         $composerLock         The first element is the path to the `composer.lock` file as a
438
     *                                            string and the second element its decoded contents as an
439
     *                                            associative array.
440
     * @param SplFileInfo[] $files                List of files
441
     * @param SplFileInfo[] $binaryFiles          List of binary files
442
     * @param bool          $dumpAutoload         Whether or not the Composer autoloader should be dumped
443
     * @param bool          $excludeComposerFiles Whether or not the Composer files composer.json, composer.lock and
444
     *                                            installed.json should be removed from the PHAR
445
     * @param Compactor[]   $compactors           List of file contents compactors
446
     * @param null|int      $compressionAlgorithm Compression algorithm constant value. See the \Phar class constants
447
     * @param null|int      $fileMode             File mode in octal form
448
     * @param string        $mainScriptPath       The main script file path
449
     * @param string        $mainScriptContents   The processed content of the main script file
450
     * @param MapFile       $fileMapper           Utility to map the files from outside and inside the PHAR
451
     * @param mixed         $metadata             The PHAR Metadata
452
     * @param bool          $promptForPrivateKey  If the user should be prompted for the private key passphrase
453
     * @param array         $replacements         The processed list of replacement placeholders and their values
454
     * @param null|string   $shebang              The shebang line
455
     * @param int           $signingAlgorithm     The PHAR siging algorithm. See \Phar constants
456
     * @param null|string   $stubBannerContents   The stub banner comment
457
     * @param null|string   $stubBannerPath       The path to the stub banner comment file
458
     * @param null|string   $stubPath             The PHAR stub file path
459
     * @param bool          $isInterceptFileFuncs Whether or not Phar::interceptFileFuncs() should be used
460
     * @param bool          $isStubGenerated      Whether or not if the PHAR stub should be generated
461
     * @param bool          $checkRequirements    Whether the PHAR will check the application requirements before
462
     *                                            running
463
     * @param string[]      $warnings
464
     * @param string[]      $recommendations
465
     */
466
    private function __construct(
467
        ?string $file,
468
        string $alias,
469
        string $basePath,
470
        ComposerFile $composerJson,
471
        ComposerFile $composerLock,
472
        array $files,
473
        array $binaryFiles,
474
        bool $autodiscoveredFiles,
475
        bool $dumpAutoload,
476
        bool $excludeComposerFiles,
477
        bool $excludeDevPackages,
478
        array $compactors,
479
        ?int $compressionAlgorithm,
480
        ?int $fileMode,
481
        ?string $mainScriptPath,
482
        ?string $mainScriptContents,
483
        MapFile $fileMapper,
484
        $metadata,
485
        string $tmpOutputPath,
486
        string $outputPath,
487
        ?string $privateKeyPassphrase,
488
        ?string $privateKeyPath,
489
        bool $promptForPrivateKey,
490
        array $replacements,
491
        ?string $shebang,
492
        int $signingAlgorithm,
493
        ?string $stubBannerContents,
494
        ?string $stubBannerPath,
495
        ?string $stubPath,
496
        bool $isInterceptFileFuncs,
497
        bool $isStubGenerated,
498
        bool $checkRequirements,
499
        array $warnings,
500
        array $recommendations
501
    ) {
502
        Assertion::nullOrInArray(
503
            $compressionAlgorithm,
504
            get_phar_compression_algorithms(),
505
            sprintf(
506
                'Invalid compression algorithm "%%s", use one of "%s" instead.',
507
                implode('", "', array_keys(get_phar_compression_algorithms()))
508
            )
509
        );
510
511
        if (null === $mainScriptPath) {
512
            Assertion::null($mainScriptContents);
513
        } else {
514
            Assertion::notNull($mainScriptContents);
515
        }
516
517
        $this->file = $file;
518
        $this->alias = $alias;
519
        $this->basePath = $basePath;
520
        $this->composerJson = $composerJson;
521
        $this->composerLock = $composerLock;
522
        $this->files = $files;
523
        $this->binaryFiles = $binaryFiles;
524
        $this->autodiscoveredFiles = $autodiscoveredFiles;
525
        $this->dumpAutoload = $dumpAutoload;
526
        $this->excludeComposerFiles = $excludeComposerFiles;
527
        $this->excludeDevFiles = $excludeDevPackages;
528
        $this->compactors = $compactors;
529
        $this->compressionAlgorithm = $compressionAlgorithm;
530
        $this->fileMode = $fileMode;
531
        $this->mainScriptPath = $mainScriptPath;
532
        $this->mainScriptContents = $mainScriptContents;
533
        $this->fileMapper = $fileMapper;
534
        $this->metadata = $metadata;
535
        $this->tmpOutputPath = $tmpOutputPath;
536
        $this->outputPath = $outputPath;
537
        $this->privateKeyPassphrase = $privateKeyPassphrase;
538
        $this->privateKeyPath = $privateKeyPath;
539
        $this->promptForPrivateKey = $promptForPrivateKey;
540
        $this->processedReplacements = $replacements;
541
        $this->shebang = $shebang;
542
        $this->signingAlgorithm = $signingAlgorithm;
543
        $this->stubBannerContents = $stubBannerContents;
544
        $this->stubBannerPath = $stubBannerPath;
545
        $this->stubPath = $stubPath;
546
        $this->isInterceptFileFuncs = $isInterceptFileFuncs;
547
        $this->isStubGenerated = $isStubGenerated;
548
        $this->checkRequirements = $checkRequirements;
549
        $this->warnings = $warnings;
550
        $this->recommendations = $recommendations;
551
    }
552
553
    public function export(): string
554
    {
555
        $exportedConfig = clone $this;
556
557
        $basePath = $exportedConfig->basePath;
558
559
        /**
560
         * @param null|SplFileInfo|string $path
561
         */
562
        $normalizePath = static function ($path) use ($basePath): ?string {
563
            if (null === $path) {
564
                return null;
565
            }
566
567
            if ($path instanceof SplFileInfo) {
568
                $path = $path->getPathname();
569
            }
570
571
            return make_path_relative($path, $basePath);
572
        };
573
574
        $normalizeProperty = static function (&$property) use ($normalizePath): void {
575
            $property = $normalizePath($property);
576
        };
577
578
        $normalizeFiles = static function (&$files) use ($normalizePath): void {
579
            $files = array_map($normalizePath, $files);
580
            sort($files, SORT_STRING);
581
        };
582
583
        $normalizeFiles($exportedConfig->files);
584
        $normalizeFiles($exportedConfig->binaryFiles);
585
586
        $exportedConfig->composerJson = new ComposerFile(
587
            $normalizePath($exportedConfig->composerJson->getPath()),
588
            $exportedConfig->composerJson->getDecodedContents()
589
        );
590
        $exportedConfig->composerLock = new ComposerFile(
591
            $normalizePath($exportedConfig->composerLock->getPath()),
592
            $exportedConfig->composerLock->getDecodedContents()
593
        );
594
595
        $normalizeProperty($exportedConfig->file);
596
        $normalizeProperty($exportedConfig->mainScriptPath);
597
        $normalizeProperty($exportedConfig->tmpOutputPath);
598
        $normalizeProperty($exportedConfig->outputPath);
599
        $normalizeProperty($exportedConfig->privateKeyPath);
600
        $normalizeProperty($exportedConfig->stubBannerPath);
601
        $normalizeProperty($exportedConfig->stubPath);
602
603
        $exportedConfig->compressionAlgorithm = array_flip(get_phar_compression_algorithms())[$exportedConfig->compressionAlgorithm ?? Phar::NONE];
604
        $exportedConfig->signingAlgorithm = array_flip(get_phar_signing_algorithms())[$exportedConfig->signingAlgorithm];
605
        $exportedConfig->compactors = array_map('get_class', $exportedConfig->compactors);
606
        $exportedConfig->fileMode = '0'.decoct($exportedConfig->fileMode);
607
608
        $cloner = new VarCloner();
609
        $cloner->setMaxItems(-1);
610
        $cloner->setMaxString(-1);
611
612
        $splInfoCaster = static function (SplFileInfo $fileInfo) use ($normalizePath): array {
613
            return [$normalizePath($fileInfo)];
614
        };
615
616
        $cloner->addCasters([
617
            SplFileInfo::class => $splInfoCaster,
618
            SymfonySplFileInfo::class => $splInfoCaster,
619
        ]);
620
621
        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...
622
            $cloner->cloneVar($exportedConfig),
623
            true
624
        );
625
    }
626
627
    public function getConfigurationFile(): ?string
628
    {
629
        return $this->file;
630
    }
631
632
    public function getAlias(): string
633
    {
634
        return $this->alias;
635
    }
636
637
    public function getBasePath(): string
638
    {
639
        return $this->basePath;
640
    }
641
642
    public function getComposerJson(): ?string
643
    {
644
        return $this->composerJson->getPath();
645
    }
646
647
    public function getDecodedComposerJsonContents(): ?array
648
    {
649
        return null === $this->composerJson->getPath() ? null : $this->composerJson->getDecodedContents();
650
    }
651
652
    public function getComposerLock(): ?string
653
    {
654
        return $this->composerLock->getPath();
655
    }
656
657
    public function getDecodedComposerLockContents(): ?array
658
    {
659
        return null === $this->composerLock->getPath() ? null : $this->composerLock->getDecodedContents();
660
    }
661
662
    /**
663
     * @return SplFileInfo[]
664
     */
665
    public function getFiles(): array
666
    {
667
        return $this->files;
668
    }
669
670
    /**
671
     * @return SplFileInfo[]
672
     */
673
    public function getBinaryFiles(): array
674
    {
675
        return $this->binaryFiles;
676
    }
677
678
    public function hasAutodiscoveredFiles(): bool
679
    {
680
        return $this->autodiscoveredFiles;
681
    }
682
683
    public function dumpAutoload(): bool
684
    {
685
        return $this->dumpAutoload;
686
    }
687
688
    public function excludeComposerFiles(): bool
689
    {
690
        return $this->excludeComposerFiles;
691
    }
692
693
    public function excludeDevFiles(): bool
694
    {
695
        return $this->excludeDevFiles;
696
    }
697
698
    /**
699
     * @return Compactor[] the list of compactors
700
     */
701
    public function getCompactors(): array
702
    {
703
        return $this->compactors;
704
    }
705
706
    public function getCompressionAlgorithm(): ?int
707
    {
708
        return $this->compressionAlgorithm;
709
    }
710
711
    public function getFileMode(): ?int
712
    {
713
        return $this->fileMode;
714
    }
715
716
    public function hasMainScript(): bool
717
    {
718
        return null !== $this->mainScriptPath;
719
    }
720
721
    public function getMainScriptPath(): string
722
    {
723
        Assertion::notNull(
724
            $this->mainScriptPath,
725
            'Cannot retrieve the main script path: no main script configured.'
726
        );
727
728
        return $this->mainScriptPath;
729
    }
730
731
    public function getMainScriptContents(): string
732
    {
733
        Assertion::notNull(
734
            $this->mainScriptPath,
735
            'Cannot retrieve the main script contents: no main script configured.'
736
        );
737
738
        return $this->mainScriptContents;
739
    }
740
741
    public function checkRequirements(): bool
742
    {
743
        return $this->checkRequirements;
744
    }
745
746
    public function getTmpOutputPath(): string
747
    {
748
        return $this->tmpOutputPath;
749
    }
750
751
    public function getOutputPath(): string
752
    {
753
        return $this->outputPath;
754
    }
755
756
    public function getFileMapper(): MapFile
757
    {
758
        return $this->fileMapper;
759
    }
760
761
    /**
762
     * @return mixed
763
     */
764
    public function getMetadata()
765
    {
766
        return $this->metadata;
767
    }
768
769
    public function getPrivateKeyPassphrase(): ?string
770
    {
771
        return $this->privateKeyPassphrase;
772
    }
773
774
    public function getPrivateKeyPath(): ?string
775
    {
776
        return $this->privateKeyPath;
777
    }
778
779
    /**
780
     * @deprecated Use promptForPrivateKey() instead
781
     */
782
    public function isPrivateKeyPrompt(): bool
783
    {
784
        return $this->promptForPrivateKey;
785
    }
786
787
    public function promptForPrivateKey(): bool
788
    {
789
        return $this->promptForPrivateKey;
790
    }
791
792
    /**
793
     * @return scalar[]
794
     */
795
    public function getReplacements(): array
796
    {
797
        return $this->processedReplacements;
798
    }
799
800
    public function getShebang(): ?string
801
    {
802
        return $this->shebang;
803
    }
804
805
    public function getSigningAlgorithm(): int
806
    {
807
        return $this->signingAlgorithm;
808
    }
809
810
    public function getStubBannerContents(): ?string
811
    {
812
        return $this->stubBannerContents;
813
    }
814
815
    public function getStubBannerPath(): ?string
816
    {
817
        return $this->stubBannerPath;
818
    }
819
820
    public function getStubPath(): ?string
821
    {
822
        return $this->stubPath;
823
    }
824
825
    public function isInterceptFileFuncs(): bool
826
    {
827
        return $this->isInterceptFileFuncs;
828
    }
829
830
    public function isStubGenerated(): bool
831
    {
832
        return $this->isStubGenerated;
833
    }
834
835
    /**
836
     * @return string[]
837
     */
838
    public function getWarnings(): array
839
    {
840
        return $this->warnings;
841
    }
842
843
    /**
844
     * @return string[]
845
     */
846
    public function getRecommendations(): array
847
    {
848
        return $this->recommendations;
849
    }
850
851
    private static function retrieveAlias(stdClass $raw, bool $userStubUsed, ConfigurationLogger $logger): string
852
    {
853
        self::checkIfDefaultValue($logger, $raw, self::ALIAS_KEY);
854
855
        if (false === isset($raw->{self::ALIAS_KEY})) {
856
            return unique_id(self::DEFAULT_ALIAS_PREFIX).'.phar';
857
        }
858
859
        $alias = trim($raw->{self::ALIAS_KEY});
860
861
        Assertion::notEmpty($alias, 'A PHAR alias cannot be empty when provided.');
862
863
        if ($userStubUsed) {
864
            $logger->addWarning(
865
                sprintf(
866
                    'The "%s" setting has been set but is ignored since a custom stub path is used',
867
                    self::ALIAS_KEY
868
                )
869
            );
870
        }
871
872
        return $alias;
873
    }
874
875
    private static function retrieveBasePath(?string $file, stdClass $raw, ConfigurationLogger $logger): string
876
    {
877
        if (null === $file) {
878
            return getcwd();
879
        }
880
881
        if (false === isset($raw->{self::BASE_PATH_KEY})) {
882
            return realpath(dirname($file));
883
        }
884
885
        $basePath = trim($raw->{self::BASE_PATH_KEY});
886
887
        Assertion::directory(
888
            $basePath,
889
            'The base path "%s" is not a directory or does not exist.'
890
        );
891
892
        $basePath = realpath($basePath);
893
        $defaultPath = realpath(dirname($file));
894
895
        if ($basePath === $defaultPath) {
896
            self::addRecommendationForDefaultValue($logger, self::BASE_PATH_KEY);
897
        }
898
899
        return $basePath;
900
    }
901
902
    /**
903
     * Checks if files should be auto-discovered. It does NOT account for the force-autodiscovery setting.
904
     */
905
    private static function autodiscoverFiles(?string $file, stdClass $raw): bool
906
    {
907
        if (null === $file) {
908
            return true;
909
        }
910
911
        $associativeRaw = (array) $raw;
912
913
        return self::FILES_SETTINGS === array_diff(self::FILES_SETTINGS, array_keys($associativeRaw));
914
    }
915
916
    private static function retrieveForceFilesAutodiscovery(stdClass $raw, ConfigurationLogger $logger): bool
917
    {
918
        self::checkIfDefaultValue($logger, $raw, self::AUTO_DISCOVERY_KEY, false);
919
920
        return $raw->{self::AUTO_DISCOVERY_KEY} ?? false;
921
    }
922
923
    private static function retrieveBlacklistFilter(
924
        stdClass $raw,
925
        string $basePath,
926
        ConfigurationLogger $logger,
927
        ?string ...$excludedPaths
928
    ): array {
929
        $blacklist = array_flip(
930
            self::retrieveBlacklist($raw, $basePath, $logger, ...$excludedPaths)
931
        );
932
933
        $blacklistFilter = static function (SplFileInfo $file) use ($blacklist): ?bool {
934
            if ($file->isLink()) {
935
                return false;
936
            }
937
938
            if (false === $file->getRealPath()) {
939
                return false;
940
            }
941
942
            if (array_key_exists($file->getRealPath(), $blacklist)) {
943
                return false;
944
            }
945
946
            return null;
947
        };
948
949
        return [array_keys($blacklist), $blacklistFilter];
950
    }
951
952
    /**
953
     * @param null[]|string[] $excludedPaths
954
     *
955
     * @return string[]
956
     */
957
    private static function retrieveBlacklist(
958
        stdClass $raw,
959
        string $basePath,
960
        ConfigurationLogger $logger,
961
        ?string ...$excludedPaths
962
    ): array {
963
        self::checkIfDefaultValue($logger, $raw, self::BLACKLIST_KEY, []);
964
965
        $normalizedBlacklist = array_map(
966
            static function (string $excludedPath) use ($basePath): string {
967
                return self::normalizePath($excludedPath, $basePath);
968
            },
969
            array_filter($excludedPaths)
970
        );
971
972
        /** @var string[] $blacklist */
973
        $blacklist = $raw->{self::BLACKLIST_KEY} ?? [];
974
975
        foreach ($blacklist as $file) {
976
            $normalizedBlacklist[] = self::normalizePath($file, $basePath);
977
            $normalizedBlacklist[] = canonicalize(make_path_relative(trim($file), $basePath));
978
        }
979
980
        return array_unique($normalizedBlacklist);
981
    }
982
983
    /**
984
     * @param string[] $excludedPaths
985
     * @param string[] $alwaysExcludedPaths
986
     * @param string[] $devPackages
987
     *
988
     * @return SplFileInfo[]
989
     */
990
    private static function collectFiles(
991
        stdClass $raw,
992
        string $basePath,
993
        ?string $mainScriptPath,
994
        Closure $blacklistFilter,
995
        array $excludedPaths,
996
        array $alwaysExcludedPaths,
997
        array $devPackages,
998
        ComposerFiles $composerFiles,
999
        bool $autodiscoverFiles,
1000
        bool $forceFilesAutodiscovery,
1001
        ConfigurationLogger $logger
1002
    ): array {
1003
        $files = [self::retrieveFiles($raw, self::FILES_KEY, $basePath, $composerFiles, $alwaysExcludedPaths, $logger)];
1004
1005
        if ($autodiscoverFiles || $forceFilesAutodiscovery) {
1006
            [$filesToAppend, $directories] = self::retrieveAllDirectoriesToInclude(
1007
                $basePath,
1008
                $composerFiles->getComposerJson()->getDecodedContents(),
1009
                $devPackages,
1010
                $composerFiles->getPaths(),
1011
                $excludedPaths
1012
            );
1013
1014
            $files[] = self::wrapInSplFileInfo($filesToAppend);
1015
1016
            $files[] = self::retrieveAllFiles(
1017
                $basePath,
1018
                $directories,
1019
                $mainScriptPath,
1020
                $blacklistFilter,
1021
                $excludedPaths,
1022
                $devPackages
1023
            );
1024
        }
1025
1026
        if (false === $autodiscoverFiles) {
1027
            $files[] = self::retrieveDirectories(
1028
                $raw,
1029
                self::DIRECTORIES_KEY,
1030
                $basePath,
1031
                $blacklistFilter,
1032
                $excludedPaths,
1033
                $logger
1034
            );
1035
1036
            $filesFromFinders = self::retrieveFilesFromFinders(
1037
                $raw,
1038
                self::FINDER_KEY,
1039
                $basePath,
1040
                $blacklistFilter,
1041
                $devPackages,
1042
                $logger
1043
            );
1044
1045
            foreach ($filesFromFinders as $filesFromFinder) {
1046
                // Avoid an array_merge here as it can be quite expansive at this stage depending of the number of files
1047
                $files[] = $filesFromFinder;
1048
            }
1049
1050
            $files[] = self::wrapInSplFileInfo($composerFiles->getPaths());
1051
        }
1052
1053
        return self::retrieveFilesAggregate(...$files);
1054
    }
1055
1056
    /**
1057
     * @param string[] $excludedPaths
1058
     * @param string[] $alwaysExcludedPaths
1059
     * @param string[] $devPackages
1060
     *
1061
     * @return SplFileInfo[]
1062
     */
1063
    private static function collectBinaryFiles(
1064
        stdClass $raw,
1065
        string $basePath,
1066
        Closure $blacklistFilter,
1067
        array $excludedPaths,
1068
        array $alwaysExcludedPaths,
1069
        array $devPackages,
1070
        ConfigurationLogger $logger
1071
    ): array {
1072
        $binaryFiles = self::retrieveFiles($raw, self::FILES_BIN_KEY, $basePath, ComposerFiles::createEmpty(), $alwaysExcludedPaths, $logger);
1073
1074
        $binaryDirectories = self::retrieveDirectories(
1075
            $raw,
1076
            self::DIRECTORIES_BIN_KEY,
1077
            $basePath,
1078
            $blacklistFilter,
1079
            $excludedPaths,
1080
            $logger
1081
        );
1082
1083
        $binaryFilesFromFinders = self::retrieveFilesFromFinders(
1084
            $raw,
1085
            self::FINDER_BIN_KEY,
1086
            $basePath,
1087
            $blacklistFilter,
1088
            $devPackages,
1089
            $logger
1090
        );
1091
1092
        return self::retrieveFilesAggregate($binaryFiles, $binaryDirectories, ...$binaryFilesFromFinders);
1093
    }
1094
1095
    /**
1096
     * @param string[] $excludedFiles
1097
     *
1098
     * @return SplFileInfo[]
1099
     */
1100
    private static function retrieveFiles(
1101
        stdClass $raw,
1102
        string $key,
1103
        string $basePath,
1104
        ComposerFiles $composerFiles,
1105
        array $excludedFiles,
1106
        ConfigurationLogger $logger
1107
    ): array {
1108
        self::checkIfDefaultValue($logger, $raw, $key, []);
1109
1110
        $excludedFiles = array_flip($excludedFiles);
1111
        $files = array_filter([
1112
            $composerFiles->getComposerJson()->getPath(),
1113
            $composerFiles->getComposerLock()->getPath(),
1114
        ]);
1115
1116
        if (false === isset($raw->{$key})) {
1117
            return self::wrapInSplFileInfo($files);
1118
        }
1119
1120
        if ([] === (array) $raw->{$key}) {
1121
            return self::wrapInSplFileInfo($files);
1122
        }
1123
1124
        $files = array_merge((array) $raw->{$key}, $files);
1125
1126
        Assertion::allString($files);
1127
1128
        $normalizePath = static function (string $file) use ($basePath, $key, $excludedFiles): ?SplFileInfo {
1129
            $file = self::normalizePath($file, $basePath);
1130
1131
            Assertion::false(
1132
                is_link($file),
1133
                sprintf(
1134
                    'Cannot add the link "%s": links are not supported.',
1135
                    $file
1136
                )
1137
            );
1138
1139
            Assertion::file(
1140
                $file,
1141
                sprintf(
1142
                    '"%s" must contain a list of existing files. Could not find "%%s".',
1143
                    $key
1144
                )
1145
            );
1146
1147
            return array_key_exists($file, $excludedFiles) ? null : new SplFileInfo($file);
1148
        };
1149
1150
        return array_filter(array_map($normalizePath, $files));
1151
    }
1152
1153
    /**
1154
     * @param string   $key           Config property name
1155
     * @param string[] $excludedPaths
1156
     *
1157
     * @return iterable|SplFileInfo[]
1158
     */
1159
    private static function retrieveDirectories(
1160
        stdClass $raw,
1161
        string $key,
1162
        string $basePath,
1163
        Closure $blacklistFilter,
1164
        array $excludedPaths,
1165
        ConfigurationLogger $logger
1166
    ): iterable {
1167
        $directories = self::retrieveDirectoryPaths($raw, $key, $basePath, $logger);
1168
1169
        if ([] !== $directories) {
1170
            $finder = Finder::create()
1171
                ->files()
1172
                ->filter($blacklistFilter)
1173
                ->ignoreVCS(true)
1174
                ->in($directories)
1175
            ;
1176
1177
            foreach ($excludedPaths as $excludedPath) {
1178
                $finder->notPath($excludedPath);
1179
            }
1180
1181
            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...
1182
        }
1183
1184
        return [];
1185
    }
1186
1187
    /**
1188
     * @param string[] $devPackages
1189
     *
1190
     * @return iterable[]|SplFileInfo[][]
1191
     */
1192
    private static function retrieveFilesFromFinders(
1193
        stdClass $raw,
1194
        string $key,
1195
        string $basePath,
1196
        Closure $blacklistFilter,
1197
        array $devPackages,
1198
        ConfigurationLogger $logger
1199
    ): array {
1200
        self::checkIfDefaultValue($logger, $raw, $key, []);
1201
1202
        if (false === isset($raw->{$key})) {
1203
            return [];
1204
        }
1205
1206
        $finder = $raw->{$key};
1207
1208
        return self::processFinders($finder, $basePath, $blacklistFilter, $devPackages);
1209
    }
1210
1211
    /**
1212
     * @param iterable[]|SplFileInfo[][] $fileIterators
1213
     *
1214
     * @return SplFileInfo[]
1215
     */
1216
    private static function retrieveFilesAggregate(iterable ...$fileIterators): array
1217
    {
1218
        $files = [];
1219
1220
        foreach ($fileIterators as $fileIterator) {
1221
            foreach ($fileIterator as $file) {
1222
                $files[(string) $file] = $file;
1223
            }
1224
        }
1225
1226
        return array_values($files);
1227
    }
1228
1229
    /**
1230
     * @param string[] $devPackages
1231
     *
1232
     * @return Finder[]|SplFileInfo[][]
1233
     */
1234
    private static function processFinders(
1235
        array $findersConfig,
1236
        string $basePath,
1237
        Closure $blacklistFilter,
1238
        array $devPackages
1239
    ): array {
1240
        $processFinderConfig = static function (stdClass $config) use ($basePath, $blacklistFilter, $devPackages) {
1241
            return self::processFinder($config, $basePath, $blacklistFilter, $devPackages);
1242
        };
1243
1244
        return array_map($processFinderConfig, $findersConfig);
1245
    }
1246
1247
    /**
1248
     * @param string[] $devPackages
1249
     *
1250
     * @return Finder|SplFileInfo[]
1251
     */
1252
    private static function processFinder(
1253
        stdClass $config,
1254
        string $basePath,
1255
        Closure $blacklistFilter,
1256
        array $devPackages
1257
    ): Finder {
1258
        $finder = Finder::create()
1259
            ->files()
1260
            ->filter($blacklistFilter)
1261
            ->filter(
1262
                static function (SplFileInfo $fileInfo) use ($devPackages): bool {
1263
                    foreach ($devPackages as $devPackage) {
1264
                        if ($devPackage === longest_common_base_path([$devPackage, $fileInfo->getRealPath()])) {
1265
                            // File belongs to the dev package
1266
                            return false;
1267
                        }
1268
                    }
1269
1270
                    return true;
1271
                }
1272
            )
1273
            ->ignoreVCS(true)
1274
        ;
1275
1276
        $normalizedConfig = (static function (array $config, Finder $finder): array {
1277
            $normalizedConfig = [];
1278
1279
            foreach ($config as $method => $arguments) {
1280
                $method = trim($method);
1281
                $arguments = (array) $arguments;
1282
1283
                Assertion::methodExists(
1284
                    $method,
1285
                    $finder,
1286
                    'The method "Finder::%s" does not exist.'
1287
                );
1288
1289
                $normalizedConfig[$method] = $arguments;
1290
            }
1291
1292
            krsort($normalizedConfig);
1293
1294
            return $normalizedConfig;
1295
        })((array) $config, $finder);
1296
1297
        $createNormalizedDirectories = static function (string $directory) use ($basePath): ?string {
1298
            $directory = self::normalizePath($directory, $basePath);
1299
1300
            Assertion::false(
1301
                is_link($directory),
1302
                sprintf(
1303
                    'Cannot append the link "%s" to the Finder: links are not supported.',
1304
                    $directory
1305
                )
1306
            );
1307
1308
            Assertion::directory($directory);
1309
1310
            return $directory;
1311
        };
1312
1313
        $normalizeFileOrDirectory = static function (?string &$fileOrDirectory) use ($basePath, $blacklistFilter): void {
1314
            if (null === $fileOrDirectory) {
1315
                return;
1316
            }
1317
1318
            $fileOrDirectory = self::normalizePath($fileOrDirectory, $basePath);
1319
1320
            Assertion::false(
1321
                is_link($fileOrDirectory),
1322
                sprintf(
1323
                    'Cannot append the link "%s" to the Finder: links are not supported.',
1324
                    $fileOrDirectory
1325
                )
1326
            );
1327
1328
            Assertion::true(
1329
                file_exists($fileOrDirectory),
1330
                sprintf(
1331
                    'Path "%s" was expected to be a file or directory. It may be a symlink (which are unsupported).',
1332
                    $fileOrDirectory
1333
                )
1334
            );
1335
1336
            if (false === is_file($fileOrDirectory)) {
1337
                Assertion::directory($fileOrDirectory);
1338
            } else {
1339
                Assertion::file($fileOrDirectory);
1340
            }
1341
1342
            if (false === $blacklistFilter(new SplFileInfo($fileOrDirectory))) {
1343
                $fileOrDirectory = null;
1344
            }
1345
        };
1346
1347
        foreach ($normalizedConfig as $method => $arguments) {
1348
            if ('in' === $method) {
1349
                $normalizedConfig[$method] = $arguments = array_map($createNormalizedDirectories, $arguments);
1350
            }
1351
1352
            if ('exclude' === $method) {
1353
                $arguments = array_unique(array_map('trim', $arguments));
1354
            }
1355
1356
            if ('append' === $method) {
1357
                array_walk($arguments, $normalizeFileOrDirectory);
1358
1359
                $arguments = [array_filter($arguments)];
1360
            }
1361
1362
            foreach ($arguments as $argument) {
1363
                $finder->$method($argument);
1364
            }
1365
        }
1366
1367
        return $finder;
1368
    }
1369
1370
    /**
1371
     * @param string[] $devPackages
1372
     * @param string[] $filesToAppend
1373
     *
1374
     * @return string[][]
1375
     */
1376
    private static function retrieveAllDirectoriesToInclude(
1377
        string $basePath,
1378
        ?array $decodedJsonContents,
1379
        array $devPackages,
1380
        array $filesToAppend,
1381
        array $excludedPaths
1382
    ): array {
1383
        $toString = static function ($file): string {
1384
            // @param string|SplFileInfo $file
1385
            return (string) $file;
1386
        };
1387
1388
        if (null !== $decodedJsonContents && array_key_exists('vendor-dir', $decodedJsonContents)) {
1389
            $vendorDir = self::normalizePath($decodedJsonContents['vendor-dir'], $basePath);
1390
        } else {
1391
            $vendorDir = self::normalizePath('vendor', $basePath);
1392
        }
1393
1394
        if (file_exists($vendorDir)) {
1395
            // The installed.json file is necessary for dumping the autoload correctly. Note however that it will not exists if no
1396
            // dependencies are included in the `composer.json`
1397
            $installedJsonFiles = self::normalizePath($vendorDir.'/composer/installed.json', $basePath);
1398
1399
            if (file_exists($installedJsonFiles)) {
1400
                $filesToAppend[] = $installedJsonFiles;
1401
            }
1402
1403
            $vendorPackages = toArray(values(map(
1404
                $toString,
1405
                Finder::create()
1406
                    ->in($vendorDir)
1407
                    ->directories()
1408
                    ->depth(1)
1409
                    ->ignoreUnreadableDirs()
1410
                    ->filter(
1411
                        static function (SplFileInfo $fileInfo): ?bool {
1412
                            if ($fileInfo->isLink()) {
1413
                                return false;
1414
                            }
1415
1416
                            return null;
1417
                        }
1418
                    )
1419
            )));
1420
1421
            $vendorPackages = array_diff($vendorPackages, $devPackages);
1422
1423
            if (null === $decodedJsonContents || false === array_key_exists('autoload', $decodedJsonContents)) {
1424
                $files = toArray(values(map(
1425
                    $toString,
1426
                    Finder::create()
1427
                        ->in($basePath)
1428
                        ->files()
1429
                        ->depth(0)
1430
                )));
1431
1432
                $directories = toArray(values(map(
1433
                    $toString,
1434
                    Finder::create()
1435
                        ->in($basePath)
1436
                        ->notPath('vendor')
1437
                        ->directories()
1438
                        ->depth(0)
1439
                )));
1440
1441
                return [
1442
                    array_merge(
1443
                        array_diff($files, $excludedPaths),
1444
                        $filesToAppend
1445
                    ),
1446
                    array_merge(
1447
                        array_diff($directories, $excludedPaths),
1448
                        $vendorPackages
1449
                    ),
1450
                ];
1451
            }
1452
1453
            $paths = $vendorPackages;
1454
        } else {
1455
            $paths = [];
1456
        }
1457
1458
        $autoload = $decodedJsonContents['autoload'] ?? [];
1459
1460
        if (array_key_exists('psr-4', $autoload)) {
1461
            foreach ($autoload['psr-4'] as $path) {
1462
                /** @var string|string[] $path */
1463
                $composerPaths = (array) $path;
1464
1465
                foreach ($composerPaths as $composerPath) {
1466
                    $paths[] = '' !== trim($composerPath) ? $composerPath : $basePath;
1467
                }
1468
            }
1469
        }
1470
1471
        if (array_key_exists('psr-0', $autoload)) {
1472
            foreach ($autoload['psr-0'] as $path) {
1473
                /** @var string|string[] $path */
1474
                $composerPaths = (array) $path;
1475
1476
                foreach ($composerPaths as $composerPath) {
1477
                    $paths[] = '' !== trim($composerPath) ? $composerPath : $basePath;
1478
                }
1479
            }
1480
        }
1481
1482
        if (array_key_exists('classmap', $autoload)) {
1483
            foreach ($autoload['classmap'] as $path) {
1484
                // @var string $path
1485
                $paths[] = $path;
1486
            }
1487
        }
1488
1489
        $normalizePath = static function (string $path) use ($basePath): string {
1490
            return is_absolute_path($path)
1491
                ? canonicalize($path)
1492
                : self::normalizePath(trim($path, '/ '), $basePath)
1493
            ;
1494
        };
1495
1496
        if (array_key_exists('files', $autoload)) {
1497
            foreach ($autoload['files'] as $path) {
1498
                // @var string $path
1499
                $path = $normalizePath($path);
1500
1501
                Assertion::file($path);
1502
                Assertion::false(is_link($path), 'Cannot add the link "'.$path.'": links are not supported.');
1503
1504
                $filesToAppend[] = $path;
1505
            }
1506
        }
1507
1508
        $files = $filesToAppend;
1509
        $directories = [];
1510
1511
        foreach ($paths as $path) {
1512
            $path = $normalizePath($path);
1513
1514
            Assertion::true(file_exists($path), 'File or directory "'.$path.'" was expected to exist.');
1515
            Assertion::false(is_link($path), 'Cannot add the link "'.$path.'": links are not supported.');
1516
1517
            if (is_file($path)) {
1518
                $files[] = $path;
1519
            } else {
1520
                $directories[] = $path;
1521
            }
1522
        }
1523
1524
        [$files, $directories] = [
1525
            array_unique($files),
1526
            array_unique($directories),
1527
        ];
1528
1529
        return [
1530
            array_diff($files, $excludedPaths),
1531
            array_diff($directories, $excludedPaths),
1532
        ];
1533
    }
1534
1535
    /**
1536
     * @param string[] $files
1537
     * @param string[] $directories
1538
     * @param string[] $excludedPaths
1539
     * @param string[] $devPackages
1540
     *
1541
     * @return SplFileInfo[]
1542
     */
1543
    private static function retrieveAllFiles(
1544
        string $basePath,
1545
        array $directories,
1546
        ?string $mainScriptPath,
1547
        Closure $blacklistFilter,
1548
        array $excludedPaths,
1549
        array $devPackages
1550
    ): iterable {
1551
        if ([] === $directories) {
1552
            return [];
1553
        }
1554
1555
        $relativeDevPackages = array_map(
1556
            static function (string $packagePath) use ($basePath): string {
1557
                return make_path_relative($packagePath, $basePath);
1558
            },
1559
            $devPackages
1560
        );
1561
1562
        $finder = Finder::create()
1563
            ->files()
1564
            ->filter($blacklistFilter)
1565
            ->exclude($relativeDevPackages)
1566
            ->ignoreVCS(true)
1567
            ->ignoreDotFiles(true)
1568
            // Remove build files
1569
            ->notName('composer.json')
1570
            ->notName('composer.lock')
1571
            ->notName('Makefile')
1572
            ->notName('Vagrantfile')
1573
            ->notName('phpstan*.neon*')
1574
            ->notName('infection*.json*')
1575
            ->notName('humbug*.json*')
1576
            ->notName('easy-coding-standard.neon*')
1577
            ->notName('phpbench.json*')
1578
            ->notName('phpcs.xml*')
1579
            ->notName('psalm.xml*')
1580
            ->notName('scoper.inc*')
1581
            ->notName('box*.json*')
1582
            ->notName('phpdoc*.xml*')
1583
            ->notName('codecov.yml*')
1584
            ->notName('Dockerfile')
1585
            ->exclude('build')
1586
            ->exclude('dist')
1587
            ->exclude('example')
1588
            ->exclude('examples')
1589
            // Remove documentation
1590
            ->notName('*.md')
1591
            ->notName('*.rst')
1592
            ->notName('/^readme((?!\.php)(\..*+))?$/i')
1593
            ->notName('/^upgrade((?!\.php)(\..*+))?$/i')
1594
            ->notName('/^contributing((?!\.php)(\..*+))?$/i')
1595
            ->notName('/^changelog((?!\.php)(\..*+))?$/i')
1596
            ->notName('/^authors?((?!\.php)(\..*+))?$/i')
1597
            ->notName('/^conduct((?!\.php)(\..*+))?$/i')
1598
            ->notName('/^todo((?!\.php)(\..*+))?$/i')
1599
            ->exclude('doc')
1600
            ->exclude('docs')
1601
            ->exclude('documentation')
1602
            // Remove backup files
1603
            ->notName('*~')
1604
            ->notName('*.back')
1605
            ->notName('*.swp')
1606
            // Remove tests
1607
            ->notName('*Test.php')
1608
            ->exclude('test')
1609
            ->exclude('Test')
1610
            ->exclude('tests')
1611
            ->exclude('Tests')
1612
            ->notName('/phpunit.*\.xml(.dist)?/')
1613
            ->notName('/behat.*\.yml(.dist)?/')
1614
            ->exclude('spec')
1615
            ->exclude('specs')
1616
            ->exclude('features')
1617
            // Remove CI config
1618
            ->exclude('travis')
1619
            ->notName('travis.yml')
1620
            ->notName('appveyor.yml')
1621
            ->notName('build.xml*')
1622
        ;
1623
1624
        if (null !== $mainScriptPath) {
1625
            $finder->notPath(make_path_relative($mainScriptPath, $basePath));
1626
        }
1627
1628
        $finder->in($directories);
1629
1630
        $excludedPaths = array_unique(
1631
            array_filter(
1632
                array_map(
1633
                    static function (string $path) use ($basePath): string {
1634
                        return make_path_relative($path, $basePath);
1635
                    },
1636
                    $excludedPaths
1637
                ),
1638
                static function (string $path): bool {
1639
                    return '..' !== substr($path, 0, 2);
1640
                }
1641
            )
1642
        );
1643
1644
        foreach ($excludedPaths as $excludedPath) {
1645
            $finder->notPath($excludedPath);
1646
        }
1647
1648
        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...
1649
    }
1650
1651
    /**
1652
     * @param string $key Config property name
1653
     *
1654
     * @return string[]
1655
     */
1656
    private static function retrieveDirectoryPaths(
1657
        stdClass $raw,
1658
        string $key,
1659
        string $basePath,
1660
        ConfigurationLogger $logger
1661
    ): array {
1662
        self::checkIfDefaultValue($logger, $raw, $key, []);
1663
1664
        if (false === isset($raw->{$key})) {
1665
            return [];
1666
        }
1667
1668
        $directories = $raw->{$key};
1669
1670
        $normalizeDirectory = static function (string $directory) use ($basePath, $key): string {
1671
            $directory = self::normalizePath($directory, $basePath);
1672
1673
            Assertion::false(
1674
                is_link($directory),
1675
                sprintf(
1676
                    'Cannot add the link "%s": links are not supported.',
1677
                    $directory
1678
                )
1679
            );
1680
1681
            Assertion::directory(
1682
                $directory,
1683
                sprintf(
1684
                    '"%s" must contain a list of existing directories. Could not find "%%s".',
1685
                    $key
1686
                )
1687
            );
1688
1689
            return $directory;
1690
        };
1691
1692
        return array_map($normalizeDirectory, $directories);
1693
    }
1694
1695
    private static function normalizePath(string $file, string $basePath): string
1696
    {
1697
        return make_path_absolute(trim($file), $basePath);
1698
    }
1699
1700
    /**
1701
     * @param string[] $files
1702
     *
1703
     * @return SplFileInfo[]
1704
     */
1705
    private static function wrapInSplFileInfo(array $files): array
1706
    {
1707
        return array_map(
1708
            static function (string $file): SplFileInfo {
1709
                return new SplFileInfo($file);
1710
            },
1711
            $files
1712
        );
1713
    }
1714
1715
    private static function retrieveDumpAutoload(stdClass $raw, ComposerFiles $composerFiles, ConfigurationLogger $logger): bool
1716
    {
1717
        self::checkIfDefaultValue($logger, $raw, self::DUMP_AUTOLOAD_KEY, null);
1718
1719
        $canDumpAutoload = (
1720
            null !== $composerFiles->getComposerJson()->getPath()
1721
            && (
1722
                // The composer.lock and installed.json are optional (e.g. if there is no dependencies installed)
1723
                // but when one is present, the other must be as well otherwise the dumped autoloader will be broken
1724
                (
1725
                    null === $composerFiles->getComposerLock()->getPath()
1726
                    && null === $composerFiles->getInstalledJson()->getPath()
1727
                )
1728
                || (
1729
                    null !== $composerFiles->getComposerLock()->getPath()
1730
                    && null !== $composerFiles->getInstalledJson()->getPath()
1731
                )
1732
                || (
1733
                    null === $composerFiles->getComposerLock()->getPath()
1734
                    && null !== $composerFiles->getInstalledJson()->getPath()
1735
                    && [] === $composerFiles->getInstalledJson()->getDecodedContents()
1736
                )
1737
            )
1738
        );
1739
1740
        if ($canDumpAutoload) {
1741
            self::checkIfDefaultValue($logger, $raw, self::DUMP_AUTOLOAD_KEY, true);
1742
        }
1743
1744
        if (false === property_exists($raw, self::DUMP_AUTOLOAD_KEY)) {
1745
            return $canDumpAutoload;
1746
        }
1747
1748
        $dumpAutoload = $raw->{self::DUMP_AUTOLOAD_KEY} ?? true;
1749
1750
        if (false === $canDumpAutoload && $dumpAutoload) {
1751
            $logger->addWarning(
1752
                sprintf(
1753
                    'The "%s" setting has been set but has been ignored because the composer.json, composer.lock'
1754
                    .' and vendor/composer/installed.json files are necessary but could not be found.',
1755
                    self::DUMP_AUTOLOAD_KEY
1756
                )
1757
            );
1758
1759
            return false;
1760
        }
1761
1762
        return $canDumpAutoload && false !== $dumpAutoload;
1763
    }
1764
1765
    private static function retrieveExcludeDevFiles(stdClass $raw, bool $dumpAutoload, ConfigurationLogger $logger): bool
1766
    {
1767
        self::checkIfDefaultValue($logger, $raw, self::EXCLUDE_DEV_FILES_KEY, $dumpAutoload);
1768
1769
        if (false === property_exists($raw, self::EXCLUDE_DEV_FILES_KEY)) {
1770
            return $dumpAutoload;
1771
        }
1772
1773
        $excludeDevFiles = $raw->{self::EXCLUDE_DEV_FILES_KEY} ?? $dumpAutoload;
1774
1775
        if (true === $excludeDevFiles && false === $dumpAutoload) {
1776
            $logger->addWarning(sprintf(
1777
                'The "%s" setting has been set but has been ignored because the Composer autoloader is not dumped',
1778
                self::EXCLUDE_DEV_FILES_KEY
1779
            ));
1780
1781
            return false;
1782
        }
1783
1784
        return $excludeDevFiles;
1785
    }
1786
1787
    private static function retrieveExcludeComposerFiles(stdClass $raw, ConfigurationLogger $logger): bool
1788
    {
1789
        self::checkIfDefaultValue($logger, $raw, self::EXCLUDE_COMPOSER_FILES_KEY, true);
1790
1791
        return $raw->{self::EXCLUDE_COMPOSER_FILES_KEY} ?? true;
1792
    }
1793
1794
    /**
1795
     * @return Compactor[]
1796
     */
1797
    private static function retrieveCompactors(stdClass $raw, string $basePath, ConfigurationLogger $logger): array
1798
    {
1799
        self::checkIfDefaultValue($logger, $raw, self::COMPACTORS_KEY, []);
1800
1801
        $compactorClasses = array_unique((array) ($raw->{self::COMPACTORS_KEY} ?? []));
1802
1803
        $ignoredAnnotations = self::retrievePhpCompactorIgnoredAnnotations($raw, $compactorClasses, $logger);
1804
1805
        if (false === isset($raw->{self::COMPACTORS_KEY})) {
1806
            return [];
1807
        }
1808
1809
        $compactors = array_map(
1810
            static function (string $class) use ($raw, $basePath, $logger, $ignoredAnnotations): Compactor {
1811
                Assertion::classExists($class, 'The compactor class "%s" does not exist.');
1812
                Assertion::implementsInterface($class, Compactor::class, 'The class "%s" is not a compactor class.');
1813
1814
                if (LegacyPhp::class === $class) {
1815
                    $logger->addRecommendation(
1816
                        sprintf(
1817
                            'The compactor "%s" has been deprecated, use "%s" instead.',
1818
                            LegacyPhp::class,
1819
                            PhpCompactor::class
1820
                        )
1821
                    );
1822
                }
1823
1824
                if (LegacyJson::class === $class) {
1825
                    $logger->addRecommendation(
1826
                        sprintf(
1827
                            'The compactor "%s" has been deprecated, use "%s" instead.',
1828
                            LegacyJson::class,
1829
                            JsonCompactor::class
1830
                        )
1831
                    );
1832
                }
1833
1834
                if (PhpCompactor::class === $class || LegacyPhp::class === $class) {
1835
                    return self::createPhpCompactor($ignoredAnnotations);
1836
                }
1837
1838
                if (PhpScoperCompactor::class === $class) {
1839
                    return self::createPhpScoperCompactor($raw, $basePath, $logger);
1840
                }
1841
1842
                return new $class();
1843
            },
1844
            $compactorClasses
1845
        );
1846
1847
        $scoperCompactor = false;
1848
1849
        foreach ($compactors as $compactor) {
1850
            if ($compactor instanceof PhpScoperCompactor) {
1851
                $scoperCompactor = true;
1852
            }
1853
1854
            if ($compactor instanceof PhpCompactor) {
1855
                if (true === $scoperCompactor) {
1856
                    $logger->addRecommendation(
1857
                        sprintf(
1858
                            'The PHP compactor has been registered after the PhpScoper compactor. It is '
1859
                            .'recommended to register the PHP compactor before for a clearer code and faster processing.'
1860
                        )
1861
                    );
1862
                }
1863
1864
                break;
1865
            }
1866
        }
1867
1868
        return $compactors;
1869
    }
1870
1871
    private static function retrieveCompressionAlgorithm(stdClass $raw, ConfigurationLogger $logger): ?int
1872
    {
1873
        self::checkIfDefaultValue($logger, $raw, self::COMPRESSION_KEY, 'NONE');
1874
1875
        if (false === isset($raw->{self::COMPRESSION_KEY})) {
1876
            return null;
1877
        }
1878
1879
        $knownAlgorithmNames = array_keys(get_phar_compression_algorithms());
1880
1881
        Assertion::inArray(
1882
            $raw->{self::COMPRESSION_KEY},
1883
            $knownAlgorithmNames,
1884
            sprintf(
1885
                'Invalid compression algorithm "%%s", use one of "%s" instead.',
1886
                implode('", "', $knownAlgorithmNames)
1887
            )
1888
        );
1889
1890
        $value = get_phar_compression_algorithms()[$raw->{self::COMPRESSION_KEY}];
1891
1892
        // Phar::NONE is not valid for compressFiles()
1893
        if (Phar::NONE === $value) {
1894
            return null;
1895
        }
1896
1897
        return $value;
1898
    }
1899
1900
    private static function retrieveFileMode(stdClass $raw, ConfigurationLogger $logger): ?int
1901
    {
1902
        if (property_exists($raw, self::CHMOD_KEY) && null === $raw->{self::CHMOD_KEY}) {
1903
            self::addRecommendationForDefaultValue($logger, self::CHMOD_KEY);
1904
        }
1905
1906
        $defaultChmod = intval(0755, 8);
1907
1908
        if (isset($raw->{self::CHMOD_KEY})) {
1909
            $chmod = intval($raw->{self::CHMOD_KEY}, 8);
1910
1911
            if ($defaultChmod === $chmod) {
1912
                self::addRecommendationForDefaultValue($logger, self::CHMOD_KEY);
1913
            }
1914
1915
            return $chmod;
1916
        }
1917
1918
        return $defaultChmod;
1919
    }
1920
1921
    private static function retrieveMainScriptPath(
1922
        stdClass $raw,
1923
        string $basePath,
1924
        ?array $decodedJsonContents,
1925
        ConfigurationLogger $logger
1926
    ): ?string {
1927
        $firstBin = false;
1928
1929
        if (null !== $decodedJsonContents && array_key_exists('bin', $decodedJsonContents)) {
1930
            /** @var false|string $firstBin */
1931
            $firstBin = current((array) $decodedJsonContents['bin']);
1932
1933
            if (false !== $firstBin) {
1934
                $firstBin = self::normalizePath($firstBin, $basePath);
1935
            }
1936
        }
1937
1938
        if (isset($raw->{self::MAIN_KEY})) {
1939
            $main = $raw->{self::MAIN_KEY};
1940
1941
            if (is_string($main)) {
1942
                $main = self::normalizePath($main, $basePath);
1943
1944
                if ($main === $firstBin) {
1945
                    $logger->addRecommendation(
1946
                        sprintf(
1947
                            'The "%s" setting can be omitted since is set to its default value',
1948
                            self::MAIN_KEY
1949
                        )
1950
                    );
1951
                }
1952
            }
1953
        } else {
1954
            $main = false !== $firstBin ? $firstBin : self::normalizePath(self::DEFAULT_MAIN_SCRIPT, $basePath);
1955
        }
1956
1957
        if (is_bool($main)) {
1958
            Assertion::false(
1959
                $main,
1960
                'Cannot "enable" a main script: either disable it with `false` or give the main script file path.'
1961
            );
1962
1963
            return null;
1964
        }
1965
1966
        Assertion::file($main);
1967
1968
        return $main;
1969
    }
1970
1971
    private static function retrieveMainScriptContents(?string $mainScriptPath): ?string
1972
    {
1973
        if (null === $mainScriptPath) {
1974
            return null;
1975
        }
1976
1977
        $contents = file_contents($mainScriptPath);
1978
1979
        // Remove the shebang line: the shebang line in a PHAR should be located in the stub file which is the real
1980
        // PHAR entry point file.
1981
        // If one needs the shebang, then the main file should act as the stub and be registered as such and in which
1982
        // case the main script can be ignored or disabled.
1983
        return preg_replace('/^#!.*\s*/', '', $contents);
1984
    }
1985
1986
    private static function retrieveComposerFiles(string $basePath): ComposerFiles
1987
    {
1988
        $retrieveFileAndContents = static function (string $file): ?ComposerFile {
1989
            $json = new Json();
1990
1991
            if (false === file_exists($file) || false === is_file($file) || false === is_readable($file)) {
1992
                return ComposerFile::createEmpty();
1993
            }
1994
1995
            try {
1996
                $contents = (array) $json->decodeFile($file, true);
1997
            } catch (ParsingException $exception) {
1998
                throw new InvalidArgumentException(
1999
                    sprintf(
2000
                        'Expected the file "%s" to be a valid composer.json file but an error has been found: %s',
2001
                        $file,
2002
                        $exception->getMessage()
2003
                    ),
2004
                    0,
2005
                    $exception
2006
                );
2007
            }
2008
2009
            return new ComposerFile($file, $contents);
2010
        };
2011
2012
        return new ComposerFiles(
2013
            $retrieveFileAndContents(canonicalize($basePath.'/composer.json')),
2014
            $retrieveFileAndContents(canonicalize($basePath.'/composer.lock')),
2015
            $retrieveFileAndContents(canonicalize($basePath.'/vendor/composer/installed.json'))
2016
        );
2017
    }
2018
2019
    /**
2020
     * @return string[][]
2021
     */
2022
    private static function retrieveMap(stdClass $raw, ConfigurationLogger $logger): array
2023
    {
2024
        self::checkIfDefaultValue($logger, $raw, self::MAP_KEY, []);
2025
2026
        if (false === isset($raw->{self::MAP_KEY})) {
2027
            return [];
2028
        }
2029
2030
        $map = [];
2031
        $rawMap = (array) $raw->{self::MAP_KEY};
2032
2033
        foreach ($rawMap as $item) {
2034
            $processed = [];
2035
2036
            foreach ($item as $match => $replace) {
2037
                $processed[canonicalize(trim($match))] = canonicalize(trim($replace));
2038
            }
2039
2040
            if (isset($processed['_empty_'])) {
2041
                $processed[''] = $processed['_empty_'];
2042
2043
                unset($processed['_empty_']);
2044
            }
2045
2046
            $map[] = $processed;
2047
        }
2048
2049
        return $map;
2050
    }
2051
2052
    /**
2053
     * @return mixed
2054
     */
2055
    private static function retrieveMetadata(stdClass $raw, ConfigurationLogger $logger)
2056
    {
2057
        self::checkIfDefaultValue($logger, $raw, self::METADATA_KEY);
2058
2059
        if (false === isset($raw->{self::METADATA_KEY})) {
2060
            return null;
2061
        }
2062
2063
        $metadata = $raw->{self::METADATA_KEY};
2064
2065
        return is_object($metadata) ? (array) $metadata : $metadata;
2066
    }
2067
2068
    /**
2069
     * @return string[] The first element is the temporary output path and the second the final one
2070
     */
2071
    private static function retrieveOutputPath(
2072
        stdClass $raw,
2073
        string $basePath,
2074
        ?string $mainScriptPath,
2075
        ConfigurationLogger $logger
2076
    ): array {
2077
        $defaultPath = null;
2078
2079
        if (null !== $mainScriptPath
2080
            && 1 === preg_match('/^(?<main>.*?)(?:\.[\p{L}\d]+)?$/u', $mainScriptPath, $matches)
2081
        ) {
2082
            $defaultPath = $matches['main'].'.phar';
2083
        }
2084
2085
        if (isset($raw->{self::OUTPUT_KEY})) {
2086
            $path = self::normalizePath($raw->{self::OUTPUT_KEY}, $basePath);
2087
2088
            if ($path === $defaultPath) {
2089
                self::addRecommendationForDefaultValue($logger, self::OUTPUT_KEY);
2090
            }
2091
        } elseif (null !== $defaultPath) {
2092
            $path = $defaultPath;
2093
        } else {
2094
            // Last resort, should not happen
2095
            $path = self::normalizePath(self::DEFAULT_OUTPUT_FALLBACK, $basePath);
2096
        }
2097
2098
        $tmp = $real = $path;
2099
2100
        if ('.phar' !== substr($real, -5)) {
2101
            $tmp .= '.phar';
2102
        }
2103
2104
        return [$tmp, $real];
2105
    }
2106
2107
    private static function retrievePrivateKeyPath(
2108
        stdClass $raw,
2109
        string $basePath,
2110
        int $signingAlgorithm,
2111
        ConfigurationLogger $logger
2112
    ): ?string {
2113
        if (property_exists($raw, self::KEY_KEY) && Phar::OPENSSL !== $signingAlgorithm) {
2114
            if (null === $raw->{self::KEY_KEY}) {
2115
                $logger->addRecommendation(
2116
                    'The setting "key" has been set but is unnecessary since the signing algorithm is not "OPENSSL".'
2117
                );
2118
            } else {
2119
                $logger->addWarning(
2120
                    'The setting "key" has been set but is ignored since the signing algorithm is not "OPENSSL".'
2121
                );
2122
            }
2123
2124
            return null;
2125
        }
2126
2127
        if (!isset($raw->{self::KEY_KEY})) {
2128
            Assertion::true(
2129
                Phar::OPENSSL !== $signingAlgorithm,
2130
                'Expected to have a private key for OpenSSL signing but none have been provided.'
2131
            );
2132
2133
            return null;
2134
        }
2135
2136
        $path = self::normalizePath($raw->{self::KEY_KEY}, $basePath);
2137
2138
        Assertion::file($path);
2139
2140
        return $path;
2141
    }
2142
2143
    private static function retrievePrivateKeyPassphrase(
2144
        stdClass $raw,
2145
        int $algorithm,
2146
        ConfigurationLogger $logger
2147
    ): ?string {
2148
        self::checkIfDefaultValue($logger, $raw, self::KEY_PASS_KEY);
2149
2150
        if (false === property_exists($raw, self::KEY_PASS_KEY)) {
2151
            return null;
2152
        }
2153
2154
        /** @var null|false|string $keyPass */
2155
        $keyPass = $raw->{self::KEY_PASS_KEY};
2156
2157
        if (Phar::OPENSSL !== $algorithm) {
2158
            if (false === $keyPass || null === $keyPass) {
2159
                $logger->addRecommendation(
2160
                    sprintf(
2161
                        'The setting "%s" has been set but is unnecessary since the signing algorithm is '
2162
                        .'not "OPENSSL".',
2163
                        self::KEY_PASS_KEY
2164
                    )
2165
                );
2166
            } else {
2167
                $logger->addWarning(
2168
                    sprintf(
2169
                        'The setting "%s" has been set but ignored the signing algorithm is not "OPENSSL".',
2170
                        self::KEY_PASS_KEY
2171
                    )
2172
                );
2173
            }
2174
2175
            return null;
2176
        }
2177
2178
        return is_string($keyPass) ? $keyPass : null;
2179
    }
2180
2181
    /**
2182
     * @return scalar[]
2183
     */
2184
    private static function retrieveReplacements(stdClass $raw, ?string $file, ConfigurationLogger $logger): array
2185
    {
2186
        self::checkIfDefaultValue($logger, $raw, self::REPLACEMENTS_KEY, new stdClass());
2187
2188
        if (null === $file) {
2189
            return [];
2190
        }
2191
2192
        $replacements = isset($raw->{self::REPLACEMENTS_KEY}) ? (array) $raw->{self::REPLACEMENTS_KEY} : [];
2193
2194
        if (null !== ($git = self::retrievePrettyGitPlaceholder($raw, $logger))) {
2195
            $replacements[$git] = self::retrievePrettyGitTag($file);
2196
        }
2197
2198
        if (null !== ($git = self::retrieveGitHashPlaceholder($raw, $logger))) {
2199
            $replacements[$git] = self::retrieveGitHash($file);
2200
        }
2201
2202
        if (null !== ($git = self::retrieveGitShortHashPlaceholder($raw, $logger))) {
2203
            $replacements[$git] = self::retrieveGitHash($file, true);
2204
        }
2205
2206
        if (null !== ($git = self::retrieveGitTagPlaceholder($raw, $logger))) {
2207
            $replacements[$git] = self::retrieveGitTag($file);
2208
        }
2209
2210
        if (null !== ($git = self::retrieveGitVersionPlaceholder($raw, $logger))) {
2211
            $replacements[$git] = self::retrieveGitVersion($file);
2212
        }
2213
2214
        /**
2215
         * @var string
2216
         * @var bool   $valueSetByUser
2217
         */
2218
        [$datetimeFormat, $valueSetByUser] = self::retrieveDatetimeFormat($raw, $logger);
2219
2220
        if (null !== ($date = self::retrieveDatetimeNowPlaceHolder($raw, $logger))) {
2221
            $replacements[$date] = self::retrieveDatetimeNow($datetimeFormat);
2222
        } elseif ($valueSetByUser) {
2223
            $logger->addRecommendation(
2224
                sprintf(
2225
                    'The setting "%s" has been set but is unnecessary because the setting "%s" is not set.',
2226
                    self::DATETIME_FORMAT_KEY,
2227
                    self::DATETIME_KEY
2228
                )
2229
            );
2230
        }
2231
2232
        $sigil = self::retrieveReplacementSigil($raw, $logger);
2233
2234
        foreach ($replacements as $key => $value) {
2235
            unset($replacements[$key]);
2236
            $replacements[$sigil.$key.$sigil] = $value;
2237
        }
2238
2239
        return $replacements;
2240
    }
2241
2242
    private static function retrievePrettyGitPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
2243
    {
2244
        return self::retrievePlaceholder($raw, $logger, self::GIT_KEY);
2245
    }
2246
2247
    private static function retrieveGitHashPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
2248
    {
2249
        return self::retrievePlaceholder($raw, $logger, self::GIT_COMMIT_KEY);
2250
    }
2251
2252
    /**
2253
     * @param bool $short Use the short version
2254
     *
2255
     * @return string the commit hash
2256
     */
2257
    private static function retrieveGitHash(string $file, bool $short = false): string
2258
    {
2259
        return self::runGitCommand(
2260
            sprintf(
2261
                'git log --pretty="%s" -n1 HEAD',
2262
                $short ? '%h' : '%H'
2263
            ),
2264
            $file
2265
        );
2266
    }
2267
2268
    private static function retrieveGitShortHashPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
2269
    {
2270
        return self::retrievePlaceholder($raw, $logger, self::GIT_COMMIT_SHORT_KEY);
2271
    }
2272
2273
    private static function retrieveGitTagPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
2274
    {
2275
        return self::retrievePlaceholder($raw, $logger, self::GIT_TAG_KEY);
2276
    }
2277
2278
    private static function retrievePlaceholder(stdClass $raw, ConfigurationLogger $logger, string $key): ?string
2279
    {
2280
        self::checkIfDefaultValue($logger, $raw, $key);
2281
2282
        return $raw->{$key} ?? null;
2283
    }
2284
2285
    private static function retrieveGitTag(string $file): string
2286
    {
2287
        return self::runGitCommand('git describe --tags HEAD', $file);
2288
    }
2289
2290
    private static function retrievePrettyGitTag(string $file): string
2291
    {
2292
        $version = self::retrieveGitTag($file);
2293
2294
        if (preg_match('/^(?<tag>.+)-\d+-g(?<hash>[a-f0-9]{7})$/', $version, $matches)) {
2295
            return sprintf('%s@%s', $matches['tag'], $matches['hash']);
2296
        }
2297
2298
        return $version;
2299
    }
2300
2301
    private static function retrieveGitVersionPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
2302
    {
2303
        return self::retrievePlaceholder($raw, $logger, self::GIT_VERSION_KEY);
2304
    }
2305
2306
    private static function retrieveGitVersion(string $file): ?string
2307
    {
2308
        try {
2309
            return self::retrieveGitTag($file);
2310
        } catch (RuntimeException $exception) {
2311
            try {
2312
                return self::retrieveGitHash($file, true);
2313
            } catch (RuntimeException $exception) {
2314
                throw new RuntimeException(
2315
                    sprintf(
2316
                        'The tag or commit hash could not be retrieved from "%s": %s',
2317
                        dirname($file),
2318
                        $exception->getMessage()
2319
                    ),
2320
                    0,
2321
                    $exception
2322
                );
2323
            }
2324
        }
2325
    }
2326
2327
    private static function retrieveDatetimeNowPlaceHolder(stdClass $raw, ConfigurationLogger $logger): ?string
2328
    {
2329
        return self::retrievePlaceholder($raw, $logger, self::DATETIME_KEY);
2330
    }
2331
2332
    private static function retrieveDatetimeNow(string $format): string
2333
    {
2334
        $now = new DateTimeImmutable('now', new DateTimeZone('UTC'));
2335
2336
        return $now->format($format);
2337
    }
2338
2339
    private static function retrieveDatetimeFormat(stdClass $raw, ConfigurationLogger $logger): array
2340
    {
2341
        self::checkIfDefaultValue($logger, $raw, self::DATETIME_FORMAT_KEY, self::DEFAULT_DATETIME_FORMAT);
2342
        self::checkIfDefaultValue($logger, $raw, self::DATETIME_FORMAT_KEY, self::DATETIME_FORMAT_DEPRECATED_KEY);
2343
2344
        if (isset($raw->{self::DATETIME_FORMAT_KEY})) {
2345
            $format = $raw->{self::DATETIME_FORMAT_KEY};
2346
        } elseif (isset($raw->{self::DATETIME_FORMAT_DEPRECATED_KEY})) {
2347
            @trigger_error(
2348
                sprintf(
2349
                    'The "%s" is deprecated, use "%s" setting instead.',
2350
                    self::DATETIME_FORMAT_DEPRECATED_KEY,
2351
                    self::DATETIME_FORMAT_KEY
2352
                ),
2353
                E_USER_DEPRECATED
2354
            );
2355
            $logger->addWarning(
2356
                sprintf(
2357
                    'The "%s" is deprecated, use "%s" setting instead.',
2358
                    self::DATETIME_FORMAT_DEPRECATED_KEY,
2359
                    self::DATETIME_FORMAT_KEY
2360
                )
2361
            );
2362
2363
            $format = $raw->{self::DATETIME_FORMAT_DEPRECATED_KEY};
2364
        } else {
2365
            $format = null;
2366
        }
2367
2368
        if (null !== $format) {
2369
            $formattedDate = (new DateTimeImmutable())->format($format);
2370
2371
            Assertion::false(
2372
                false === $formattedDate || $formattedDate === $format,
2373
                sprintf(
2374
                    'Expected the datetime format to be a valid format: "%s" is not',
2375
                    $format
2376
                )
2377
            );
2378
2379
            return [$format, true];
2380
        }
2381
2382
        return [self::DEFAULT_DATETIME_FORMAT, false];
2383
    }
2384
2385
    private static function retrieveReplacementSigil(stdClass $raw, ConfigurationLogger $logger): string
2386
    {
2387
        return self::retrievePlaceholder($raw, $logger, self::REPLACEMENT_SIGIL_KEY) ?? self::DEFAULT_REPLACEMENT_SIGIL;
2388
    }
2389
2390
    private static function retrieveShebang(stdClass $raw, bool $stubIsGenerated, ConfigurationLogger $logger): ?string
2391
    {
2392
        self::checkIfDefaultValue($logger, $raw, self::SHEBANG_KEY, self::DEFAULT_SHEBANG);
2393
2394
        if (false === isset($raw->{self::SHEBANG_KEY})) {
2395
            return self::DEFAULT_SHEBANG;
2396
        }
2397
2398
        $shebang = $raw->{self::SHEBANG_KEY};
2399
2400
        if (false === $shebang) {
2401
            if (false === $stubIsGenerated) {
2402
                $logger->addRecommendation(
2403
                    sprintf(
2404
                        'The "%s" has been set to `false` but is unnecessary since the Box built-in stub is not'
2405
                        .' being used',
2406
                        self::SHEBANG_KEY
2407
                    )
2408
                );
2409
            }
2410
2411
            return null;
2412
        }
2413
2414
        Assertion::string($shebang, 'Expected shebang to be either a string, false or null, found true');
2415
2416
        $shebang = trim($shebang);
2417
2418
        Assertion::notEmpty($shebang, 'The shebang should not be empty.');
2419
        Assertion::true(
2420
            '#!' === substr($shebang, 0, 2),
2421
            sprintf(
2422
                'The shebang line must start with "#!". Got "%s" instead',
2423
                $shebang
2424
            )
2425
        );
2426
2427
        if (false === $stubIsGenerated) {
2428
            $logger->addWarning(
2429
                sprintf(
2430
                    'The "%s" has been set but ignored since it is used only with the Box built-in stub which is not'
2431
                    .' used',
2432
                    self::SHEBANG_KEY
2433
                )
2434
            );
2435
        }
2436
2437
        return $shebang;
2438
    }
2439
2440
    private static function retrieveSigningAlgorithm(stdClass $raw, ConfigurationLogger $logger): int
2441
    {
2442
        if (property_exists($raw, self::ALGORITHM_KEY) && null === $raw->{self::ALGORITHM_KEY}) {
2443
            self::addRecommendationForDefaultValue($logger, self::ALGORITHM_KEY);
2444
        }
2445
2446
        if (false === isset($raw->{self::ALGORITHM_KEY})) {
2447
            return self::DEFAULT_SIGNING_ALGORITHM;
2448
        }
2449
2450
        $algorithm = strtoupper($raw->{self::ALGORITHM_KEY});
2451
2452
        Assertion::inArray($algorithm, array_keys(get_phar_signing_algorithms()));
2453
2454
        Assertion::true(
2455
            defined('Phar::'.$algorithm),
2456
            sprintf(
2457
                'The signing algorithm "%s" is not supported by your current PHAR version.',
2458
                $algorithm
2459
            )
2460
        );
2461
2462
        $algorithm = constant('Phar::'.$algorithm);
2463
2464
        if (self::DEFAULT_SIGNING_ALGORITHM === $algorithm) {
2465
            self::addRecommendationForDefaultValue($logger, self::ALGORITHM_KEY);
2466
        }
2467
2468
        return $algorithm;
2469
    }
2470
2471
    private static function retrieveStubBannerContents(stdClass $raw, bool $stubIsGenerated, ConfigurationLogger $logger): ?string
2472
    {
2473
        self::checkIfDefaultValue($logger, $raw, self::BANNER_KEY, self::getDefaultBanner());
2474
2475
        if (false === isset($raw->{self::BANNER_KEY})) {
2476
            return self::getDefaultBanner();
2477
        }
2478
2479
        $banner = $raw->{self::BANNER_KEY};
2480
2481
        if (false === $banner) {
2482
            if (false === $stubIsGenerated) {
2483
                $logger->addRecommendation(
2484
                    sprintf(
2485
                        'The "%s" setting has been set but is unnecessary since the Box built-in stub is not '
2486
                        .'being used',
2487
                        self::BANNER_KEY
2488
                    )
2489
                );
2490
            }
2491
2492
            return null;
2493
        }
2494
2495
        Assertion::true(is_string($banner) || is_array($banner), 'The banner cannot accept true as a value');
2496
2497
        if (is_array($banner)) {
2498
            $banner = implode("\n", $banner);
2499
        }
2500
2501
        if (false === $stubIsGenerated) {
2502
            $logger->addWarning(
2503
                sprintf(
2504
                    'The "%s" setting has been set but is ignored since the Box built-in stub is not being used',
2505
                    self::BANNER_KEY
2506
                )
2507
            );
2508
        }
2509
2510
        return $banner;
2511
    }
2512
2513
    private static function getDefaultBanner(): string
2514
    {
2515
        return sprintf(self::DEFAULT_BANNER, get_box_version());
2516
    }
2517
2518
    private static function retrieveStubBannerPath(
2519
        stdClass $raw,
2520
        string $basePath,
2521
        bool $stubIsGenerated,
2522
        ConfigurationLogger $logger
2523
    ): ?string {
2524
        self::checkIfDefaultValue($logger, $raw, self::BANNER_FILE_KEY);
2525
2526
        if (false === isset($raw->{self::BANNER_FILE_KEY})) {
2527
            return null;
2528
        }
2529
2530
        $bannerFile = make_path_absolute($raw->{self::BANNER_FILE_KEY}, $basePath);
2531
2532
        Assertion::file($bannerFile);
2533
2534
        if (false === $stubIsGenerated) {
2535
            $logger->addWarning(
2536
                sprintf(
2537
                    'The "%s" setting has been set but is ignored since the Box built-in stub is not being used',
2538
                    self::BANNER_FILE_KEY
2539
                )
2540
            );
2541
        }
2542
2543
        return $bannerFile;
2544
    }
2545
2546
    private static function normalizeStubBannerContents(?string $contents): ?string
2547
    {
2548
        if (null === $contents) {
2549
            return null;
2550
        }
2551
2552
        $banner = explode("\n", $contents);
2553
        $banner = array_map('trim', $banner);
2554
2555
        return implode("\n", $banner);
2556
    }
2557
2558
    private static function retrieveStubPath(stdClass $raw, string $basePath, ConfigurationLogger $logger): ?string
2559
    {
2560
        self::checkIfDefaultValue($logger, $raw, self::STUB_KEY);
2561
2562
        if (isset($raw->{self::STUB_KEY}) && is_string($raw->{self::STUB_KEY})) {
2563
            $stubPath = make_path_absolute($raw->{self::STUB_KEY}, $basePath);
2564
2565
            Assertion::file($stubPath);
2566
2567
            return $stubPath;
2568
        }
2569
2570
        return null;
2571
    }
2572
2573
    private static function retrieveInterceptsFileFunctions(
2574
        stdClass $raw,
2575
        bool $stubIsGenerated,
2576
        ConfigurationLogger $logger
2577
    ): bool {
2578
        self::checkIfDefaultValue($logger, $raw, self::INTERCEPT_KEY, false);
2579
2580
        if (false === isset($raw->{self::INTERCEPT_KEY})) {
2581
            return false;
2582
        }
2583
2584
        $intercept = $raw->{self::INTERCEPT_KEY};
2585
2586
        if ($intercept && false === $stubIsGenerated) {
2587
            $logger->addWarning(
2588
                sprintf(
2589
                    'The "%s" setting has been set but is ignored since the Box built-in stub is not being used',
2590
                    self::INTERCEPT_KEY
2591
                )
2592
            );
2593
        }
2594
2595
        return $intercept;
2596
    }
2597
2598
    private static function retrievePromptForPrivateKey(
2599
        stdClass $raw,
2600
        int $signingAlgorithm,
2601
        ConfigurationLogger $logger
2602
    ): bool {
2603
        if (isset($raw->{self::KEY_PASS_KEY}) && true === $raw->{self::KEY_PASS_KEY}) {
2604
            if (Phar::OPENSSL !== $signingAlgorithm) {
2605
                $logger->addWarning(
2606
                    'A prompt for password for the private key has been requested but ignored since the signing '
2607
                    .'algorithm used is not "OPENSSL.'
2608
                );
2609
2610
                return false;
2611
            }
2612
2613
            return true;
2614
        }
2615
2616
        return false;
2617
    }
2618
2619
    private static function retrieveIsStubGenerated(stdClass $raw, ?string $stubPath, ConfigurationLogger $logger): bool
2620
    {
2621
        self::checkIfDefaultValue($logger, $raw, self::STUB_KEY, true);
2622
2623
        return null === $stubPath && (false === isset($raw->{self::STUB_KEY}) || false !== $raw->{self::STUB_KEY});
2624
    }
2625
2626
    private static function retrieveCheckRequirements(
2627
        stdClass $raw,
2628
        bool $hasComposerJson,
2629
        bool $hasComposerLock,
2630
        bool $pharStubUsed,
2631
        ConfigurationLogger $logger
2632
    ): bool {
2633
        self::checkIfDefaultValue($logger, $raw, self::CHECK_REQUIREMENTS_KEY, true);
2634
2635
        if (false === property_exists($raw, self::CHECK_REQUIREMENTS_KEY)) {
2636
            return $hasComposerJson || $hasComposerLock;
2637
        }
2638
2639
        /** @var bool $checkRequirements */
2640
        $checkRequirements = $raw->{self::CHECK_REQUIREMENTS_KEY} ?? true;
2641
2642
        if ($checkRequirements && false === $hasComposerJson && false === $hasComposerLock) {
2643
            $logger->addWarning(
2644
                'The requirement checker could not be used because the composer.json and composer.lock file could not '
2645
                .'be found.'
2646
            );
2647
2648
            return false;
2649
        }
2650
2651
        if ($checkRequirements && $pharStubUsed) {
2652
            $logger->addWarning(
2653
                sprintf(
2654
                    'The "%s" setting has been set but has been ignored since the PHAR built-in stub is being '
2655
                    .'used.',
2656
                    self::CHECK_REQUIREMENTS_KEY
2657
                )
2658
            );
2659
        }
2660
2661
        return $checkRequirements;
2662
    }
2663
2664
    private static function retrievePhpScoperConfig(stdClass $raw, string $basePath, ConfigurationLogger $logger): PhpScoperConfiguration
2665
    {
2666
        self::checkIfDefaultValue($logger, $raw, self::PHP_SCOPER_KEY, self::PHP_SCOPER_CONFIG);
2667
2668
        if (!isset($raw->{self::PHP_SCOPER_KEY})) {
2669
            $configFilePath = make_path_absolute(self::PHP_SCOPER_CONFIG, $basePath);
2670
2671
            return file_exists($configFilePath)
2672
                ? PhpScoperConfiguration::load($configFilePath)
2673
                : PhpScoperConfiguration::load()
2674
             ;
2675
        }
2676
2677
        $configFile = $raw->{self::PHP_SCOPER_KEY};
2678
2679
        Assertion::string($configFile);
2680
2681
        $configFilePath = make_path_absolute($configFile, $basePath);
2682
2683
        Assertion::file($configFilePath);
2684
        Assertion::readable($configFilePath);
2685
2686
        return PhpScoperConfiguration::load($configFilePath);
2687
    }
2688
2689
    /**
2690
     * Runs a Git command on the repository.
2691
     *
2692
     * @return string The trimmed output from the command
2693
     */
2694
    private static function runGitCommand(string $command, string $file): string
2695
    {
2696
        $path = dirname($file);
2697
2698
        $process = Process::fromShellCommandline($command, $path);
2699
2700
        if (0 === $process->run()) {
2701
            return trim($process->getOutput());
2702
        }
2703
2704
        throw new RuntimeException(
2705
            sprintf(
2706
                'The tag or commit hash could not be retrieved from "%s": %s',
2707
                $path,
2708
                $process->getErrorOutput()
2709
            )
2710
        );
2711
    }
2712
2713
    /**
2714
     * @param string[] $compactorClasses
2715
     *
2716
     * @return string[]
2717
     */
2718
    private static function retrievePhpCompactorIgnoredAnnotations(
2719
        stdClass $raw,
2720
        array $compactorClasses,
2721
        ConfigurationLogger $logger
2722
    ): array {
2723
        $hasPhpCompactor = in_array(PhpCompactor::class, $compactorClasses, true) || in_array(LegacyPhp::class, $compactorClasses, true);
2724
2725
        self::checkIfDefaultValue($logger, $raw, self::ANNOTATIONS_KEY, true);
2726
        self::checkIfDefaultValue($logger, $raw, self::ANNOTATIONS_KEY, null);
2727
2728
        if (false === property_exists($raw, self::ANNOTATIONS_KEY)) {
2729
            return self::DEFAULT_IGNORED_ANNOTATIONS;
2730
        }
2731
2732
        if (false === $hasPhpCompactor) {
2733
            $logger->addWarning(
2734
                sprintf(
2735
                    'The "%s" setting has been set but is ignored since no PHP compactor has been configured',
2736
                    self::ANNOTATIONS_KEY
2737
                )
2738
            );
2739
        }
2740
2741
        /** @var null|bool|stdClass $annotations */
2742
        $annotations = $raw->{self::ANNOTATIONS_KEY};
2743
2744
        if (true === $annotations || null === $annotations) {
2745
            return self::DEFAULT_IGNORED_ANNOTATIONS;
2746
        }
2747
2748
        if (false === $annotations) {
0 ignored issues
show
introduced by
The condition false === $annotations is always true.
Loading history...
2749
            return [];
2750
        }
2751
2752
        if (false === property_exists($annotations, self::IGNORED_ANNOTATIONS_KEY)) {
2753
            $logger->addWarning(
2754
                sprintf(
2755
                    'The "%s" setting has been set but no "%s" setting has been found, hence "%s" is treated as'
2756
                    .' if it is set to `false`',
2757
                    self::ANNOTATIONS_KEY,
2758
                    self::IGNORED_ANNOTATIONS_KEY,
2759
                    self::ANNOTATIONS_KEY
2760
                )
2761
            );
2762
2763
            return [];
2764
        }
2765
2766
        $ignored = [];
2767
2768
        if (property_exists($annotations, self::IGNORED_ANNOTATIONS_KEY)
2769
            && in_array($ignored = $annotations->{self::IGNORED_ANNOTATIONS_KEY}, [null, []], true)
2770
        ) {
2771
            self::addRecommendationForDefaultValue($logger, self::ANNOTATIONS_KEY.'#'.self::IGNORED_ANNOTATIONS_KEY);
2772
2773
            return (array) $ignored;
2774
        }
2775
2776
        return $ignored;
2777
    }
2778
2779
    private static function createPhpCompactor(array $ignoredAnnotations): Compactor
2780
    {
2781
        $ignoredAnnotations = array_values(
2782
            array_filter(
2783
                array_map(
2784
                    static function (string $annotation): ?string {
2785
                        return strtolower(trim($annotation));
2786
                    },
2787
                    $ignoredAnnotations
2788
                )
2789
            )
2790
        );
2791
2792
        return new PhpCompactor(
2793
            new DocblockAnnotationParser(
2794
                new DocblockParser(),
2795
                new AnnotationDumper(),
2796
                $ignoredAnnotations
2797
            )
2798
        );
2799
    }
2800
2801
    private static function createPhpScoperCompactor(stdClass $raw, string $basePath, ConfigurationLogger $logger): Compactor
2802
    {
2803
        $phpScoperConfig = self::retrievePhpScoperConfig($raw, $basePath, $logger);
2804
2805
        $phpScoper = (new class() extends ApplicationFactory {
2806
            public static function createScoper(): Scoper
2807
            {
2808
                return parent::createScoper();
2809
            }
2810
        })::createScoper();
2811
2812
        if ([] !== $phpScoperConfig->getWhitelistedFiles()) {
2813
            $whitelistedFiles = array_values(
2814
                array_unique(
2815
                    array_map(
2816
                        static function (string $path) use ($basePath): string {
2817
                            return make_path_relative($path, $basePath);
2818
                        },
2819
                        $phpScoperConfig->getWhitelistedFiles()
2820
                    )
2821
                )
2822
            );
2823
2824
            $phpScoper = new FileWhitelistScoper($phpScoper, ...$whitelistedFiles);
2825
        }
2826
2827
        $prefix = $phpScoperConfig->getPrefix() ?? unique_id('_HumbugBox');
2828
2829
        return new PhpScoperCompactor(
2830
            new SimpleScoper(
2831
                $phpScoper,
2832
                $prefix,
2833
                $phpScoperConfig->getWhitelist(),
2834
                $phpScoperConfig->getPatchers()
2835
            )
2836
        );
2837
    }
2838
2839
    private static function checkIfDefaultValue(
2840
        ConfigurationLogger $logger,
2841
        stdClass $raw,
2842
        string $key,
2843
        $defaultValue = null
2844
    ): void {
2845
        if (false === property_exists($raw, $key)) {
2846
            return;
2847
        }
2848
2849
        $value = $raw->{$key};
2850
2851
        if (null === $value
2852
            || (false === is_object($defaultValue) && $defaultValue === $value)
2853
            || (is_object($defaultValue) && $defaultValue == $value)
2854
        ) {
2855
            $logger->addRecommendation(
2856
                sprintf(
2857
                    'The "%s" setting can be omitted since is set to its default value',
2858
                    $key
2859
                )
2860
            );
2861
        }
2862
    }
2863
2864
    private static function addRecommendationForDefaultValue(ConfigurationLogger $logger, string $key): void
2865
    {
2866
        $logger->addRecommendation(
2867
            sprintf(
2868
                'The "%s" setting can be omitted since is set to its default value',
2869
                $key
2870
            )
2871
        );
2872
    }
2873
}
2874