Passed
Pull Request — master (#285)
by Théo
02:18
created

Configuration::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 83
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 43
dl 0
loc 83
rs 9.232
c 0
b 0
f 0
cc 2
nc 2
nop 33

How to fix   Long Method    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the box project.
7
 *
8
 * (c) Kevin Herrera <[email protected]>
9
 *     Théo Fidry <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace KevinGH\Box;
16
17
use Assert\Assertion;
18
use Closure;
19
use DateTimeImmutable;
20
use DateTimeZone;
21
use Herrera\Annotations\Tokenizer;
22
use Herrera\Box\Compactor\Php as LegacyPhp;
23
use Humbug\PhpScoper\Configuration as PhpScoperConfiguration;
24
use Humbug\PhpScoper\Console\ApplicationFactory;
25
use Humbug\PhpScoper\Scoper;
26
use InvalidArgumentException;
27
use KevinGH\Box\Compactor\Php;
28
use KevinGH\Box\Compactor\PhpScoper as PhpScoperCompactor;
29
use KevinGH\Box\Composer\ComposerConfiguration;
30
use KevinGH\Box\Json\Json;
31
use KevinGH\Box\PhpScoper\SimpleScoper;
32
use Phar;
33
use RuntimeException;
34
use Seld\JsonLint\ParsingException;
35
use SplFileInfo;
36
use stdClass;
37
use Symfony\Component\Finder\Finder;
38
use Symfony\Component\Process\Process;
39
use const E_USER_DEPRECATED;
40
use function array_column;
41
use function array_diff;
42
use function array_filter;
43
use function array_key_exists;
44
use function array_keys;
45
use function array_map;
46
use function array_merge;
47
use function array_unique;
48
use function constant;
49
use function defined;
50
use function dirname;
51
use function file_exists;
52
use function in_array;
53
use function intval;
54
use function is_array;
55
use function is_bool;
56
use function is_file;
57
use function is_link;
58
use function is_object;
59
use function is_readable;
60
use function is_string;
61
use function iter\map;
62
use function iter\toArray;
63
use function iter\values;
64
use function KevinGH\Box\FileSystem\canonicalize;
65
use function KevinGH\Box\FileSystem\file_contents;
66
use function KevinGH\Box\FileSystem\is_absolute_path;
67
use function KevinGH\Box\FileSystem\longest_common_base_path;
68
use function KevinGH\Box\FileSystem\make_path_absolute;
69
use function KevinGH\Box\FileSystem\make_path_relative;
70
use function preg_match;
71
use function property_exists;
72
use function realpath;
73
use function sprintf;
74
use function strtoupper;
75
use function substr;
76
use function trigger_error;
77
use function uniqid;
78
79
/**
80
 * @private
81
 */
82
final class Configuration
83
{
84
    private const DEFAULT_ALIAS = 'test.phar';
85
    private const DEFAULT_MAIN_SCRIPT = 'index.php';
86
    private const DEFAULT_DATETIME_FORMAT = 'Y-m-d H:i:s';
87
    private const DEFAULT_REPLACEMENT_SIGIL = '@';
88
    private const DEFAULT_SHEBANG = '#!/usr/bin/env php';
89
    private const DEFAULT_BANNER = <<<'BANNER'
90
Generated by Humbug Box.
91
92
@link https://github.com/humbug/box
93
BANNER;
94
    private const FILES_SETTINGS = [
95
        'directories',
96
        'finder',
97
    ];
98
    private const PHP_SCOPER_CONFIG = 'scoper.inc.php';
99
    private const DEFAULT_SIGNING_ALGORITHM = Phar::SHA1;
100
101
    private const ALGORITHM_KEY = 'algorithm';
102
    private const ALIAS_KEY = 'alias';
103
    private const ANNOTATIONS_KEY = 'annotations';
104
    private const AUTO_DISCOVERY_KEY = 'force-autodiscovery';
105
    private const BANNER_KEY = 'banner';
106
    private const BANNER_FILE_KEY = 'banner-file';
107
    private const BASE_PATH_KEY = 'base-path';
108
    private const BLACKLIST_KEY = 'blacklist';
109
    private const CHECK_REQUIREMENTS_KEY = 'check-requirements';
110
    private const CHMOD_KEY = 'chmod';
111
    private const COMPACTORS_KEY = 'compactors';
112
    private const COMPRESSION_KEY = 'compression';
113
    private const DATETIME_KEY = 'datetime';
114
    private const DATETIME_FORMAT_KEY = 'datetime-format';
115
    private const DATETIME_FORMAT_DEPRECATED_KEY = 'datetime_format';
116
    private const DIRECTORIES_KEY = 'directories';
117
    private const DIRECTORIES_BIN_KEY = 'directories-bin';
118
    private const DUMP_AUTOLOAD_KEY = 'dump-autoload';
119
    private const EXCLUDE_COMPOSER_FILES_KEY = 'exclude-composer-files';
120
    private const FILES_KEY = 'files';
121
    private const FILES_BIN_KEY = 'files-bin';
122
    private const FINDER_KEY = 'finder';
123
    private const FINDER_BIN_KEY = 'finder-bin';
124
    private const GIT_KEY = 'git';
125
    private const GIT_COMMIT_KEY = 'git-commit';
126
    private const GIT_COMMIT_SHORT_KEY = 'git-commit-short';
127
    private const GIT_TAG_KEY = 'git-tag';
128
    private const GIT_VERSION_KEY = 'git-version';
129
    private const INTERCEPT_KEY = 'intercept';
130
    private const KEY_KEY = 'key';
131
    private const KEY_PASS_KEY = 'key-pass';
132
    private const MAIN_KEY = 'main';
133
    private const MAP_KEY = 'map';
134
    private const METADATA_KEY = 'metadata';
135
    private const OUTPUT_KEY = 'output';
136
    private const PHP_SCOPER_KEY = 'php-scoper';
137
    private const REPLACEMENT_SIGIL_KEY = 'replacement-sigil';
138
    private const REPLACEMENTS_KEY = 'replacements';
139
    private const SHEBANG_KEY = 'shebang';
140
    private const STUB_KEY = 'stub';
141
142
    private $file;
143
    private $fileMode;
144
    private $alias;
145
    private $basePath;
146
    private $composerJson;
147
    private $composerLock;
148
    private $files;
149
    private $binaryFiles;
150
    private $autodiscoveredFiles;
151
    private $dumpAutoload;
152
    private $excludeComposerFiles;
153
    private $compactors;
154
    private $compressionAlgorithm;
155
    private $mainScriptPath;
156
    private $mainScriptContents;
157
    private $map;
158
    private $fileMapper;
159
    private $metadata;
160
    private $tmpOutputPath;
161
    private $outputPath;
162
    private $privateKeyPassphrase;
163
    private $privateKeyPath;
164
    private $promptForPrivateKey;
165
    private $processedReplacements;
166
    private $shebang;
167
    private $signingAlgorithm;
168
    private $stubBannerContents;
169
    private $stubBannerPath;
170
    private $stubPath;
171
    private $isInterceptFileFuncs;
172
    private $isStubGenerated;
173
    private $checkRequirements;
174
    private $warnings;
175
    private $recommendations;
176
177
    public static function create(?string $file, stdClass $raw): self
178
    {
179
        $logger = new ConfigurationLogger();
180
181
        $alias = self::retrieveAlias($raw);
182
183
        $basePath = self::retrieveBasePath($file, $raw, $logger);
184
185
        $composerFiles = self::retrieveComposerFiles($basePath);
186
187
        $mainScriptPath = self::retrieveMainScriptPath($raw, $basePath, $composerFiles[0][1], $logger);
188
        $mainScriptContents = self::retrieveMainScriptContents($mainScriptPath);
189
190
        [$tmpOutputPath, $outputPath] = self::retrieveOutputPath($raw, $basePath, $mainScriptPath, $logger);
191
192
        /** @var (string|null)[] $composerJson */
193
        $composerJson = $composerFiles[0];
194
        /** @var (string|null)[] $composerJson */
195
        $composerLock = $composerFiles[1];
196
197
        $devPackages = ComposerConfiguration::retrieveDevPackages($basePath, $composerJson[1], $composerLock[1]);
0 ignored issues
show
Bug introduced by
It seems like $composerJson[1] can also be of type string; however, parameter $composerJsonDecodedContents of KevinGH\Box\Composer\Com...::retrieveDevPackages() does only seem to accept null|array, maybe add an additional type check? ( Ignorable by Annotation )

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

197
        $devPackages = ComposerConfiguration::retrieveDevPackages($basePath, /** @scrutinizer ignore-type */ $composerJson[1], $composerLock[1]);
Loading history...
198
199
        /**
200
         * @var string[]
201
         * @var Closure  $blacklistFilter
202
         */
203
        [$excludedPaths, $blacklistFilter] = self::retrieveBlacklistFilter(
204
            $raw,
205
            $basePath,
206
            $logger,
207
            $tmpOutputPath,
208
            $outputPath,
209
            $mainScriptPath
210
        );
211
212
        $autodiscoverFiles = self::autodiscoverFiles($file, $raw);
213
        $forceFilesAutodiscovery = self::retrieveForceFilesAutodiscovery($raw, $logger);
214
215
        $filesAggregate = self::collectFiles(
216
            $raw,
217
            $basePath,
218
            $mainScriptPath,
219
            $blacklistFilter,
220
            $excludedPaths,
221
            $devPackages,
222
            $composerFiles,
223
            $composerJson,
224
            $autodiscoverFiles,
225
            $forceFilesAutodiscovery,
226
            $logger
227
        );
228
        $binaryFilesAggregate = self::collectBinaryFiles(
229
            $raw,
230
            $basePath,
231
            $mainScriptPath,
232
            $blacklistFilter,
233
            $excludedPaths,
234
            $devPackages,
235
            $logger
236
        );
237
238
        $dumpAutoload = self::retrieveDumpAutoload($raw, null !== $composerJson[0], $logger);
239
240
        $excludeComposerFiles = self::retrieveExcludeComposerFiles($raw, $logger);
241
242
        $compactors = self::retrieveCompactors($raw, $basePath, $logger);
243
        $compressionAlgorithm = self::retrieveCompressionAlgorithm($raw, $logger);
244
245
        $fileMode = self::retrieveFileMode($raw, $logger);
246
247
        $map = self::retrieveMap($raw, $logger);
248
        $fileMapper = new MapFile($basePath, $map);
249
250
        $metadata = self::retrieveMetadata($raw, $logger);
251
252
        $signingAlgorithm = self::retrieveSigningAlgorithm($raw, $logger);
253
        $promptForPrivateKey = self::retrievePromptForPrivateKey($raw, $signingAlgorithm, $logger);
254
        $privateKeyPath = self::retrievePrivateKeyPath($raw, $basePath, $signingAlgorithm, $logger);
255
        $privateKeyPassphrase = self::retrievePrivateKeyPassphrase($raw, $signingAlgorithm, $logger);
256
257
        $replacements = self::retrieveReplacements($raw, $file, $logger);
258
259
        $shebang = self::retrieveShebang($raw, $logger);
260
261
        $stubBannerContents = self::retrieveStubBannerContents($raw, $logger);
262
        $stubBannerPath = self::retrieveStubBannerPath($raw, $basePath, $logger);
263
264
        if (null !== $stubBannerPath) {
265
            $stubBannerContents = file_contents($stubBannerPath);
266
        }
267
268
        $stubBannerContents = self::normalizeStubBannerContents($stubBannerContents);
269
270
        $stubPath = self::retrieveStubPath($raw, $basePath, $logger);
271
272
        // TODO: add warning related to the stub generation
273
        $interceptsFileFuncs = self::retrieveInterceptsFileFuncs($raw, $logger);
274
        $isStubGenerated = self::retrieveIsStubGenerated($raw, $stubPath);
275
276
        $checkRequirements = self::retrieveCheckRequirements(
277
            $raw,
278
            null !== $composerJson[0],
279
            null !== $composerLock[0],
280
            $logger
281
        );
282
283
        return new self(
284
            $file,
285
            $alias,
286
            $basePath,
287
            $composerJson,
288
            $composerLock,
289
            $filesAggregate,
290
            $binaryFilesAggregate,
291
            $autodiscoverFiles || $forceFilesAutodiscovery,
292
            $dumpAutoload,
293
            $excludeComposerFiles,
294
            $compactors,
295
            $compressionAlgorithm,
296
            $fileMode,
297
            $mainScriptPath,
298
            $mainScriptContents,
299
            $fileMapper,
300
            $metadata,
301
            $tmpOutputPath,
302
            $outputPath,
303
            $privateKeyPassphrase,
304
            $privateKeyPath,
305
            $promptForPrivateKey,
306
            $replacements,
307
            $shebang,
308
            $signingAlgorithm,
309
            $stubBannerContents,
310
            $stubBannerPath,
311
            $stubPath,
312
            $interceptsFileFuncs,
313
            $isStubGenerated,
314
            $checkRequirements,
315
            $logger->getWarnings(),
316
            $logger->getRecommendations()
317
        );
318
    }
319
320
    /**
321
     * @param null|string   $file
322
     * @param null|string   $alias
323
     * @param string        $basePath             Utility to private the base path used and be able to retrieve a
324
     *                                            path relative to it (the base path)
325
     * @param array         $composerJson         The first element is the path to the `composer.json` file as a
326
     *                                            string and the second element its decoded contents as an
327
     *                                            associative array.
328
     * @param array         $composerLock         The first element is the path to the `composer.lock` file as a
329
     *                                            string and the second element its decoded contents as an
330
     *                                            associative array.
331
     * @param SplFileInfo[] $files                List of files
332
     * @param SplFileInfo[] $binaryFiles          List of binary files
333
     * @param bool          $dumpAutoload         Whether or not the Composer autoloader should be dumped
334
     * @param bool          $excludeComposerFiles Whether or not the Composer files composer.json, composer.lock and
335
     *                                            installed.json should be removed from the PHAR
336
     * @param Compactor[]   $compactors           List of file contents compactors
337
     * @param null|int      $compressionAlgorithm Compression algorithm constant value. See the \Phar class constants
338
     * @param null|int      $fileMode             File mode in octal form
339
     * @param string        $mainScriptPath       The main script file path
340
     * @param string        $mainScriptContents   The processed content of the main script file
341
     * @param MapFile       $fileMapper           Utility to map the files from outside and inside the PHAR
342
     * @param mixed         $metadata             The PHAR Metadata
343
     * @param bool          $promptForPrivateKey  If the user should be prompted for the private key passphrase
344
     * @param scalar[]      $replacements         The processed list of replacement placeholders and their values
345
     * @param null|string   $shebang              The shebang line
346
     * @param int           $signingAlgorithm     The PHAR siging algorithm. See \Phar constants
347
     * @param null|string   $stubBannerContents   The stub banner comment
348
     * @param null|string   $stubBannerPath       The path to the stub banner comment file
349
     * @param null|string   $stubPath             The PHAR stub file path
350
     * @param bool          $isInterceptFileFuncs Whether or not Phar::interceptFileFuncs() should be used
351
     * @param bool          $isStubGenerated      Whether or not if the PHAR stub should be generated
352
     * @param bool          $checkRequirements    Whether the PHAR will check the application requirements before
353
     *                                            running
354
     * @param string[]      $warnings
355
     * @param string[]      $recommendations
356
     */
357
    private function __construct(
358
        ?string $file,
359
        string $alias,
360
        string $basePath,
361
        array $composerJson,
362
        array $composerLock,
363
        array $files,
364
        array $binaryFiles,
365
        bool $autodiscoveredFiles,
366
        bool $dumpAutoload,
367
        bool $excludeComposerFiles,
368
        array $compactors,
369
        ?int $compressionAlgorithm,
370
        ?int $fileMode,
371
        ?string $mainScriptPath,
372
        ?string $mainScriptContents,
373
        MapFile $fileMapper,
374
        $metadata,
375
        string $tmpOutputPath,
376
        string $outputPath,
377
        ?string $privateKeyPassphrase,
378
        ?string $privateKeyPath,
379
        bool $promptForPrivateKey,
380
        array $replacements,
381
        ?string $shebang,
382
        int $signingAlgorithm,
383
        ?string $stubBannerContents,
384
        ?string $stubBannerPath,
385
        ?string $stubPath,
386
        bool $isInterceptFileFuncs,
387
        bool $isStubGenerated,
388
        bool $checkRequirements,
389
        array $warnings,
390
        array $recommendations
391
    ) {
392
        Assertion::nullOrInArray(
393
            $compressionAlgorithm,
394
            get_phar_compression_algorithms(),
395
            sprintf(
396
                'Invalid compression algorithm "%%s", use one of "%s" instead.',
397
                implode('", "', array_keys(get_phar_compression_algorithms()))
398
            )
399
        );
400
401
        if (null === $mainScriptPath) {
402
            Assertion::null($mainScriptContents);
403
        } else {
404
            Assertion::notNull($mainScriptContents);
405
        }
406
407
        $this->file = $file;
408
        $this->alias = $alias;
409
        $this->basePath = $basePath;
410
        $this->composerJson = $composerJson;
411
        $this->composerLock = $composerLock;
412
        $this->files = $files;
413
        $this->binaryFiles = $binaryFiles;
414
        $this->autodiscoveredFiles = $autodiscoveredFiles;
415
        $this->dumpAutoload = $dumpAutoload;
416
        $this->excludeComposerFiles = $excludeComposerFiles;
417
        $this->compactors = $compactors;
418
        $this->compressionAlgorithm = $compressionAlgorithm;
419
        $this->fileMode = $fileMode;
420
        $this->mainScriptPath = $mainScriptPath;
421
        $this->mainScriptContents = $mainScriptContents;
422
        $this->fileMapper = $fileMapper;
423
        $this->metadata = $metadata;
424
        $this->tmpOutputPath = $tmpOutputPath;
425
        $this->outputPath = $outputPath;
426
        $this->privateKeyPassphrase = $privateKeyPassphrase;
427
        $this->privateKeyPath = $privateKeyPath;
428
        $this->promptForPrivateKey = $promptForPrivateKey;
429
        $this->processedReplacements = $replacements;
430
        $this->shebang = $shebang;
431
        $this->signingAlgorithm = $signingAlgorithm;
432
        $this->stubBannerContents = $stubBannerContents;
433
        $this->stubBannerPath = $stubBannerPath;
434
        $this->stubPath = $stubPath;
435
        $this->isInterceptFileFuncs = $isInterceptFileFuncs;
436
        $this->isStubGenerated = $isStubGenerated;
437
        $this->checkRequirements = $checkRequirements;
438
        $this->warnings = $warnings;
439
        $this->recommendations = $recommendations;
440
    }
441
442
    public function getConfigurationFile(): ?string
443
    {
444
        return $this->file;
445
    }
446
447
    public function getAlias(): string
448
    {
449
        return $this->alias;
450
    }
451
452
    public function getBasePath(): string
453
    {
454
        return $this->basePath;
455
    }
456
457
    public function getComposerJson(): ?string
458
    {
459
        return $this->composerJson[0];
460
    }
461
462
    public function getDecodedComposerJsonContents(): ?array
463
    {
464
        return $this->composerJson[1];
465
    }
466
467
    public function getComposerLock(): ?string
468
    {
469
        return $this->composerLock[0];
470
    }
471
472
    public function getDecodedComposerLockContents(): ?array
473
    {
474
        return $this->composerLock[1];
475
    }
476
477
    /**
478
     * @return string[]
479
     */
480
    public function getFiles(): array
481
    {
482
        return $this->files;
483
    }
484
485
    /**
486
     * @return string[]
487
     */
488
    public function getBinaryFiles(): array
489
    {
490
        return $this->binaryFiles;
491
    }
492
493
    public function hasAutodiscoveredFiles(): bool
494
    {
495
        return $this->autodiscoveredFiles;
496
    }
497
498
    public function dumpAutoload(): bool
499
    {
500
        return $this->dumpAutoload;
501
    }
502
503
    public function excludeComposerFiles(): bool
504
    {
505
        return $this->excludeComposerFiles;
506
    }
507
508
    /**
509
     * @return Compactor[] the list of compactors
510
     */
511
    public function getCompactors(): array
512
    {
513
        return $this->compactors;
514
    }
515
516
    public function getCompressionAlgorithm(): ?int
517
    {
518
        return $this->compressionAlgorithm;
519
    }
520
521
    public function getFileMode(): ?int
522
    {
523
        return $this->fileMode;
524
    }
525
526
    public function hasMainScript(): bool
527
    {
528
        return null !== $this->mainScriptPath;
529
    }
530
531
    public function getMainScriptPath(): string
532
    {
533
        Assertion::notNull(
534
            $this->mainScriptPath,
535
            'Cannot retrieve the main script path: no main script configured.'
536
        );
537
538
        return $this->mainScriptPath;
539
    }
540
541
    public function getMainScriptContents(): string
542
    {
543
        Assertion::notNull(
544
            $this->mainScriptPath,
545
            'Cannot retrieve the main script contents: no main script configured.'
546
        );
547
548
        return $this->mainScriptContents;
549
    }
550
551
    public function checkRequirements(): bool
552
    {
553
        return $this->checkRequirements;
554
    }
555
556
    public function getTmpOutputPath(): string
557
    {
558
        return $this->tmpOutputPath;
559
    }
560
561
    public function getOutputPath(): string
562
    {
563
        return $this->outputPath;
564
    }
565
566
    public function getFileMapper(): MapFile
567
    {
568
        return $this->fileMapper;
569
    }
570
571
    /**
572
     * @return mixed
573
     */
574
    public function getMetadata()
575
    {
576
        return $this->metadata;
577
    }
578
579
    public function getPrivateKeyPassphrase(): ?string
580
    {
581
        return $this->privateKeyPassphrase;
582
    }
583
584
    public function getPrivateKeyPath(): ?string
585
    {
586
        return $this->privateKeyPath;
587
    }
588
589
    /**
590
     * @deprecated Use promptForPrivateKey() instead
591
     */
592
    public function isPrivateKeyPrompt(): bool
593
    {
594
        return $this->promptForPrivateKey;
595
    }
596
597
    public function promptForPrivateKey(): bool
598
    {
599
        return $this->promptForPrivateKey;
600
    }
601
602
    /**
603
     * @return scalar[]
604
     */
605
    public function getReplacements(): array
606
    {
607
        return $this->processedReplacements;
608
    }
609
610
    public function getShebang(): ?string
611
    {
612
        return $this->shebang;
613
    }
614
615
    public function getSigningAlgorithm(): int
616
    {
617
        return $this->signingAlgorithm;
618
    }
619
620
    public function getStubBannerContents(): ?string
621
    {
622
        return $this->stubBannerContents;
623
    }
624
625
    public function getStubBannerPath(): ?string
626
    {
627
        return $this->stubBannerPath;
628
    }
629
630
    public function getStubPath(): ?string
631
    {
632
        return $this->stubPath;
633
    }
634
635
    public function isInterceptFileFuncs(): bool
636
    {
637
        @trigger_error(
638
            'The function "Configuration::isInterceptFileFuncs()" is deprecated, use "Configuration::interceptsFileFuncs()" instead.',
639
            E_USER_DEPRECATED
640
        );
641
642
        return $this->interceptsFileFuncs();
643
    }
644
645
    public function interceptsFileFuncs(): bool
646
    {
647
        return $this->isInterceptFileFuncs;
648
    }
649
650
    public function isStubGenerated(): bool
651
    {
652
        return $this->isStubGenerated;
653
    }
654
655
    /**
656
     * @return string[]
657
     */
658
    public function getWarnings(): array
659
    {
660
        return $this->warnings;
661
    }
662
663
    /**
664
     * @return string[]
665
     */
666
    public function getRecommendations(): array
667
    {
668
        return $this->recommendations;
669
    }
670
671
    private static function retrieveAlias(stdClass $raw): string
672
    {
673
        if (false === isset($raw->{self::ALIAS_KEY})) {
674
            return uniqid('box-auto-generated-alias-', false).'.phar';
675
        }
676
677
        $alias = trim($raw->{self::ALIAS_KEY});
678
679
        Assertion::notEmpty($alias, 'A PHAR alias cannot be empty when provided.');
680
681
        return $alias;
682
    }
683
684
    private static function retrieveBasePath(?string $file, stdClass $raw, ConfigurationLogger $logger): string
685
    {
686
        if (null === $file) {
687
            return getcwd();
688
        }
689
690
        if (false === isset($raw->{self::BASE_PATH_KEY})) {
691
            return realpath(dirname($file));
692
        }
693
694
        $basePath = trim($raw->{self::BASE_PATH_KEY});
695
696
        Assertion::directory(
697
            $basePath,
698
            'The base path "%s" is not a directory or does not exist.'
699
        );
700
701
        $basePath = realpath($basePath);
702
        $defaultPath = realpath(dirname($file));
703
704
        if ($basePath === $defaultPath) {
705
            self::addRecommendationForDefaultValue($logger, self::BASE_PATH_KEY);
706
        }
707
708
        return $basePath;
709
    }
710
711
    /**
712
     * Checks if files should be auto-discovered. It does NOT account for the force-autodiscovery setting.
713
     */
714
    private static function autodiscoverFiles(?string $file, stdClass $raw): bool
715
    {
716
        if (null === $file) {
717
            return true;
718
        }
719
720
        $associativeRaw = (array) $raw;
721
722
        return self::FILES_SETTINGS === array_diff(self::FILES_SETTINGS, array_keys($associativeRaw));
723
    }
724
725
    private static function retrieveForceFilesAutodiscovery(stdClass $raw, ConfigurationLogger $logger): bool
726
    {
727
        if (property_exists($raw, self::AUTO_DISCOVERY_KEY) && false === $raw->{self::AUTO_DISCOVERY_KEY}) {
728
            self::addRecommendationForDefaultValue($logger, self::AUTO_DISCOVERY_KEY);
729
        }
730
731
        return $raw->{self::AUTO_DISCOVERY_KEY} ?? false;
732
    }
733
734
    private static function retrieveBlacklistFilter(
735
        stdClass $raw,
736
        string $basePath,
737
        ConfigurationLogger $logger,
738
        ?string ...$excludedPaths
739
    ): array {
740
        $blacklist = self::retrieveBlacklist($raw, $basePath, $logger, ...$excludedPaths);
741
742
        $blacklistFilter = function (SplFileInfo $file) use ($blacklist): ?bool {
743
            if ($file->isLink()) {
744
                return false;
745
            }
746
747
            if (false === $file->getRealPath()) {
748
                return false;
749
            }
750
751
            if (in_array($file->getRealPath(), $blacklist, true)) {
752
                return false;
753
            }
754
755
            return null;
756
        };
757
758
        return [$blacklist, $blacklistFilter];
759
    }
760
761
    /**
762
     * @param stdClass        $raw
763
     * @param string          $basePath
764
     * @param null[]|string[] $excludedPaths
765
     *
766
     * @return string[]
767
     */
768
    private static function retrieveBlacklist(
769
        stdClass $raw,
770
        string $basePath,
771
        ConfigurationLogger $logger,
772
        ?string ...$excludedPaths
773
    ): array {
774
        if (property_exists($raw, self::BLACKLIST_KEY) && [] === $raw->{self::BLACKLIST_KEY}) {
775
            self::addRecommendationForDefaultValue($logger, self::BLACKLIST_KEY);
776
        }
777
778
        /** @var string[] $blacklist */
779
        $blacklist = array_merge(
780
            array_filter($excludedPaths),
781
            $raw->{self::BLACKLIST_KEY} ?? []
782
        );
783
784
        $normalizedBlacklist = [];
785
786
        foreach ($blacklist as $file) {
787
            $normalizedBlacklist[] = self::normalizePath($file, $basePath);
788
            $normalizedBlacklist[] = canonicalize(make_path_relative(trim($file), $basePath));
789
        }
790
791
        return array_unique($normalizedBlacklist);
792
    }
793
794
    /**
795
     * @param string[] $excludedPaths
796
     * @param string[] $devPackages
797
     *
798
     * @return SplFileInfo[]
799
     */
800
    private static function collectFiles(
801
        stdClass $raw,
802
        string $basePath,
803
        ?string $mainScriptPath,
804
        Closure $blacklistFilter,
805
        array $excludedPaths,
806
        array $devPackages,
807
        array $composerFiles,
808
        array $composerJson,
809
        bool $autodiscoverFiles,
810
        bool $forceFilesAutodiscovery,
811
        ConfigurationLogger $logger
812
    ): array {
813
        $files = [self::retrieveFiles($raw, self::FILES_KEY, $basePath, $composerFiles, $mainScriptPath, $logger)];
814
815
        if ($autodiscoverFiles || $forceFilesAutodiscovery) {
816
            [$filesToAppend, $directories] = self::retrieveAllDirectoriesToInclude(
817
                $basePath,
818
                $composerJson[1],
819
                $devPackages,
820
                array_filter(
821
                    array_column($composerFiles, 0)
822
                ),
823
                $excludedPaths
824
            );
825
826
            $files[] = $filesToAppend;
827
828
            $files[] = self::retrieveAllFiles(
829
                $basePath,
830
                $directories,
831
                $mainScriptPath,
832
                $blacklistFilter,
833
                $excludedPaths,
834
                $devPackages
835
            );
836
        }
837
838
        if (false === $autodiscoverFiles) {
839
            $files[] = self::retrieveDirectories(
840
                $raw,
841
                self::DIRECTORIES_KEY,
842
                $basePath,
843
                $blacklistFilter,
844
                $excludedPaths,
845
                $logger
846
            );
847
848
            $filesFromFinders = self::retrieveFilesFromFinders(
849
                $raw,
850
                self::FINDER_KEY,
851
                $basePath,
852
                $blacklistFilter,
853
                $devPackages,
854
                $logger
855
            );
856
857
            foreach ($filesFromFinders as $filesFromFinder) {
858
                // Avoid an array_merge here as it can be quite expansive at this stage depending of the number of files
859
                $files[] = $filesFromFinder;
860
            }
861
        }
862
863
        return self::retrieveFilesAggregate(...$files);
864
    }
865
866
    /**
867
     * @param string[] $excludedPaths
868
     * @param string[] $devPackages
869
     *
870
     * @return SplFileInfo[]
871
     */
872
    private static function collectBinaryFiles(
873
        stdClass $raw,
874
        string $basePath,
875
        ?string $mainScriptPath,
876
        Closure $blacklistFilter,
877
        array $excludedPaths,
878
        array $devPackages,
879
        ConfigurationLogger $logger
880
    ): array {
881
        $binaryFiles = self::retrieveFiles($raw, self::FILES_BIN_KEY, $basePath, [], $mainScriptPath, $logger);
882
883
        $binaryDirectories = self::retrieveDirectories(
884
            $raw,
885
            self::DIRECTORIES_BIN_KEY,
886
            $basePath,
887
            $blacklistFilter,
888
            $excludedPaths,
889
            $logger
890
        );
891
892
        $binaryFilesFromFinders = self::retrieveFilesFromFinders(
893
            $raw,
894
            self::FINDER_BIN_KEY,
895
            $basePath,
896
            $blacklistFilter,
897
            $devPackages,
898
            $logger
899
        );
900
901
        return self::retrieveFilesAggregate($binaryFiles, $binaryDirectories, ...$binaryFilesFromFinders);
902
    }
903
904
    /**
905
     * @return SplFileInfo[]
906
     */
907
    private static function retrieveFiles(
908
        stdClass $raw,
909
        string $key,
910
        string $basePath,
911
        array $composerFiles,
912
        ?string $mainScriptPath,
913
        ConfigurationLogger $logger
914
    ): array {
915
        $files = [];
916
917
        if (isset($composerFiles[0][0])) {
918
            $files[] = $composerFiles[0][0];
919
        }
920
921
        if (isset($composerFiles[1][1])) {
922
            $files[] = $composerFiles[1][0];
923
        }
924
925
        if (false === isset($raw->{$key})) {
926
            return $files;
927
        }
928
929
        if ([] === (array) $raw->{$key}) {
930
            self::addRecommendationForDefaultValue($logger, $key);
931
932
            return $files;
933
        }
934
935
        $files = array_merge((array) $raw->{$key}, $files);
936
937
        Assertion::allString($files);
938
939
        $normalizePath = function (string $file) use ($basePath, $key, $mainScriptPath): ?SplFileInfo {
940
            $file = self::normalizePath($file, $basePath);
941
942
            Assertion::false(
943
                is_link($file),
944
                sprintf(
945
                    'Cannot add the link "%s": links are not supported.',
946
                    $file
947
                )
948
            );
949
950
            Assertion::file(
951
                $file,
952
                sprintf(
953
                    '"%s" must contain a list of existing files. Could not find "%%s".',
954
                    $key
955
                )
956
            );
957
958
            return $mainScriptPath === $file ? null : new SplFileInfo($file);
959
        };
960
961
        return array_filter(array_map($normalizePath, $files));
962
    }
963
964
    /**
965
     * @param string   $key           Config property name
966
     * @param string[] $excludedPaths
967
     *
968
     * @return iterable|SplFileInfo[]
969
     */
970
    private static function retrieveDirectories(
971
        stdClass $raw,
972
        string $key,
973
        string $basePath,
974
        Closure $blacklistFilter,
975
        array $excludedPaths,
976
        ConfigurationLogger $logger
977
    ): iterable {
978
        $directories = self::retrieveDirectoryPaths($raw, $key, $basePath, $logger);
979
980
        if ([] !== $directories) {
981
            $finder = Finder::create()
982
                ->files()
983
                ->filter($blacklistFilter)
984
                ->ignoreVCS(true)
985
                ->in($directories)
986
            ;
987
988
            foreach ($excludedPaths as $excludedPath) {
989
                $finder->notPath($excludedPath);
990
            }
991
992
            return $finder;
993
        }
994
995
        return [];
996
    }
997
998
    /**
999
     * @param string[] $devPackages
1000
     *
1001
     * @return iterable[]|SplFileInfo[][]
1002
     */
1003
    private static function retrieveFilesFromFinders(
1004
        stdClass $raw,
1005
        string $key,
1006
        string $basePath,
1007
        Closure $blacklistFilter,
1008
        array $devPackages,
1009
        ConfigurationLogger $logger
1010
    ): array {
1011
        if (isset($raw->{$key})) {
1012
            $finder = $raw->{$key};
1013
1014
            if ([] === $finder) {
1015
                self::addRecommendationForDefaultValue($logger, $key);
1016
1017
                return [];
1018
            }
1019
1020
            return self::processFinders($finder, $basePath, $blacklistFilter, $devPackages);
1021
        }
1022
1023
        return [];
1024
    }
1025
1026
    /**
1027
     * @param iterable[]|SplFileInfo[][] $fileIterators
1028
     *
1029
     * @return SplFileInfo[]
1030
     */
1031
    private static function retrieveFilesAggregate(iterable ...$fileIterators): array
1032
    {
1033
        $files = [];
1034
1035
        foreach ($fileIterators as $fileIterator) {
1036
            foreach ($fileIterator as $file) {
1037
                $files[(string) $file] = $file;
1038
            }
1039
        }
1040
1041
        return array_values($files);
1042
    }
1043
1044
    /**
1045
     * @param string[] $devPackages
1046
     *
1047
     * @return Finder[]|SplFileInfo[][]
1048
     */
1049
    private static function processFinders(
1050
        array $findersConfig,
1051
        string $basePath,
1052
        Closure $blacklistFilter,
1053
        array $devPackages
1054
    ): array {
1055
        $processFinderConfig = function (stdClass $config) use ($basePath, $blacklistFilter, $devPackages) {
1056
            return self::processFinder($config, $basePath, $blacklistFilter, $devPackages);
1057
        };
1058
1059
        return array_map($processFinderConfig, $findersConfig);
1060
    }
1061
1062
    /**
1063
     * @param string[] $devPackages
1064
     *
1065
     * @return Finder|SplFileInfo[]
1066
     */
1067
    private static function processFinder(
1068
        stdClass $config,
1069
        string $basePath,
1070
        Closure $blacklistFilter,
1071
        array $devPackages
1072
    ): Finder {
1073
        $finder = Finder::create()
1074
            ->files()
1075
            ->filter($blacklistFilter)
1076
            ->filter(
1077
                function (SplFileInfo $fileInfo) use ($devPackages): bool {
1078
                    foreach ($devPackages as $devPackage) {
1079
                        if ($devPackage === longest_common_base_path([$devPackage, $fileInfo->getRealPath()])) {
1080
                            // File belongs to the dev package
1081
                            return false;
1082
                        }
1083
                    }
1084
1085
                    return true;
1086
                }
1087
            )
1088
            ->ignoreVCS(true)
1089
        ;
1090
1091
        $normalizedConfig = (function (array $config, Finder $finder): array {
1092
            $normalizedConfig = [];
1093
1094
            foreach ($config as $method => $arguments) {
1095
                $method = trim($method);
1096
                $arguments = (array) $arguments;
1097
1098
                Assertion::methodExists(
1099
                    $method,
1100
                    $finder,
1101
                    'The method "Finder::%s" does not exist.'
1102
                );
1103
1104
                $normalizedConfig[$method] = $arguments;
1105
            }
1106
1107
            krsort($normalizedConfig);
1108
1109
            return $normalizedConfig;
1110
        })((array) $config, $finder);
1111
1112
        $createNormalizedDirectories = function (string $directory) use ($basePath): ?string {
1113
            $directory = self::normalizePath($directory, $basePath);
1114
1115
            Assertion::false(
1116
                is_link($directory),
1117
                sprintf(
1118
                    'Cannot append the link "%s" to the Finder: links are not supported.',
1119
                    $directory
1120
                )
1121
            );
1122
1123
            Assertion::directory($directory);
1124
1125
            return $directory;
1126
        };
1127
1128
        $normalizeFileOrDirectory = function (string &$fileOrDirectory) use ($basePath, $blacklistFilter): void {
1129
            $fileOrDirectory = self::normalizePath($fileOrDirectory, $basePath);
1130
1131
            Assertion::false(
1132
                is_link($fileOrDirectory),
1133
                sprintf(
1134
                    'Cannot append the link "%s" to the Finder: links are not supported.',
1135
                    $fileOrDirectory
1136
                )
1137
            );
1138
1139
            Assertion::false(
1140
                file_exists($fileOrDirectory),
1141
                sprintf(
1142
                    'Path "%s" was expected to be a file or directory. It may be a symlink (which are unsupported).',
1143
                    $fileOrDirectory
1144
                )
1145
            );
1146
1147
            if (false === is_file($fileOrDirectory)) {
1148
                Assertion::directory($fileOrDirectory);
1149
            } else {
1150
                Assertion::file($fileOrDirectory);
1151
            }
1152
1153
            if (false === $blacklistFilter(new SplFileInfo($fileOrDirectory))) {
1154
                $fileOrDirectory = null;
1155
            }
1156
        };
1157
1158
        foreach ($normalizedConfig as $method => $arguments) {
1159
            if ('in' === $method) {
1160
                $normalizedConfig[$method] = $arguments = array_map($createNormalizedDirectories, $arguments);
1161
            }
1162
1163
            if ('exclude' === $method) {
1164
                $arguments = array_unique(array_map('trim', $arguments));
1165
            }
1166
1167
            if ('append' === $method) {
1168
                array_walk($arguments, $normalizeFileOrDirectory);
1169
1170
                $arguments = [array_filter($arguments)];
1171
            }
1172
1173
            foreach ($arguments as $argument) {
1174
                $finder->$method($argument);
1175
            }
1176
        }
1177
1178
        return $finder;
1179
    }
1180
1181
    /**
1182
     * @param string[] $devPackages
1183
     * @param string[] $filesToAppend
1184
     *
1185
     * @return string[][]
1186
     */
1187
    private static function retrieveAllDirectoriesToInclude(
1188
        string $basePath,
1189
        ?array $decodedJsonContents,
1190
        array $devPackages,
1191
        array $filesToAppend,
1192
        array $excludedPaths
1193
    ): array {
1194
        $toString = function ($file): string {
1195
            // @param string|SplFileInfo $file
1196
            return (string) $file;
1197
        };
1198
1199
        if (null !== $decodedJsonContents && array_key_exists('vendor-dir', $decodedJsonContents)) {
1200
            $vendorDir = self::normalizePath($decodedJsonContents['vendor-dir'], $basePath);
1201
        } else {
1202
            $vendorDir = self::normalizePath('vendor', $basePath);
1203
        }
1204
1205
        if (file_exists($vendorDir)) {
1206
            // The installed.json file is necessary for dumping the autoload correctly. Note however that it will not exists if no
1207
            // dependencies are included in the `composer.json`
1208
            $installedJsonFiles = self::normalizePath($vendorDir.'/composer/installed.json', $basePath);
1209
1210
            if (file_exists($installedJsonFiles)) {
1211
                $filesToAppend[] = $installedJsonFiles;
1212
            }
1213
1214
            $vendorPackages = toArray(values(map(
1215
                $toString,
1216
                Finder::create()
1217
                    ->in($vendorDir)
1218
                    ->directories()
1219
                    ->depth(1)
1220
                    ->ignoreUnreadableDirs()
1221
                    ->filter(
1222
                        function (SplFileInfo $fileInfo): ?bool {
1223
                            if ($fileInfo->isLink()) {
1224
                                return false;
1225
                            }
1226
1227
                            return null;
1228
                        }
1229
                    )
1230
            )));
1231
1232
            $vendorPackages = array_diff($vendorPackages, $devPackages);
1233
1234
            if (null === $decodedJsonContents || false === array_key_exists('autoload', $decodedJsonContents)) {
1235
                $files = toArray(values(map(
1236
                    $toString,
1237
                    Finder::create()
1238
                        ->in($basePath)
1239
                        ->files()
1240
                        ->depth(0)
1241
                )));
1242
1243
                $directories = toArray(values(map(
1244
                    $toString,
1245
                    Finder::create()
1246
                        ->in($basePath)
1247
                        ->notPath('vendor')
1248
                        ->directories()
1249
                        ->depth(0)
1250
                )));
1251
1252
                return [
1253
                    array_merge($files, $filesToAppend),
1254
                    array_merge($directories, $vendorPackages),
1255
                ];
1256
            }
1257
1258
            $paths = $vendorPackages;
1259
        } else {
1260
            $paths = [];
1261
        }
1262
1263
        $autoload = $decodedJsonContents['autoload'] ?? [];
1264
1265
        if (array_key_exists('psr-4', $autoload)) {
1266
            foreach ($autoload['psr-4'] as $path) {
1267
                /** @var string|string[] $path */
1268
                $composerPaths = (array) $path;
1269
1270
                foreach ($composerPaths as $composerPath) {
1271
                    $paths[] = '' !== trim($composerPath) ? $composerPath : $basePath;
1272
                }
1273
            }
1274
        }
1275
1276
        if (array_key_exists('psr-0', $autoload)) {
1277
            foreach ($autoload['psr-0'] as $path) {
1278
                /** @var string|string[] $path */
1279
                $composerPaths = (array) $path;
1280
1281
                foreach ($composerPaths as $composerPath) {
1282
                    if ('' !== trim($composerPath)) {
1283
                        $paths[] = $composerPath;
1284
                    }
1285
                }
1286
            }
1287
        }
1288
1289
        if (array_key_exists('classmap', $autoload)) {
1290
            foreach ($autoload['classmap'] as $path) {
1291
                // @var string $path
1292
                $paths[] = $path;
1293
            }
1294
        }
1295
1296
        $normalizePath = function (string $path) use ($basePath): string {
1297
            return is_absolute_path($path)
1298
                ? canonicalize($path)
1299
                : self::normalizePath(trim($path, '/ '), $basePath)
1300
            ;
1301
        };
1302
1303
        if (array_key_exists('files', $autoload)) {
1304
            foreach ($autoload['files'] as $path) {
1305
                // @var string $path
1306
                $path = $normalizePath($path);
1307
1308
                Assertion::file($path);
1309
                Assertion::false(is_link($path), 'Cannot add the link "'.$path.'": links are not supported.');
1310
1311
                $filesToAppend[] = $path;
1312
            }
1313
        }
1314
1315
        $files = $filesToAppend;
1316
        $directories = [];
1317
1318
        foreach ($paths as $path) {
1319
            $path = $normalizePath($path);
1320
1321
            Assertion::true(file_exists($path), 'File or directory "'.$path.'" was expected to exist.');
1322
            Assertion::false(is_link($path), 'Cannot add the link "'.$path.'": links are not supported.');
1323
1324
            if (is_file($path)) {
1325
                $files[] = $path;
1326
            } else {
1327
                $directories[] = $path;
1328
            }
1329
        }
1330
1331
        [$files, $directories] = [
1332
            array_unique($files),
1333
            array_unique($directories),
1334
        ];
1335
1336
        return [
1337
            array_diff($files, $excludedPaths),
1338
            array_diff($directories, $excludedPaths),
1339
        ];
1340
    }
1341
1342
    /**
1343
     * @param string[] $files
1344
     * @param string[] $directories
1345
     * @param string[] $excludedPaths
1346
     * @param string[] $devPackages
1347
     *
1348
     * @return SplFileInfo[]
1349
     */
1350
    private static function retrieveAllFiles(
1351
        string $basePath,
1352
        array $directories,
1353
        ?string $mainScriptPath,
1354
        Closure $blacklistFilter,
1355
        array $excludedPaths,
1356
        array $devPackages
1357
    ): iterable {
1358
        if ([] === $directories) {
1359
            return [];
1360
        }
1361
1362
        $relativeDevPackages = array_map(
1363
            function (string $packagePath) use ($basePath): string {
1364
                return make_path_relative($packagePath, $basePath);
1365
            },
1366
            $devPackages
1367
        );
1368
1369
        $finder = Finder::create()
1370
            ->files()
1371
            ->filter($blacklistFilter)
1372
            ->exclude($relativeDevPackages)
1373
            ->ignoreVCS(true)
1374
            ->ignoreDotFiles(true)
1375
            // Remove build files
1376
            ->notName('composer.json')
1377
            ->notName('composer.lock')
1378
            ->notName('Makefile')
1379
            ->notName('Vagrantfile')
1380
            ->notName('phpstan*.neon*')
1381
            ->notName('infection*.json*')
1382
            ->notName('humbug*.json*')
1383
            ->notName('easy-coding-standard.neon*')
1384
            ->notName('phpbench.json*')
1385
            ->notName('phpcs.xml*')
1386
            ->notName('psalm.xml*')
1387
            ->notName('scoper.inc*')
1388
            ->notName('box*.json*')
1389
            ->notName('phpdoc*.xml*')
1390
            ->notName('codecov.yml*')
1391
            ->notName('Dockerfile')
1392
            ->exclude('build')
1393
            ->exclude('dist')
1394
            ->exclude('example')
1395
            ->exclude('examples')
1396
            // Remove documentation
1397
            ->notName('*.md')
1398
            ->notName('*.rst')
1399
            ->notName('/^readme(\..*+)?$/i')
1400
            ->notName('/^upgrade(\..*+)?$/i')
1401
            ->notName('/^contributing(\..*+)?$/i')
1402
            ->notName('/^changelog(\..*+)?$/i')
1403
            ->notName('/^authors?(\..*+)?$/i')
1404
            ->notName('/^conduct(\..*+)?$/i')
1405
            ->notName('/^todo(\..*+)?$/i')
1406
            ->exclude('doc')
1407
            ->exclude('docs')
1408
            ->exclude('documentation')
1409
            // Remove backup files
1410
            ->notName('*~')
1411
            ->notName('*.back')
1412
            ->notName('*.swp')
1413
            // Remove tests
1414
            ->notName('*Test.php')
1415
            ->exclude('test')
1416
            ->exclude('Test')
1417
            ->exclude('tests')
1418
            ->exclude('Tests')
1419
            ->notName('/phpunit.*\.xml(.dist)?/')
1420
            ->notName('/behat.*\.yml(.dist)?/')
1421
            ->exclude('spec')
1422
            ->exclude('specs')
1423
            ->exclude('features')
1424
            // Remove CI config
1425
            ->exclude('travis')
1426
            ->notName('travis.yml')
1427
            ->notName('appveyor.yml')
1428
            ->notName('build.xml*')
1429
        ;
1430
1431
        if (null !== $mainScriptPath) {
1432
            $finder->notPath(make_path_relative($mainScriptPath, $basePath));
1433
        }
1434
1435
        $finder->in($directories);
1436
1437
        $excludedPaths = array_unique(
1438
            array_filter(
1439
                array_map(
1440
                    function (string $path) use ($basePath): string {
1441
                        return make_path_relative($path, $basePath);
1442
                    },
1443
                    $excludedPaths
1444
                ),
1445
                function (string $path): bool {
1446
                    return '..' !== substr($path, 0, 2);
1447
                }
1448
            )
1449
        );
1450
1451
        foreach ($excludedPaths as $excludedPath) {
1452
            $finder->notPath($excludedPath);
1453
        }
1454
1455
        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...
1456
    }
1457
1458
    /**
1459
     * @param stdClass $raw
1460
     * @param string   $key      Config property name
1461
     * @param string   $basePath
1462
     *
1463
     * @return string[]
1464
     */
1465
    private static function retrieveDirectoryPaths(
1466
        stdClass $raw,
1467
        string $key,
1468
        string $basePath,
1469
        ConfigurationLogger $logger
1470
    ): array {
1471
        if (false === isset($raw->{$key})) {
1472
            return [];
1473
        }
1474
1475
        $directories = $raw->{$key};
1476
1477
        if ([] === $directories) {
1478
            self::addRecommendationForDefaultValue($logger, $key);
1479
1480
            return [];
1481
        }
1482
1483
        $normalizeDirectory = function (string $directory) use ($basePath, $key): string {
1484
            $directory = self::normalizePath($directory, $basePath);
1485
1486
            Assertion::false(
1487
                is_link($directory),
1488
                sprintf(
1489
                    'Cannot add the link "%s": links are not supported.',
1490
                    $directory
1491
                )
1492
            );
1493
1494
            Assertion::directory(
1495
                $directory,
1496
                sprintf(
1497
                    '"%s" must contain a list of existing directories. Could not find "%%s".',
1498
                    $key
1499
                )
1500
            );
1501
1502
            return $directory;
1503
        };
1504
1505
        return array_map($normalizeDirectory, $directories);
1506
    }
1507
1508
    private static function normalizePath(string $file, string $basePath): string
1509
    {
1510
        return make_path_absolute(trim($file), $basePath);
1511
    }
1512
1513
    private static function retrieveDumpAutoload(stdClass $raw, bool $composerJson, ConfigurationLogger $logger): bool
1514
    {
1515
        if (property_exists($raw, self::DUMP_AUTOLOAD_KEY) && true === $raw->{self::DUMP_AUTOLOAD_KEY}) {
1516
            self::addRecommendationForDefaultValue($logger, self::DUMP_AUTOLOAD_KEY);
1517
        }
1518
1519
        if (false === property_exists($raw, self::DUMP_AUTOLOAD_KEY)) {
1520
            return $composerJson;
1521
        }
1522
1523
        $dumpAutoload = $raw->{self::DUMP_AUTOLOAD_KEY} ?? true;
1524
1525
        if (false === $composerJson && $dumpAutoload) {
1526
            $logger->addWarning(
1527
                'The "dump-autoload" setting has been set but has been ignored because the composer.json file necessary'
1528
                .' for it could not be found'
1529
            );
1530
1531
            return false;
1532
        }
1533
1534
        return $composerJson && false !== $dumpAutoload;
1535
    }
1536
1537
    private static function retrieveExcludeComposerFiles(stdClass $raw, ConfigurationLogger $logger): bool
1538
    {
1539
        if (property_exists($raw, self::EXCLUDE_COMPOSER_FILES_KEY)
1540
            && (null === $raw->{self::EXCLUDE_COMPOSER_FILES_KEY} || true === $raw->{self::EXCLUDE_COMPOSER_FILES_KEY})
1541
        ) {
1542
            self::addRecommendationForDefaultValue($logger, self::EXCLUDE_COMPOSER_FILES_KEY);
1543
        }
1544
1545
        return $raw->{self::EXCLUDE_COMPOSER_FILES_KEY} ?? true;
1546
    }
1547
1548
    /**
1549
     * @return Compactor[]
1550
     */
1551
    private static function retrieveCompactors(stdClass $raw, string $basePath, ConfigurationLogger $logger): array
1552
    {
1553
        if (property_exists($raw, self::COMPACTORS_KEY)
1554
            && (null === $raw->{self::COMPACTORS_KEY} || [] === $raw->{self::COMPACTORS_KEY})
1555
        ) {
1556
            self::addRecommendationForDefaultValue($logger, self::COMPACTORS_KEY);
1557
        }
1558
1559
        if (false === isset($raw->{self::COMPACTORS_KEY})) {
1560
            return [];
1561
        }
1562
1563
        $compactorClasses = array_unique((array) $raw->{self::COMPACTORS_KEY});
1564
1565
        return array_map(
1566
            function (string $class) use ($raw, $basePath, $logger): Compactor {
1567
                Assertion::classExists($class, 'The compactor class "%s" does not exist.');
1568
                Assertion::implementsInterface($class, Compactor::class, 'The class "%s" is not a compactor class.');
1569
1570
                if (Php::class === $class || LegacyPhp::class === $class) {
1571
                    return self::createPhpCompactor($raw);
1572
                }
1573
1574
                if (PhpScoperCompactor::class === $class) {
1575
                    $phpScoperConfig = self::retrievePhpScoperConfig($raw, $basePath, $logger);
1576
1577
                    $prefix = null === $phpScoperConfig->getPrefix()
1578
                        ? uniqid('_HumbugBox', false)
1579
                        : $phpScoperConfig->getPrefix()
1580
                    ;
1581
1582
                    return new PhpScoperCompactor(
1583
                        new SimpleScoper(
1584
                            (new class() extends ApplicationFactory {
1585
                                public static function createScoper(): Scoper
1586
                                {
1587
                                    return parent::createScoper();
1588
                                }
1589
                            })::createScoper(),
1590
                            $prefix,
1591
                            $phpScoperConfig->getWhitelist(),
1592
                            $phpScoperConfig->getPatchers()
1593
                        )
1594
                    );
1595
                }
1596
1597
                return new $class();
1598
            },
1599
            $compactorClasses
1600
        );
1601
    }
1602
1603
    private static function retrieveCompressionAlgorithm(stdClass $raw, ConfigurationLogger $logger): ?int
1604
    {
1605
        if (property_exists($raw, self::COMPRESSION_KEY) && null === $raw->{self::COMPRESSION_KEY}) {
1606
            self::addRecommendationForDefaultValue($logger, self::COMPRESSION_KEY);
1607
        }
1608
1609
        if (false === isset($raw->{self::COMPRESSION_KEY})) {
1610
            return null;
1611
        }
1612
1613
        $knownAlgorithmNames = array_keys(get_phar_compression_algorithms());
1614
1615
        Assertion::inArray(
1616
            $raw->{self::COMPRESSION_KEY},
1617
            $knownAlgorithmNames,
1618
            sprintf(
1619
                'Invalid compression algorithm "%%s", use one of "%s" instead.',
1620
                implode('", "', $knownAlgorithmNames)
1621
            )
1622
        );
1623
1624
        $value = get_phar_compression_algorithms()[$raw->{self::COMPRESSION_KEY}];
1625
1626
        // Phar::NONE is not valid for compressFiles()
1627
        if (Phar::NONE === $value) {
1628
            return null;
1629
        }
1630
1631
        return $value;
1632
    }
1633
1634
    private static function retrieveFileMode(stdClass $raw, ConfigurationLogger $logger): ?int
1635
    {
1636
        if (property_exists($raw, self::CHMOD_KEY) && null === $raw->{self::CHMOD_KEY}) {
1637
            self::addRecommendationForDefaultValue($logger, self::CHMOD_KEY);
1638
        }
1639
1640
        $defaultChmod = intval(0755, 8);
1641
1642
        if (isset($raw->{self::CHMOD_KEY})) {
1643
            $chmod = intval($raw->{self::CHMOD_KEY}, 8);
1644
1645
            if ($defaultChmod === $chmod) {
1646
                self::addRecommendationForDefaultValue($logger, self::CHMOD_KEY);
1647
            }
1648
1649
            return $chmod;
1650
        }
1651
1652
        return $defaultChmod;
1653
    }
1654
1655
    private static function retrieveMainScriptPath(
1656
        stdClass $raw,
1657
        string $basePath,
1658
        ?array $decodedJsonContents,
1659
        ConfigurationLogger $logger
1660
    ): ?string {
1661
        $firstBin = false;
1662
1663
        if (null !== $decodedJsonContents && array_key_exists('bin', $decodedJsonContents)) {
1664
            /** @var false|string $firstBin */
1665
            $firstBin = current((array) $decodedJsonContents['bin']);
1666
1667
            if (false !== $firstBin) {
1668
                $firstBin = self::normalizePath($firstBin, $basePath);
1669
            }
1670
        }
1671
1672
        if (isset($raw->{self::MAIN_KEY})) {
1673
            $main = $raw->{self::MAIN_KEY};
1674
1675
            if (is_string($main)) {
1676
                $main = self::normalizePath($main, $basePath);
1677
1678
                if ($main === $firstBin) {
1679
                    $logger->addRecommendation('The "main" setting can be omitted since is set to its default value');
1680
                }
1681
            }
1682
        } else {
1683
            $main = false !== $firstBin ? $firstBin : self::normalizePath(self::DEFAULT_MAIN_SCRIPT, $basePath);
1684
        }
1685
1686
        if (is_bool($main)) {
1687
            Assertion::false(
1688
                $main,
1689
                'Cannot "enable" a main script: either disable it with `false` or give the main script file path.'
1690
            );
1691
1692
            return null;
1693
        }
1694
1695
        Assertion::file($main);
1696
1697
        return $main;
1698
    }
1699
1700
    private static function retrieveMainScriptContents(?string $mainScriptPath): ?string
1701
    {
1702
        if (null === $mainScriptPath) {
1703
            return null;
1704
        }
1705
1706
        $contents = file_contents($mainScriptPath);
1707
1708
        // Remove the shebang line: the shebang line in a PHAR should be located in the stub file which is the real
1709
        // PHAR entry point file.
1710
        // If one needs the shebang, then the main file should act as the stub and be registered as such and in which
1711
        // case the main script can be ignored or disabled.
1712
        return preg_replace('/^#!.*\s*/', '', $contents);
1713
    }
1714
1715
    /**
1716
     * @return string|null[][]
1717
     */
1718
    private static function retrieveComposerFiles(string $basePath): array
1719
    {
1720
        $retrieveFileAndContents = function (string $file): array {
1721
            $json = new Json();
1722
1723
            if (false === file_exists($file) || false === is_file($file) || false === is_readable($file)) {
1724
                return [null, null];
1725
            }
1726
1727
            try {
1728
                $contents = $json->decodeFile($file, true);
1729
            } catch (ParsingException $exception) {
1730
                throw new InvalidArgumentException(
1731
                    sprintf(
1732
                        'Expected the file "%s" to be a valid composer.json file but an error has been found: %s',
1733
                        $file,
1734
                        $exception->getMessage()
1735
                    ),
1736
                    0,
1737
                    $exception
1738
                );
1739
            }
1740
1741
            return [$file, $contents];
1742
        };
1743
1744
        [$composerJson, $composerJsonContents] = $retrieveFileAndContents(canonicalize($basePath.'/composer.json'));
1745
        [$composerLock, $composerLockContents] = $retrieveFileAndContents(canonicalize($basePath.'/composer.lock'));
1746
1747
        return [
1748
            [$composerJson, $composerJsonContents],
1749
            [$composerLock, $composerLockContents],
1750
        ];
1751
    }
1752
1753
    /**
1754
     * @return string[][]
1755
     */
1756
    private static function retrieveMap(stdClass $raw, ConfigurationLogger $logger): array
1757
    {
1758
        if (false === isset($raw->{self::MAP_KEY})) {
1759
            return [];
1760
        }
1761
1762
        $map = [];
1763
        $rawMap = (array) $raw->{self::MAP_KEY};
1764
1765
        if ([] === $rawMap) {
1766
            self::addRecommendationForDefaultValue($logger, self::MAP_KEY);
1767
        }
1768
1769
        foreach ($rawMap as $item) {
1770
            $processed = [];
1771
1772
            foreach ($item as $match => $replace) {
1773
                $processed[canonicalize(trim($match))] = canonicalize(trim($replace));
1774
            }
1775
1776
            if (isset($processed['_empty_'])) {
1777
                $processed[''] = $processed['_empty_'];
1778
1779
                unset($processed['_empty_']);
1780
            }
1781
1782
            $map[] = $processed;
1783
        }
1784
1785
        return $map;
1786
    }
1787
1788
    /**
1789
     * @return mixed
1790
     */
1791
    private static function retrieveMetadata(stdClass $raw, ConfigurationLogger $logger)
1792
    {
1793
        if (property_exists($raw, self::METADATA_KEY) && null === $raw->{self::METADATA_KEY}) {
1794
            self::addRecommendationForDefaultValue($logger, self::METADATA_KEY);
1795
        }
1796
1797
        if (false === isset($raw->{self::METADATA_KEY})) {
1798
            return null;
1799
        }
1800
1801
        $metadata = $raw->{self::METADATA_KEY};
1802
1803
        return is_object($metadata) ? (array) $metadata : $metadata;
1804
    }
1805
1806
    /**
1807
     * @return string[] The first element is the temporary output path and the second the final one
1808
     */
1809
    private static function retrieveOutputPath(
1810
        stdClass $raw,
1811
        string $basePath,
1812
        ?string $mainScriptPath,
1813
        ConfigurationLogger $logger
1814
    ): array {
1815
        $defaultPath = null;
1816
1817
        if (null !== $mainScriptPath
1818
            && 1 === preg_match('/^(?<main>.*?)(?:\.[\p{L}\d]+)?$/', $mainScriptPath, $matches)
1819
        ) {
1820
            $defaultPath = $matches['main'].'.phar';
1821
        }
1822
1823
        if (isset($raw->{self::OUTPUT_KEY})) {
1824
            $path = self::normalizePath($raw->{self::OUTPUT_KEY}, $basePath);
1825
1826
            if ($path === $defaultPath) {
1827
                self::addRecommendationForDefaultValue($logger, self::OUTPUT_KEY);
1828
            }
1829
        } elseif (null !== $defaultPath) {
1830
            $path = $defaultPath;
1831
        } else {
1832
            // Last resort, should not happen
1833
            $path = self::normalizePath(self::DEFAULT_ALIAS, $basePath);
1834
        }
1835
1836
        $tmp = $real = $path;
1837
1838
        if ('.phar' !== substr($real, -5)) {
1839
            $tmp .= '.phar';
1840
        }
1841
1842
        return [$tmp, $real];
1843
    }
1844
1845
    private static function retrievePrivateKeyPath(
1846
        stdClass $raw,
1847
        string $basePath,
1848
        int $signingAlgorithm,
1849
        ConfigurationLogger $logger
1850
    ): ?string {
1851
        if (property_exists($raw, self::KEY_KEY) && Phar::OPENSSL !== $signingAlgorithm) {
1852
            if (null === $raw->{self::KEY_KEY}) {
1853
                $logger->addRecommendation(
1854
                    'The setting "key" has been set but is unnecessary since the signing algorithm is not "OPENSSL".'
1855
                );
1856
            } else {
1857
                $logger->addWarning(
1858
                    'The setting "key" has been set but is ignored since the signing algorithm is not "OPENSSL".'
1859
                );
1860
            }
1861
1862
            return null;
1863
        }
1864
1865
        if (!isset($raw->{self::KEY_KEY})) {
1866
            Assertion::true(
1867
                Phar::OPENSSL !== $signingAlgorithm,
1868
                'Expected to have a private key for OpenSSL signing but none have been provided.'
1869
            );
1870
1871
            return null;
1872
        }
1873
1874
        $path = self::normalizePath($raw->{self::KEY_KEY}, $basePath);
1875
1876
        Assertion::file($path);
1877
1878
        return $path;
1879
    }
1880
1881
    private static function retrievePrivateKeyPassphrase(
1882
        stdClass $raw,
1883
        int $algorithm,
1884
        ConfigurationLogger $logger
1885
    ): ?string {
1886
        if (false === property_exists($raw, self::KEY_PASS_KEY)) {
1887
            return null;
1888
        }
1889
1890
        /** @var null|false|string $keyPass */
1891
        $keyPass = $raw->{self::KEY_PASS_KEY};
1892
1893
        if (Phar::OPENSSL !== $algorithm) {
1894
            if (false === $keyPass || null === $keyPass) {
1895
                $logger->addRecommendation(
1896
                    sprintf(
1897
                        'The setting "%s" has been set but is unnecessary since the signing algorithm is '
1898
                        .'not "OPENSSL".',
1899
                        self::KEY_PASS_KEY
1900
                    )
1901
                );
1902
            } else {
1903
                $logger->addWarning(
1904
                    sprintf(
1905
                    'The setting "%s" has been set but ignored the signing algorithm is not "OPENSSL".',
1906
                        self::KEY_PASS_KEY
1907
                    )
1908
                );
1909
            }
1910
1911
            return null;
1912
        }
1913
1914
        return is_string($keyPass) ? $keyPass : null;
1915
    }
1916
1917
    /**
1918
     * @return scalar[]
1919
     */
1920
    private static function retrieveReplacements(stdClass $raw, ?string $file, ConfigurationLogger $logger): array
1921
    {
1922
        if (property_exists($raw, self::REPLACEMENTS_KEY) && new stdClass() == $raw->{self::REPLACEMENTS_KEY}) {
1923
            self::addRecommendationForDefaultValue($logger, self::REPLACEMENTS_KEY);
1924
        }
1925
1926
        if (null === $file) {
1927
            return [];
1928
        }
1929
1930
        $replacements = isset($raw->{self::REPLACEMENTS_KEY}) ? (array) $raw->{self::REPLACEMENTS_KEY} : [];
1931
1932
        if (null !== ($git = self::retrievePrettyGitPlaceholder($raw, $logger))) {
1933
            $replacements[$git] = self::retrievePrettyGitTag($file);
1934
        }
1935
1936
        if (null !== ($git = self::retrieveGitHashPlaceholder($raw, $logger))) {
1937
            $replacements[$git] = self::retrieveGitHash($file);
1938
        }
1939
1940
        if (null !== ($git = self::retrieveGitShortHashPlaceholder($raw, $logger))) {
1941
            $replacements[$git] = self::retrieveGitHash($file, true);
1942
        }
1943
1944
        if (null !== ($git = self::retrieveGitTagPlaceholder($raw, $logger))) {
1945
            $replacements[$git] = self::retrieveGitTag($file);
1946
        }
1947
1948
        if (null !== ($git = self::retrieveGitVersionPlaceholder($raw, $logger))) {
1949
            $replacements[$git] = self::retrieveGitVersion($file);
1950
        }
1951
1952
        /**
1953
         * @var string
1954
         * @var bool   $valueSetByUser
1955
         */
1956
        [$datetimeFormat, $valueSetByUser] = self::retrieveDatetimeFormat($raw, $logger);
1957
1958
        if (null !== ($date = self::retrieveDatetimeNowPlaceHolder($raw, $logger))) {
1959
            $replacements[$date] = self::retrieveDatetimeNow($datetimeFormat);
1960
        } elseif ($valueSetByUser) {
1961
            $logger->addRecommendation(
1962
                sprintf(
1963
                    'The setting "%s" has been set but is unnecessary because the setting "%s" is not set.',
1964
                    self::DATETIME_FORMAT_KEY,
1965
                    self::DATETIME_KEY
1966
                )
1967
            );
1968
        }
1969
1970
        $sigil = self::retrieveReplacementSigil($raw, $logger);
1971
1972
        foreach ($replacements as $key => $value) {
1973
            unset($replacements[$key]);
1974
            $replacements[$sigil.$key.$sigil] = $value;
1975
        }
1976
1977
        return $replacements;
1978
    }
1979
1980
    private static function retrievePrettyGitPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
1981
    {
1982
        return self::retrievePlaceholder($raw, $logger, self::GIT_KEY);
1983
    }
1984
1985
    private static function retrieveGitHashPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
1986
    {
1987
        return self::retrievePlaceholder($raw, $logger, self::GIT_COMMIT_KEY);
1988
    }
1989
1990
    /**
1991
     * @param string $file
1992
     * @param bool   $short Use the short version
1993
     *
1994
     * @return string the commit hash
1995
     */
1996
    private static function retrieveGitHash(string $file, bool $short = false): string
1997
    {
1998
        return self::runGitCommand(
1999
            sprintf(
2000
                'git log --pretty="%s" -n1 HEAD',
2001
                $short ? '%h' : '%H'
2002
            ),
2003
            $file
2004
        );
2005
    }
2006
2007
    private static function retrieveGitShortHashPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
2008
    {
2009
        return self::retrievePlaceholder($raw, $logger, self::GIT_COMMIT_SHORT_KEY);
2010
    }
2011
2012
    private static function retrieveGitTagPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
2013
    {
2014
        return self::retrievePlaceholder($raw, $logger, self::GIT_TAG_KEY);
2015
    }
2016
2017
    private static function retrievePlaceholder(stdClass $raw, ConfigurationLogger $logger, string $key): ?string
2018
    {
2019
        if (property_exists($raw, $key) && null === $raw->{$key}) {
2020
            self::addRecommendationForDefaultValue($logger, $key);
2021
        }
2022
2023
        return $raw->{$key} ?? null;
2024
    }
2025
2026
    private static function retrieveGitTag(string $file): string
2027
    {
2028
        return self::runGitCommand('git describe --tags HEAD', $file);
2029
    }
2030
2031
    private static function retrievePrettyGitTag(string $file): string
2032
    {
2033
        $version = self::retrieveGitTag($file);
2034
2035
        if (preg_match('/^(?<tag>.+)-\d+-g(?<hash>[a-f0-9]{7})$/', $version, $matches)) {
2036
            return sprintf('%s@%s', $matches['tag'], $matches['hash']);
2037
        }
2038
2039
        return $version;
2040
    }
2041
2042
    private static function retrieveGitVersionPlaceholder(stdClass $raw, ConfigurationLogger $logger): ?string
2043
    {
2044
        return self::retrievePlaceholder($raw, $logger, self::GIT_VERSION_KEY);
2045
    }
2046
2047
    private static function retrieveGitVersion(string $file): ?string
2048
    {
2049
        try {
2050
            return self::retrieveGitTag($file);
2051
        } catch (RuntimeException $exception) {
2052
            try {
2053
                return self::retrieveGitHash($file, true);
2054
            } catch (RuntimeException $exception) {
2055
                throw new RuntimeException(
2056
                    sprintf(
2057
                        'The tag or commit hash could not be retrieved from "%s": %s',
2058
                        dirname($file),
2059
                        $exception->getMessage()
2060
                    ),
2061
                    0,
2062
                    $exception
2063
                );
2064
            }
2065
        }
2066
    }
2067
2068
    private static function retrieveDatetimeNowPlaceHolder(stdClass $raw, ConfigurationLogger $logger): ?string
2069
    {
2070
        return self::retrievePlaceholder($raw, $logger, self::DATETIME_KEY);
2071
    }
2072
2073
    private static function retrieveDatetimeNow(string $format): string
2074
    {
2075
        $now = new DateTimeImmutable('now', new DateTimeZone('UTC'));
2076
2077
        return $now->format($format);
2078
    }
2079
2080
    private static function retrieveDatetimeFormat(stdClass $raw, ConfigurationLogger $logger): array
2081
    {
2082
        if (isset($raw->{self::DATETIME_FORMAT_KEY})) {
2083
            $format = $raw->{self::DATETIME_FORMAT_KEY};
2084
        } elseif (isset($raw->{self::DATETIME_FORMAT_DEPRECATED_KEY})) {
2085
            @trigger_error(
2086
                'The "datetime_format" is deprecated, use "datetime-format" setting instead.',
2087
                E_USER_DEPRECATED
2088
            );
2089
            $logger->addWarning('The "datetime_format" is deprecated, use "datetime-format" setting instead.');
2090
2091
            $format = $raw->{self::DATETIME_FORMAT_DEPRECATED_KEY};
2092
        } else {
2093
            $format = null;
2094
        }
2095
        /** @var null|string $format */
2096
        if (self::DEFAULT_DATETIME_FORMAT === $format) {
2097
            self::addRecommendationForDefaultValue($logger, self::DATETIME_FORMAT_KEY);
2098
        }
2099
2100
        if (null !== $format) {
2101
            $formattedDate = (new DateTimeImmutable())->format($format);
2102
2103
            Assertion::false(
2104
                false === $formattedDate || $formattedDate === $format,
2105
                sprintf(
2106
                    'Expected the datetime format to be a valid format: "%s" is not',
2107
                    $format
2108
                )
2109
            );
2110
2111
            return [$format, true];
2112
        }
2113
2114
        return [self::DEFAULT_DATETIME_FORMAT, false];
2115
    }
2116
2117
    private static function retrieveReplacementSigil(stdClass $raw, ConfigurationLogger $logger): string
2118
    {
2119
        return self::retrievePlaceholder($raw, $logger, self::REPLACEMENT_SIGIL_KEY) ?? self::DEFAULT_REPLACEMENT_SIGIL;
2120
    }
2121
2122
    private static function retrieveShebang(stdClass $raw, ConfigurationLogger $logger): ?string
2123
    {
2124
        if (false === array_key_exists(self::SHEBANG_KEY, (array) $raw)) {
2125
            return self::DEFAULT_SHEBANG;
2126
        }
2127
2128
        $shebang = $raw->{self::SHEBANG_KEY};
2129
2130
        if (false === $shebang) {
2131
            return null;
2132
        }
2133
2134
        if (null === $shebang) {
2135
            self::addRecommendationForDefaultValue($logger, self::SHEBANG_KEY);
2136
2137
            $shebang = self::DEFAULT_SHEBANG;
2138
        }
2139
2140
        Assertion::string($shebang, 'Expected shebang to be either a string, false or null, found true');
2141
2142
        $shebang = trim($shebang);
2143
2144
        Assertion::notEmpty($shebang, 'The shebang should not be empty.');
2145
        Assertion::true(
2146
            '#!' === substr($shebang, 0, 2),
2147
            sprintf(
2148
                'The shebang line must start with "#!". Got "%s" instead',
2149
                $shebang
2150
            )
2151
        );
2152
2153
        if (self::DEFAULT_SHEBANG === $shebang) {
2154
            self::addRecommendationForDefaultValue($logger, self::SHEBANG_KEY);
2155
        }
2156
2157
        return $shebang;
2158
    }
2159
2160
    private static function retrieveSigningAlgorithm(stdClass $raw, ConfigurationLogger $logger): int
2161
    {
2162
        if (property_exists($raw, self::ALGORITHM_KEY) && null === $raw->{self::ALGORITHM_KEY}) {
2163
            self::addRecommendationForDefaultValue($logger, self::ALGORITHM_KEY);
2164
        }
2165
2166
        if (false === isset($raw->{self::ALGORITHM_KEY})) {
2167
            return self::DEFAULT_SIGNING_ALGORITHM;
2168
        }
2169
2170
        $algorithm = strtoupper($raw->{self::ALGORITHM_KEY});
2171
2172
        Assertion::inArray($algorithm, array_keys(get_phar_signing_algorithms()));
2173
2174
        Assertion::true(
2175
            defined('Phar::'.$algorithm),
2176
            sprintf(
2177
                'The signing algorithm "%s" is not supported by your current PHAR version.',
2178
                $algorithm
2179
            )
2180
        );
2181
2182
        $algorithm = constant('Phar::'.$algorithm);
2183
2184
        if (self::DEFAULT_SIGNING_ALGORITHM === $algorithm) {
2185
            self::addRecommendationForDefaultValue($logger, self::ALGORITHM_KEY);
2186
        }
2187
2188
        return $algorithm;
2189
    }
2190
2191
    private static function retrieveStubBannerContents(stdClass $raw, ConfigurationLogger $logger): ?string
2192
    {
2193
        if (property_exists($raw, self::BANNER_KEY) && null === $raw->{self::BANNER_KEY}) {
2194
            self::addRecommendationForDefaultValue($logger, self::BANNER_KEY);
2195
        }
2196
2197
        if (false === isset($raw->{self::BANNER_KEY})) {
2198
            return self::DEFAULT_BANNER;
2199
        }
2200
2201
        $banner = $raw->{self::BANNER_KEY};
2202
2203
        if (self::DEFAULT_BANNER === $banner) {
2204
            self::addRecommendationForDefaultValue($logger, self::BANNER_KEY);
2205
        }
2206
2207
        if (false === $banner) {
2208
            return null;
2209
        }
2210
2211
        Assertion::true(is_string($banner) || is_array($banner), 'The banner cannot accept true as a value');
2212
2213
        if (is_array($banner)) {
2214
            $banner = implode("\n", $banner);
2215
        }
2216
2217
        return $banner;
2218
    }
2219
2220
    private static function retrieveStubBannerPath(stdClass $raw, string $basePath, ConfigurationLogger $logger): ?string
2221
    {
2222
        if (property_exists($raw, self::BANNER_FILE_KEY) && null === $raw->{self::BANNER_FILE_KEY}) {
2223
            self::addRecommendationForDefaultValue($logger, self::BANNER_FILE_KEY);
2224
        }
2225
2226
        if (false === isset($raw->{self::BANNER_FILE_KEY})) {
2227
            return null;
2228
        }
2229
2230
        $bannerFile = make_path_absolute($raw->{self::BANNER_FILE_KEY}, $basePath);
2231
2232
        Assertion::file($bannerFile);
2233
2234
        return $bannerFile;
2235
    }
2236
2237
    private static function normalizeStubBannerContents(?string $contents): ?string
2238
    {
2239
        if (null === $contents) {
2240
            return null;
2241
        }
2242
2243
        $banner = explode("\n", $contents);
2244
        $banner = array_map('trim', $banner);
2245
2246
        return implode("\n", $banner);
2247
    }
2248
2249
    private static function retrieveStubPath(stdClass $raw, string $basePath, ConfigurationLogger $logger): ?string
2250
    {
2251
        if (property_exists($raw, self::STUB_KEY) && null === $raw->{self::STUB_KEY}) {
2252
            self::addRecommendationForDefaultValue($logger, self::STUB_KEY);
2253
        }
2254
2255
        if (isset($raw->{self::STUB_KEY}) && is_string($raw->{self::STUB_KEY})) {
2256
            $stubPath = make_path_absolute($raw->{self::STUB_KEY}, $basePath);
2257
2258
            Assertion::file($stubPath);
2259
2260
            return $stubPath;
2261
        }
2262
2263
        return null;
2264
    }
2265
2266
    private static function retrieveInterceptsFileFuncs(stdClass $raw, ConfigurationLogger $logger): bool
2267
    {
2268
        if (property_exists($raw, self::INTERCEPT_KEY)
2269
            && (null === $raw->{self::INTERCEPT_KEY} || false === $raw->{self::INTERCEPT_KEY})
2270
        ) {
2271
            self::addRecommendationForDefaultValue($logger, self::INTERCEPT_KEY);
2272
        }
2273
2274
        return $raw->{self::INTERCEPT_KEY} ?? false;
2275
    }
2276
2277
    private static function retrievePromptForPrivateKey(
2278
        stdClass $raw,
2279
        int $signingAlgorithm,
2280
        ConfigurationLogger $logger
2281
    ): bool {
2282
        if (isset($raw->{self::KEY_PASS_KEY}) && true === $raw->{self::KEY_PASS_KEY}) {
2283
            if (Phar::OPENSSL !== $signingAlgorithm) {
2284
                $logger->addWarning(
2285
                    'A prompt for password for the private key has been requested but ignored since the signing '
2286
                    .'algorithm used is not "OPENSSL.'
2287
                );
2288
2289
                return false;
2290
            }
2291
2292
            return true;
2293
        }
2294
2295
        return false;
2296
    }
2297
2298
    private static function retrieveIsStubGenerated(stdClass $raw, ?string $stubPath): bool
2299
    {
2300
        return null === $stubPath && (false === isset($raw->{self::STUB_KEY}) || false !== $raw->{self::STUB_KEY});
2301
    }
2302
2303
    private static function retrieveCheckRequirements(
2304
        stdClass $raw,
2305
        bool $hasComposerJson,
2306
        bool $hasComposerLock,
2307
        ConfigurationLogger $logger
2308
    ): bool {
2309
        if (property_exists($raw, self::CHECK_REQUIREMENTS_KEY)
2310
            && (null === $raw->{self::CHECK_REQUIREMENTS_KEY} || true === $raw->{self::CHECK_REQUIREMENTS_KEY})
2311
        ) {
2312
            self::addRecommendationForDefaultValue($logger, self::CHECK_REQUIREMENTS_KEY);
2313
        }
2314
2315
        if (false === property_exists($raw, self::CHECK_REQUIREMENTS_KEY)) {
2316
            return $hasComposerJson || $hasComposerLock;
2317
        }
2318
2319
        /** @var null|bool $checkRequirements */
2320
        $checkRequirements = $raw->{self::CHECK_REQUIREMENTS_KEY} ?? true;
2321
2322
        if ($checkRequirements && false === $hasComposerJson && false === $hasComposerLock) {
2323
            $logger->addWarning(
2324
                'The requirement checker could not be used because the composer.json and composer.lock file could not '
2325
                .'be found.'
2326
            );
2327
2328
            return false;
2329
        }
2330
2331
        return $checkRequirements;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $checkRequirements could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
2332
    }
2333
2334
    private static function retrievePhpScoperConfig(stdClass $raw, string $basePath, ConfigurationLogger $logger): PhpScoperConfiguration
2335
    {
2336
        // TODO: add recommendations regarding the order
2337
        if (property_exists($raw, self::PHP_SCOPER_KEY)
2338
            && (null === $raw->{self::PHP_SCOPER_KEY} || self::PHP_SCOPER_CONFIG === $raw->{self::PHP_SCOPER_KEY})
2339
        ) {
2340
            self::addRecommendationForDefaultValue($logger, self::PHP_SCOPER_KEY);
2341
        }
2342
2343
        if (!isset($raw->{self::PHP_SCOPER_KEY})) {
2344
            $configFilePath = make_path_absolute(self::PHP_SCOPER_CONFIG, $basePath);
2345
2346
            return file_exists($configFilePath)
2347
                ? PhpScoperConfiguration::load($configFilePath)
2348
                : PhpScoperConfiguration::load()
2349
             ;
2350
        }
2351
2352
        $configFile = $raw->{self::PHP_SCOPER_KEY};
2353
2354
        Assertion::string($configFile);
2355
2356
        $configFilePath = make_path_absolute($configFile, $basePath);
2357
2358
        Assertion::file($configFilePath);
2359
        Assertion::readable($configFilePath);
2360
2361
        return PhpScoperConfiguration::load($configFilePath);
2362
    }
2363
2364
    /**
2365
     * Runs a Git command on the repository.
2366
     *
2367
     * @param string $command the command
2368
     *
2369
     * @return string the trimmed output from the command
2370
     */
2371
    private static function runGitCommand(string $command, string $file): string
2372
    {
2373
        $path = dirname($file);
2374
2375
        $process = new Process($command, $path);
2376
2377
        if (0 === $process->run()) {
2378
            return trim($process->getOutput());
2379
        }
2380
2381
        throw new RuntimeException(
2382
            sprintf(
2383
                'The tag or commit hash could not be retrieved from "%s": %s',
2384
                $path,
2385
                $process->getErrorOutput()
2386
            )
2387
        );
2388
    }
2389
2390
    private static function createPhpCompactor(stdClass $raw): Compactor
2391
    {
2392
        // TODO: false === not set; check & add test/doc
2393
        $tokenizer = new Tokenizer();
2394
2395
        if (false === empty($raw->{self::ANNOTATIONS_KEY}) && isset($raw->{self::ANNOTATIONS_KEY}->ignore)) {
2396
            $tokenizer->ignore(
2397
                (array) $raw->{self::ANNOTATIONS_KEY}->ignore
2398
            );
2399
        }
2400
2401
        return new Php($tokenizer);
2402
    }
2403
2404
    private static function addRecommendationForDefaultValue(ConfigurationLogger $logger, string $key): void
2405
    {
2406
        $logger->addRecommendation(
2407
            sprintf(
2408
                'The "%s" setting can be omitted since is set to its default value',
2409
                $key
2410
            )
2411
        );
2412
    }
2413
}
2414