Passed
Push — master ( ddbdf0...1a517e )
by Théo
02:39
created

Configuration::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 60
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 60
rs 9.5555
c 0
b 0
f 0
cc 1
eloc 30
nc 1
nop 25

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 Herrera\Annotations\Tokenizer;
21
use Herrera\Box\Compactor\Php as LegacyPhp;
22
use InvalidArgumentException;
23
use KevinGH\Box\Compactor\Php;
24
use KevinGH\Box\Composer\ComposerConfiguration;
25
use Phar;
26
use RuntimeException;
27
use SplFileInfo;
28
use stdClass;
29
use Symfony\Component\Finder\Finder;
30
use Symfony\Component\Process\Process;
31
use function iter\chain;
32
use function KevinGH\Box\FileSystem\canonicalize;
33
use function KevinGH\Box\FileSystem\file_contents;
34
use function KevinGH\Box\FileSystem\longest_common_base_path;
35
use function KevinGH\Box\FileSystem\make_path_absolute;
36
use function KevinGH\Box\FileSystem\make_path_relative;
37
38
final class Configuration
39
{
40
    private const DEFAULT_ALIAS = 'default.phar';
41
    private const DEFAULT_MAIN_SCRIPT = 'index.php';
42
    private const DEFAULT_DATETIME_FORMAT = 'Y-m-d H:i:s';
43
    private const DEFAULT_REPLACEMENT_SIGIL = '@';
44
    private const DEFAULT_SHEBANG = '#!/usr/bin/env php';
45
    private const DEFAULT_BANNER = <<<'BANNER'
46
Generated by Humbug Box.
47
48
@link https://github.com/humbug/box
49
BANNER;
50
    private const FILES_SETTINGS = [
51
        'files',
52
        'files-bin',
53
        'directories',
54
        'directories-bin',
55
        'finder',
56
        'finder-bin',
57
    ];
58
59
    private $fileMode;
60
    private $alias;
61
    private $basePath;
62
    private $files;
63
    private $binaryFiles;
64
    private $bootstrapFile;
65
    private $compactors;
66
    private $compressionAlgorithm;
67
    private $mainScriptPath;
68
    private $mainScriptContents;
69
    private $map;
70
    private $fileMapper;
71
    private $metadata;
72
    private $outputPath;
73
    private $privateKeyPassphrase;
74
    private $privateKeyPath;
75
    private $isPrivateKeyPrompt;
76
    private $processedReplacements;
77
    private $shebang;
78
    private $signingAlgorithm;
79
    private $stubBannerContents;
80
    private $stubBannerPath;
81
    private $stubPath;
82
    private $isInterceptFileFuncs;
83
    private $isStubGenerated;
84
85
    /**
86
     * @param null|string   $file
87
     * @param null|string   $alias
88
     * @param string        $basePath              Utility to private the base path used and be able to retrieve a path relative to it (the base path)
89
     * @param SplFileInfo[] $files                 List of files
90
     * @param SplFileInfo[] $binaryFiles           List of binary files
91
     * @param null|string   $bootstrapFile         The bootstrap file path
92
     * @param Compactor[]   $compactors            List of file contents compactors
93
     * @param null|int      $compressionAlgorithm  Compression algorithm constant value. See the \Phar class constants
94
     * @param null|int      $fileMode              File mode in octal form
95
     * @param string        $mainScriptPath        The main script file path
96
     * @param string        $mainScriptContents    The processed content of the main script file
97
     * @param MapFile       $fileMapper            Utility to map the files from outside and inside the PHAR
98
     * @param mixed         $metadata              The PHAR Metadata
99
     * @param string        $outputPath
100
     * @param null|string   $privateKeyPassphrase
101
     * @param null|string   $privateKeyPath
102
     * @param bool          $isPrivateKeyPrompt    If the user should be prompted for the private key passphrase
103
     * @param array         $processedReplacements The processed list of replacement placeholders and their values
104
     * @param null|string   $shebang               The shebang line
105
     * @param int           $signingAlgorithm      The PHAR siging algorithm. See \Phar constants
106
     * @param null|string   $stubBannerContents    The stub banner comment
107
     * @param null|string   $stubBannerPath        The path to the stub banner comment file
108
     * @param null|string   $stubPath              The PHAR stub file path
109
     * @param bool          $isInterceptFileFuncs  wether or not Phar::interceptFileFuncs() should be used
110
     * @param bool          $isStubGenerated       Wether or not if the PHAR stub should be generated
111
     */
112
    private function __construct(
113
        ?string $file,
114
        ?string $alias,
115
        string $basePath,
116
        array $files,
117
        array $binaryFiles,
118
        ?string $bootstrapFile,
119
        array $compactors,
120
        ?int $compressionAlgorithm,
121
        ?int $fileMode,
122
        string $mainScriptPath,
123
        string $mainScriptContents,
124
        MapFile $fileMapper,
125
        $metadata,
126
        string $outputPath,
127
        ?string $privateKeyPassphrase,
128
        ?string $privateKeyPath,
129
        bool $isPrivateKeyPrompt,
130
        array $processedReplacements,
131
        ?string $shebang,
132
        int $signingAlgorithm,
133
        ?string $stubBannerContents,
134
        ?string $stubBannerPath,
135
        ?string $stubPath,
136
        bool $isInterceptFileFuncs,
137
        bool $isStubGenerated
138
    ) {
139
        Assertion::nullOrInArray(
140
            $compressionAlgorithm,
141
            get_phar_compression_algorithms(),
142
            sprintf(
143
                'Invalid compression algorithm "%%s", use one of "%s" instead.',
144
                implode('", "', array_keys(get_phar_compression_algorithms()))
145
            )
146
        );
147
148
        $this->alias = $alias;
149
        $this->basePath = $basePath;
150
        $this->files = $files;
151
        $this->binaryFiles = $binaryFiles;
152
        $this->bootstrapFile = $bootstrapFile;
153
        $this->compactors = $compactors;
154
        $this->compressionAlgorithm = $compressionAlgorithm;
155
        $this->fileMode = $fileMode;
156
        $this->mainScriptPath = $mainScriptPath;
157
        $this->mainScriptContents = $mainScriptContents;
158
        $this->fileMapper = $fileMapper;
159
        $this->metadata = $metadata;
160
        $this->outputPath = $outputPath;
161
        $this->privateKeyPassphrase = $privateKeyPassphrase;
162
        $this->privateKeyPath = $privateKeyPath;
163
        $this->isPrivateKeyPrompt = $isPrivateKeyPrompt;
164
        $this->processedReplacements = $processedReplacements;
165
        $this->shebang = $shebang;
166
        $this->signingAlgorithm = $signingAlgorithm;
167
        $this->stubBannerContents = $stubBannerContents;
168
        $this->stubBannerPath = $stubBannerPath;
169
        $this->stubPath = $stubPath;
170
        $this->isInterceptFileFuncs = $isInterceptFileFuncs;
171
        $this->isStubGenerated = $isStubGenerated;
172
    }
173
174
    public static function create(?string $file, stdClass $raw): self
175
    {
176
        $alias = self::retrieveAlias($raw);
177
178
        $basePath = self::retrieveBasePath($file, $raw);
179
180
        $mainScriptPath = self::retrieveMainScriptPath($raw, $basePath);
181
        $mainScriptContents = self::retrieveMainScriptContents($mainScriptPath);
182
183
        $devPackages = ComposerConfiguration::retrieveDevPackages($basePath);
184
185
        $blacklistFilter = self::retrieveBlacklistFilter($raw, $basePath);
186
187
        if (self::shouldRetrieveAllFiles($file, $raw)) {
188
            $filesAggregate = self::retrieveAllFiles($basePath, $mainScriptPath, $blacklistFilter, $devPackages);
189
            $binaryFilesAggregate = [];
190
        } else {
191
            $files = self::retrieveFiles($raw, 'files', $basePath);
192
            $directories = self::retrieveDirectories($raw, 'directories', $basePath, $blacklistFilter);
193
            $filesFromFinders = self::retrieveFilesFromFinders($raw, 'finder', $basePath, $blacklistFilter, $devPackages);
194
195
            $filesAggregate = array_unique(iterator_to_array(chain($files, $directories, ...$filesFromFinders)));
196
197
            $binaryFiles = self::retrieveFiles($raw, 'files-bin', $basePath);
198
            $binaryDirectories = self::retrieveDirectories($raw, 'directories-bin', $basePath, $blacklistFilter);
199
            $binaryFilesFromFinders = self::retrieveFilesFromFinders($raw, 'finder-bin', $basePath, $blacklistFilter, $devPackages);
200
201
            $binaryFilesAggregate = array_unique(iterator_to_array(chain($binaryFiles, $binaryDirectories, ...$binaryFilesFromFinders)));
202
        }
203
204
        $bootstrapFile = self::retrieveBootstrapFile($raw, $basePath);
205
206
        $compactors = self::retrieveCompactors($raw);
207
        $compressionAlgorithm = self::retrieveCompressionAlgorithm($raw);
208
209
        $fileMode = self::retrieveFileMode($raw);
210
211
        $map = self::retrieveMap($raw);
212
        $fileMapper = new MapFile($map);
213
214
        $metadata = self::retrieveMetadata($raw);
215
216
        $outputPath = self::retrieveOutputPath($raw, $basePath);
217
218
        $privateKeyPassphrase = self::retrievePrivateKeyPassphrase($raw);
219
        $privateKeyPath = self::retrievePrivateKeyPath($raw);
220
        $isPrivateKeyPrompt = self::retrieveIsPrivateKeyPrompt($raw);
221
222
        $replacements = self::retrieveReplacements($raw);
223
        $processedReplacements = self::retrieveProcessedReplacements($replacements, $raw, $file);
224
225
        $shebang = self::retrieveShebang($raw);
226
227
        $signingAlgorithm = self::retrieveSigningAlgorithm($raw);
228
229
        $stubBannerContents = self::retrieveStubBannerContents($raw);
230
        $stubBannerPath = self::retrieveStubBannerPath($raw, $basePath);
231
232
        if (null !== $stubBannerPath) {
233
            $stubBannerContents = file_contents($stubBannerPath);
234
        }
235
236
        $stubBannerContents = self::normalizeStubBannerContents($stubBannerContents);
237
238
        $stubPath = self::retrieveStubPath($raw, $basePath);
239
240
        $isInterceptFileFuncs = self::retrieveIsInterceptFileFuncs($raw);
241
        $isStubGenerated = self::retrieveIsStubGenerated($raw);
242
243
        return new self(
244
            $file,
245
            $alias,
246
            $basePath,
247
            $filesAggregate,
248
            $binaryFilesAggregate,
249
            $bootstrapFile,
250
            $compactors,
251
            $compressionAlgorithm,
252
            $fileMode,
253
            $mainScriptPath,
254
            $mainScriptContents,
255
            $fileMapper,
256
            $metadata,
257
            $outputPath,
258
            $privateKeyPassphrase,
259
            $privateKeyPath,
260
            $isPrivateKeyPrompt,
261
            $processedReplacements,
262
            $shebang,
263
            $signingAlgorithm,
264
            $stubBannerContents,
265
            $stubBannerPath,
266
            $stubPath,
267
            $isInterceptFileFuncs,
268
            $isStubGenerated
269
        );
270
    }
271
272
    public function getAlias(): ?string
273
    {
274
        return $this->alias;
275
    }
276
277
    public function getBasePath(): string
278
    {
279
        return $this->basePath;
280
    }
281
282
    /**
283
     * @return string[]
284
     */
285
    public function getFiles(): array
286
    {
287
        return $this->files;
288
    }
289
290
    /**
291
     * @return string[]
292
     */
293
    public function getBinaryFiles(): array
294
    {
295
        return $this->binaryFiles;
296
    }
297
298
    public function getBootstrapFile(): ?string
299
    {
300
        return $this->bootstrapFile;
301
    }
302
303
    public function loadBootstrap(): void
304
    {
305
        $file = $this->bootstrapFile;
306
307
        if (null !== $file) {
308
            include $file;
309
        }
310
    }
311
312
    /**
313
     * @return Compactor[] the list of compactors
314
     */
315
    public function getCompactors(): array
316
    {
317
        return $this->compactors;
318
    }
319
320
    public function getCompressionAlgorithm(): ?int
321
    {
322
        return $this->compressionAlgorithm;
323
    }
324
325
    public function getFileMode(): ?int
326
    {
327
        return $this->fileMode;
328
    }
329
330
    public function getMainScriptPath(): string
331
    {
332
        return $this->mainScriptPath;
333
    }
334
335
    public function getMainScriptContents(): string
336
    {
337
        return $this->mainScriptContents;
338
    }
339
340
    public function getOutputPath(): string
341
    {
342
        return $this->outputPath;
343
    }
344
345
    /**
346
     * @return string[]
347
     */
348
    public function getMap(): array
349
    {
350
        return $this->fileMapper->getMap();
351
    }
352
353
    public function getFileMapper(): MapFile
354
    {
355
        return $this->fileMapper;
356
    }
357
358
    /**
359
     * @return mixed
360
     */
361
    public function getMetadata()
362
    {
363
        return $this->metadata;
364
    }
365
366
    public function getPrivateKeyPassphrase(): ?string
367
    {
368
        return $this->privateKeyPassphrase;
369
    }
370
371
    public function getPrivateKeyPath(): ?string
372
    {
373
        return $this->privateKeyPath;
374
    }
375
376
    public function isPrivateKeyPrompt(): bool
377
    {
378
        return $this->isPrivateKeyPrompt;
379
    }
380
381
    public function getProcessedReplacements(): array
382
    {
383
        return $this->processedReplacements;
384
    }
385
386
    public function getShebang(): ?string
387
    {
388
        return $this->shebang;
389
    }
390
391
    public function getSigningAlgorithm(): int
392
    {
393
        return $this->signingAlgorithm;
394
    }
395
396
    public function getStubBannerContents(): ?string
397
    {
398
        return $this->stubBannerContents;
399
    }
400
401
    public function getStubBannerPath(): ?string
402
    {
403
        return $this->stubBannerPath;
404
    }
405
406
    public function getStubPath(): ?string
407
    {
408
        return $this->stubPath;
409
    }
410
411
    public function isInterceptFileFuncs(): bool
412
    {
413
        return $this->isInterceptFileFuncs;
414
    }
415
416
    public function isStubGenerated(): bool
417
    {
418
        return $this->isStubGenerated;
419
    }
420
421
    private static function retrieveAlias(stdClass $raw): ?string
422
    {
423
        if (false === isset($raw->alias)) {
424
            return null;
425
        }
426
427
        $alias = trim($raw->alias);
428
429
        Assertion::notEmpty($alias, 'A PHAR alias cannot be empty when provided.');
430
431
        return $alias;
432
    }
433
434
    private static function retrieveBasePath(?string $file, stdClass $raw): string
435
    {
436
        if (null === $file) {
437
            return getcwd();
438
        }
439
440
        if (false === isset($raw->{'base-path'})) {
441
            return realpath(dirname($file));
442
        }
443
444
        $basePath = trim($raw->{'base-path'});
445
446
        Assertion::directory(
447
            $basePath,
448
            'The base path "%s" is not a directory or does not exist.'
449
        );
450
451
        return realpath($basePath);
452
    }
453
454
    private static function shouldRetrieveAllFiles(?string $file, stdClass $raw): bool
455
    {
456
        if (null === $file) {
457
            return true;
458
        }
459
460
        // TODO: config should be casted into an array: it is easier to do and we need an array in several places now
461
        $rawConfig = (array) $raw;
462
463
        foreach (self::FILES_SETTINGS as $key) {
464
            if (array_key_exists($key, $rawConfig)) {
465
                return false;
466
            }
467
        }
468
469
        return true;
470
    }
471
472
    /**
473
     * @param stdClass $raw
474
     * @param string   $basePath
475
     *
476
     * @return Closure
477
     */
478
    private static function retrieveBlacklistFilter(stdClass $raw, string $basePath): Closure
479
    {
480
        $blacklist = self::retrieveBlacklist($raw, $basePath);
481
482
        return function (SplFileInfo $file) use ($blacklist): ?bool {
483
            if (in_array($file->getRealPath(), $blacklist, true)) {
484
                return false;
485
            }
486
487
            return null;
488
        };
489
    }
490
491
    /**
492
     * @param stdClass $raw
493
     * @param string   $basePath
494
     *
495
     * @return string[]
496
     */
497
    private static function retrieveBlacklist(stdClass $raw, string $basePath): array
498
    {
499
        if (false === isset($raw->blacklist)) {
500
            return [];
501
        }
502
503
        $blacklist = $raw->blacklist;
504
505
        $normalizePath = function ($file) use ($basePath): string {
506
            return self::normalizeFilePath($file, $basePath);
507
        };
508
509
        return array_unique(array_map($normalizePath, $blacklist));
510
    }
511
512
    /**
513
     * @param stdClass $raw
514
     * @param string   $key      Config property name
515
     * @param string   $basePath
516
     *
517
     * @return SplFileInfo[]
518
     */
519
    private static function retrieveFiles(stdClass $raw, string $key, string $basePath): array
520
    {
521
        if (false === isset($raw->{$key})) {
522
            return [];
523
        }
524
525
        $files = (array) $raw->{$key};
526
527
        Assertion::allString($files);
528
529
        $normalizePath = function (string $file) use ($basePath, $key): SplFileInfo {
530
            $file = self::normalizeFilePath($file, $basePath);
531
532
            if (is_link($file)) {
533
                // TODO: add this to baberlei/assert
534
                throw new InvalidArgumentException(
535
                    sprintf(
536
                        'Cannot add the link "%s": links are not supported.',
537
                        $file
538
                    )
539
                );
540
            }
541
542
            Assertion::file(
543
                $file,
544
                sprintf(
545
                    '"%s" must contain a list of existing files. Could not find "%%s".',
546
                    $key
547
                )
548
            );
549
550
            return new SplFileInfo($file);
551
        };
552
553
        return array_map($normalizePath, $files);
554
    }
555
556
    /**
557
     * @param stdClass $raw
558
     * @param string   $key             Config property name
559
     * @param string   $basePath
560
     * @param Closure  $blacklistFilter
561
     *
562
     * @return iterable|SplFileInfo[]
563
     */
564
    private static function retrieveDirectories(
565
        stdClass $raw,
566
        string $key,
567
        string $basePath,
568
        Closure $blacklistFilter
569
    ): iterable {
570
        $directories = self::retrieveDirectoryPaths($raw, $key, $basePath);
571
572
        if ([] !== $directories) {
573
            return Finder::create()
1 ignored issue
show
Bug Best Practice introduced by
The expression return Symfony\Component...true)->in($directories) returns the type Symfony\Component\Finder\Finder which is incompatible with the documented return type iterable|SplFileInfo[].
Loading history...
574
                ->files()
575
                ->filter($blacklistFilter)
576
                ->ignoreVCS(true)
577
                ->in($directories)
578
            ;
579
        }
580
581
        return [];
582
    }
583
584
    /**
585
     * @param stdClass $raw
586
     * @param string   $basePath
587
     * @param Closure  $blacklistFilter
588
     * @param string[] $devPackages
589
     *
590
     * @return iterable[]|SplFileInfo[][]
591
     */
592
    private static function retrieveFilesFromFinders(
593
        stdClass $raw,
594
        string $key,
595
        string $basePath,
596
        Closure $blacklistFilter,
597
        array $devPackages
598
    ): array {
599
        if (isset($raw->{$key})) {
600
            return self::processFinders($raw->{$key}, $basePath, $blacklistFilter, $devPackages);
601
        }
602
603
        return [];
604
    }
605
606
    /**
607
     * @param array    $findersConfig
608
     * @param string   $basePath
609
     * @param Closure  $blacklistFilter
610
     * @param string[] $devPackages
611
     *
612
     * @return Finder[]|SplFileInfo[][]
613
     */
614
    private static function processFinders(
615
        array $findersConfig,
616
        string $basePath,
617
        Closure $blacklistFilter,
618
        array $devPackages
619
    ): array {
620
        $processFinderConfig = function (stdClass $config) use ($basePath, $blacklistFilter, $devPackages) {
621
            return self::processFinder($config, $basePath, $blacklistFilter, $devPackages);
622
        };
623
624
        return array_map($processFinderConfig, $findersConfig);
625
    }
626
627
    /**
628
     * @param stdClass $config
629
     * @param string   $basePath
630
     * @param Closure  $blacklistFilter
631
     * @param string[] $devPackages
632
     *
633
     * @return Finder|SplFileInfo[]
634
     */
635
    private static function processFinder(stdClass $config, string $basePath, Closure $blacklistFilter, array $devPackages): Finder
636
    {
637
        $finder = Finder::create()
638
            ->files()
639
            ->filter($blacklistFilter)
640
            ->filter(
641
                function (SplFileInfo $fileInfo) use ($devPackages): bool {
642
                    foreach ($devPackages as $devPackage) {
643
                        if ($devPackage === longest_common_base_path([$devPackage, $fileInfo->getRealPath()])) {
644
                            // File belongs to the dev package
645
                            return false;
646
                        }
647
                    }
648
649
                    return true;
650
                }
651
            )
652
            ->ignoreVCS(true)
653
        ;
654
655
        $normalizedConfig = (function (array $config, Finder $finder): array {
656
            $normalizedConfig = [];
657
658
            foreach ($config as $method => $arguments) {
659
                $method = trim($method);
660
                $arguments = (array) $arguments;
661
662
                Assertion::methodExists(
663
                    $method,
664
                    $finder,
665
                    'The method "Finder::%s" does not exist.'
666
                );
667
668
                $normalizedConfig[$method] = $arguments;
669
            }
670
671
            krsort($normalizedConfig);
672
673
            return $normalizedConfig;
674
        })((array) $config, $finder);
675
676
        $createNormalizedDirectories = function (string $directory) use ($basePath): ?string {
677
            $directory = self::normalizeDirectoryPath($directory, $basePath);
678
679
            if (is_link($directory)) {
680
                // TODO: add this to baberlei/assert
681
                throw new InvalidArgumentException(
682
                    sprintf(
683
                        'Cannot append the link "%s" to the Finder: links are not supported.',
684
                        $directory
685
                    )
686
                );
687
            }
688
689
            Assertion::directory($directory);
690
691
            return $directory;
692
        };
693
694
        $normalizeFileOrDirectory = function (string &$fileOrDirectory) use ($basePath): void {
695
            $fileOrDirectory = self::normalizeDirectoryPath($fileOrDirectory, $basePath);
696
697
            if (is_link($fileOrDirectory)) {
698
                // TODO: add this to baberlei/assert
699
                throw new InvalidArgumentException(
700
                    sprintf(
701
                        'Cannot append the link "%s" to the Finder: links are not supported.',
702
                        $fileOrDirectory
703
                    )
704
                );
705
            }
706
707
            // TODO: add this to baberlei/assert
708
            if (false === file_exists($fileOrDirectory)) {
709
                throw new InvalidArgumentException(
710
                    sprintf(
711
                        'Path "%s" was expected to be a file or directory. It may be a symlink (which are unsupported).',
712
                        $fileOrDirectory
713
                    )
714
                );
715
            }
716
717
            //TODO: add fileExists (as file or directory) to Assert
718
            if (false === is_file($fileOrDirectory)) {
719
                Assertion::directory($fileOrDirectory);
720
            } else {
721
                Assertion::file($fileOrDirectory);
722
            }
723
        };
724
725
        foreach ($normalizedConfig as $method => $arguments) {
726
            if ('in' === $method) {
727
                $normalizedConfig[$method] = $arguments = array_map($createNormalizedDirectories, $arguments);
728
            }
729
730
            if ('exclude' === $method) {
731
                $arguments = array_unique(array_map('trim', $arguments));
732
            }
733
734
            if ('append' === $method) {
735
                array_walk($arguments, $normalizeFileOrDirectory);
736
737
                $arguments = [$arguments];
738
            }
739
740
            foreach ($arguments as $argument) {
741
                $finder->$method($argument);
742
            }
743
        }
744
745
        return $finder;
746
    }
747
748
    /**
749
     * @param string   $basePath
750
     * @param string   $mainScriptPath
751
     * @param Closure  $blacklistFilter
752
     * @param string[] $devPackages
753
     *
754
     * @return SplFileInfo[]
755
     */
756
    private static function retrieveAllFiles(
757
        string $basePath,
758
        string $mainScriptPath,
759
        Closure $blacklistFilter,
760
        array $devPackages
761
    ): array {
762
        $relativeDevPackages = array_map(
763
            function (string $packagePath) use ($basePath): string {
764
                return make_path_relative($packagePath, $basePath);
765
            },
766
            $devPackages
767
        );
768
769
        $finder = Finder::create()
770
            ->files()
771
            ->in($basePath)
772
            ->notPath(make_path_relative($mainScriptPath, $basePath))
773
            ->filter($blacklistFilter)
774
            ->exclude($relativeDevPackages)
775
            ->ignoreVCS(true)
776
        ;
777
778
        return array_filter(
779
            array_unique(
780
                array_map(
781
                    function (SplFileInfo $fileInfo): ?string {
782
                        if (is_link((string) $fileInfo)) {
783
                            return null;
784
                        }
785
786
                        return false !== $fileInfo->getRealPath() ? $fileInfo->getRealPath() : null;
787
                    },
788
                    iterator_to_array(
789
                        $finder
790
                    )
791
                )
792
            )
793
        );
794
    }
795
796
    /**
797
     * @param stdClass $raw
798
     * @param string   $key      Config property name
799
     * @param string   $basePath
800
     *
801
     * @return string[]
802
     */
803
    private static function retrieveDirectoryPaths(stdClass $raw, string $key, string $basePath): array
804
    {
805
        if (false === isset($raw->{$key})) {
806
            return [];
807
        }
808
809
        $directories = $raw->{$key};
810
811
        $normalizeDirectory = function (string $directory) use ($basePath, $key): string {
812
            $directory = self::normalizeDirectoryPath($directory, $basePath);
813
814
            if (is_link($directory)) {
815
                // TODO: add this to baberlei/assert
816
                throw new InvalidArgumentException(
817
                    sprintf(
818
                        'Cannot add the link "%s": links are not supported.',
819
                        $directory
820
                    )
821
                );
822
            }
823
824
            Assertion::directory(
825
                $directory,
826
                sprintf(
827
                    '"%s" must contain a list of existing directories. Could not find "%%s".',
828
                    $key
829
                )
830
            );
831
832
            return $directory;
833
        };
834
835
        return array_map($normalizeDirectory, $directories);
836
    }
837
838
    private static function normalizeFilePath(string $file, string $basePath): string
839
    {
840
        return make_path_absolute(trim($file), $basePath);
841
    }
842
843
    private static function normalizeDirectoryPath(string $directory, string $basePath): string
844
    {
845
        return make_path_absolute(trim($directory), $basePath);
846
    }
847
848
    private static function retrieveBootstrapFile(stdClass $raw, string $basePath): ?string
849
    {
850
        // TODO: deprecate its usage & document this BC break. Compactors will not be configurable
851
        // through that extension point so this is pretty much useless unless proven otherwise.
852
        if (false === isset($raw->bootstrap)) {
853
            return null;
854
        }
855
856
        $file = self::normalizeFilePath($raw->bootstrap, $basePath);
857
858
        Assertion::file($file, 'The bootstrap path "%s" is not a file or does not exist.');
859
860
        return $file;
861
    }
862
863
    /**
864
     * @return Compactor[]
865
     */
866
    private static function retrieveCompactors(stdClass $raw): array
867
    {
868
        // TODO: only accept arrays when set unlike the doc says (it allows a string).
869
        if (false === isset($raw->compactors)) {
870
            return [];
871
        }
872
873
        $compactors = [];
874
875
        foreach ((array) $raw->compactors as $class) {
876
            Assertion::classExists($class, 'The compactor class "%s" does not exist.');
877
            Assertion::implementsInterface($class, Compactor::class, 'The class "%s" is not a compactor class.');
878
879
            if (Php::class === $class || LegacyPhp::class === $class) {
880
                $compactor = self::createPhpCompactor($raw);
881
            } else {
882
                $compactor = new $class();
883
            }
884
885
            $compactors[] = $compactor;
886
        }
887
888
        return $compactors;
889
    }
890
891
    private static function retrieveCompressionAlgorithm(stdClass $raw): ?int
892
    {
893
        // TODO: if in dev mode (when added), do not comment about the compression.
894
        // If not, add a warning to notify the user if no compression algorithm is used
895
        // provided the PHAR is not configured for web purposes.
896
        // If configured for the web, add a warning when a compression algorithm is used
897
        // as this can result in an overhead. Add a doc link explaining this.
898
        //
899
        // Unlike the doc: do not accept integers and document this BC break.
900
        if (false === isset($raw->compression)) {
901
            return null;
902
        }
903
904
        if (false === is_string($raw->compression)) {
905
            Assertion::integer(
906
                $raw->compression,
907
                'Expected compression to be an algorithm name, found %s instead.'
908
            );
909
910
            return $raw->compression;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $raw->compression returns the type string which is incompatible with the type-hinted return null|integer.
Loading history...
911
        }
912
913
        $knownAlgorithmNames = array_keys(get_phar_compression_algorithms());
914
915
        Assertion::inArray(
916
            $raw->compression,
917
            $knownAlgorithmNames,
918
            sprintf(
919
                'Invalid compression algorithm "%%s", use one of "%s" instead.',
920
                implode('", "', $knownAlgorithmNames)
921
            )
922
        );
923
924
        $value = get_phar_compression_algorithms()[$raw->compression];
925
926
        // Phar::NONE is not valid for compressFiles()
927
        if (Phar::NONE === $value) {
928
            return null;
929
        }
930
931
        return $value;
932
    }
933
934
    private static function retrieveFileMode(stdClass $raw): ?int
935
    {
936
        if (isset($raw->chmod)) {
937
            return intval($raw->chmod, 8);
938
        }
939
940
        return null;
941
    }
942
943
    private static function retrieveMainScriptPath(stdClass $raw, string $basePath): string
944
    {
945
        $main = isset($raw->main) ? $raw->main : self::DEFAULT_MAIN_SCRIPT;
946
947
        return self::normalizeFilePath($main, $basePath);
948
    }
949
950
    private static function retrieveMainScriptContents(string $mainScriptPath): string
951
    {
952
        $contents = file_contents($mainScriptPath);
953
954
        // Remove the shebang line: the shebang line in a PHAR should be located in the stub file which is the real
955
        // PHAR entry point file.
956
        return preg_replace('/^#!.*\s*/', '', $contents);
957
    }
958
959
    /**
960
     * @return string[][]
961
     */
962
    private static function retrieveMap(stdClass $raw): array
963
    {
964
        if (false === isset($raw->map)) {
965
            return [];
966
        }
967
968
        $map = [];
969
970
        foreach ((array) $raw->map as $item) {
971
            $processed = [];
972
973
            foreach ($item as $match => $replace) {
974
                $processed[canonicalize(trim($match))] = canonicalize(trim($replace));
975
            }
976
977
            if (isset($processed['_empty_'])) {
978
                $processed[''] = $processed['_empty_'];
979
980
                unset($processed['_empty_']);
981
            }
982
983
            $map[] = $processed;
984
        }
985
986
        return $map;
987
    }
988
989
    /**
990
     * @return mixed
991
     */
992
    private static function retrieveMetadata(stdClass $raw)
993
    {
994
        // TODO: the doc currently say this can be any value; check if true
995
        // and if not add checks accordingly
996
        //
997
        // Also review the doc as I don't find it very helpful...
998
        if (isset($raw->metadata)) {
999
            if (is_object($raw->metadata)) {
1000
                return (array) $raw->metadata;
1001
            }
1002
1003
            return $raw->metadata;
1004
        }
1005
1006
        return null;
1007
    }
1008
1009
    private static function retrieveOutputPath(stdClass $raw, string $basePath): string
1010
    {
1011
        if (isset($raw->output)) {
1012
            $path = $raw->output;
1013
        } else {
1014
            $path = self::DEFAULT_ALIAS;
1015
        }
1016
1017
        return make_path_absolute($path, $basePath);
1018
    }
1019
1020
    private static function retrievePrivateKeyPassphrase(stdClass $raw): ?string
1021
    {
1022
        // TODO: add check to not allow this setting without the private key path
1023
        if (isset($raw->{'key-pass'})
1024
            && is_string($raw->{'key-pass'})
1025
        ) {
1026
            return $raw->{'key-pass'};
1027
        }
1028
1029
        return null;
1030
    }
1031
1032
    private static function retrievePrivateKeyPath(stdClass $raw): ?string
1033
    {
1034
        // TODO: If passed need to check its existence
1035
        // Also need
1036
1037
        if (isset($raw->key)) {
1038
            return $raw->key;
1039
        }
1040
1041
        return null;
1042
    }
1043
1044
    private static function retrieveReplacements(stdClass $raw): array
1045
    {
1046
        // TODO: add exmample in the doc
1047
        // Add checks against the values
1048
        if (isset($raw->replacements)) {
1049
            return (array) $raw->replacements;
1050
        }
1051
1052
        return [];
1053
    }
1054
1055
    private static function retrieveProcessedReplacements(
1056
        array $replacements,
1057
        stdClass $raw,
1058
        ?string $file
1059
    ): array {
1060
        if (null === $file) {
1061
            return [];
1062
        }
1063
1064
        if (null !== ($git = self::retrieveGitHashPlaceholder($raw))) {
1065
            $replacements[$git] = self::retrieveGitHash($file);
1066
        }
1067
1068
        if (null !== ($git = self::retrieveGitShortHashPlaceholder($raw))) {
1069
            $replacements[$git] = self::retrieveGitHash($file, true);
1070
        }
1071
1072
        if (null !== ($git = self::retrieveGitTagPlaceholder($raw))) {
1073
            $replacements[$git] = self::retrieveGitTag($file);
1074
        }
1075
1076
        if (null !== ($git = self::retrieveGitVersionPlaceholder($raw))) {
1077
            $replacements[$git] = self::retrieveGitVersion($file);
1078
        }
1079
1080
        if (null !== ($date = self::retrieveDatetimeNowPlaceHolder($raw))) {
1081
            $replacements[$date] = self::retrieveDatetimeNow(
1082
                self::retrieveDatetimeFormat($raw)
1083
            );
1084
        }
1085
1086
        $sigil = self::retrieveReplacementSigil($raw);
1087
1088
        foreach ($replacements as $key => $value) {
1089
            unset($replacements[$key]);
1090
            $replacements["$sigil$key$sigil"] = $value;
1091
        }
1092
1093
        return $replacements;
1094
    }
1095
1096
    private static function retrieveGitHashPlaceholder(stdClass $raw): ?string
1097
    {
1098
        if (isset($raw->{'git-commit'})) {
1099
            return $raw->{'git-commit'};
1100
        }
1101
1102
        return null;
1103
    }
1104
1105
    /**
1106
     * @param string $file
1107
     * @param bool   $short Use the short version
1108
     *
1109
     * @return string the commit hash
1110
     */
1111
    private static function retrieveGitHash(string $file, bool $short = false): string
1112
    {
1113
        return self::runGitCommand(
1114
            sprintf(
1115
                'git log --pretty="%s" -n1 HEAD',
1116
                $short ? '%h' : '%H'
1117
            ),
1118
            $file
1119
        );
1120
    }
1121
1122
    private static function retrieveGitShortHashPlaceholder(stdClass $raw): ?string
1123
    {
1124
        if (isset($raw->{'git-commit-short'})) {
1125
            return $raw->{'git-commit-short'};
1126
        }
1127
1128
        return null;
1129
    }
1130
1131
    private static function retrieveGitTagPlaceholder(stdClass $raw): ?string
1132
    {
1133
        if (isset($raw->{'git-tag'})) {
1134
            return $raw->{'git-tag'};
1135
        }
1136
1137
        return null;
1138
    }
1139
1140
    private static function retrieveGitTag(string $file): ?string
1141
    {
1142
        return self::runGitCommand('git describe --tags HEAD', $file);
1143
    }
1144
1145
    private static function retrieveGitVersionPlaceholder(stdClass $raw): ?string
1146
    {
1147
        if (isset($raw->{'git-version'})) {
1148
            return $raw->{'git-version'};
1149
        }
1150
1151
        return null;
1152
    }
1153
1154
    private static function retrieveGitVersion(string $file): ?string
1155
    {
1156
        // TODO: check if is still relevant as IMO we are better off using OcramiusVersionPackage
1157
        // to avoid messing around with that
1158
1159
        try {
1160
            return self::retrieveGitTag($file);
1161
        } catch (RuntimeException $exception) {
1162
            try {
1163
                return self::retrieveGitHash($file, true);
1164
            } catch (RuntimeException $exception) {
1165
                throw new RuntimeException(
1166
                    sprintf(
1167
                        'The tag or commit hash could not be retrieved from "%s": %s',
1168
                        dirname($file),
1169
                        $exception->getMessage()
1170
                    ),
1171
                    0,
1172
                    $exception
1173
                );
1174
            }
1175
        }
1176
    }
1177
1178
    private static function retrieveDatetimeNowPlaceHolder(stdClass $raw): ?string
1179
    {
1180
        // TODO: double check why this is done and how it is used it's not completely clear to me.
1181
        // Also make sure the documentation is up to date after.
1182
        // Instead of having two sistinct doc entries for `datetime` and `datetime-format`, it would
1183
        // be better to have only one element IMO like:
1184
        //
1185
        // "datetime": {
1186
        //   "value": "val",
1187
        //   "format": "Y-m-d"
1188
        // }
1189
        //
1190
        // Also add a check that one cannot be provided without the other. Or maybe it should? I guess
1191
        // if the datetime format is the default one it's ok; but in any case the format should not
1192
        // be added without the datetime value...
1193
1194
        if (isset($raw->{'datetime'})) {
1195
            return $raw->{'datetime'};
1196
        }
1197
1198
        return null;
1199
    }
1200
1201
    private static function retrieveDatetimeNow(string $format)
1202
    {
1203
        $now = new DateTimeImmutable('now');
1204
1205
        $datetime = $now->format($format);
1206
1207
        if (!$datetime) {
1208
            throw new InvalidArgumentException(
1209
                sprintf(
1210
                    '""%s" is not a valid PHP date format',
1211
                    $format
1212
                )
1213
            );
1214
        }
1215
1216
        return $datetime;
1217
    }
1218
1219
    private static function retrieveDatetimeFormat(stdClass $raw): string
1220
    {
1221
        if (isset($raw->{'datetime_format'})) {
1222
            return $raw->{'datetime_format'};
1223
        }
1224
1225
        return self::DEFAULT_DATETIME_FORMAT;
1226
    }
1227
1228
    private static function retrieveReplacementSigil(stdClass $raw)
1229
    {
1230
        if (isset($raw->{'replacement-sigil'})) {
1231
            return $raw->{'replacement-sigil'};
1232
        }
1233
1234
        return self::DEFAULT_REPLACEMENT_SIGIL;
1235
    }
1236
1237
    private static function retrieveShebang(stdClass $raw): ?string
1238
    {
1239
        if (false === array_key_exists('shebang', (array) $raw)) {
1240
            return self::DEFAULT_SHEBANG;
1241
        }
1242
1243
        if (null === $raw->shebang) {
1244
            return null;
1245
        }
1246
1247
        $shebang = trim($raw->shebang);
1248
1249
        Assertion::notEmpty($shebang, 'The shebang should not be empty.');
1250
        Assertion::true(
1251
            '#!' === substr($shebang, 0, 2),
1252
            sprintf(
1253
                'The shebang line must start with "#!". Got "%s" instead',
1254
                $shebang
1255
            )
1256
        );
1257
1258
        return $shebang;
1259
    }
1260
1261
    private static function retrieveSigningAlgorithm(stdClass $raw): int
1262
    {
1263
        // TODO: trigger warning: if no signing algorithm is given provided we are not in dev mode
1264
        // TODO: trigger a warning if the signing algorithm used is weak
1265
        // TODO: no longer accept strings & document BC break
1266
        if (false === isset($raw->algorithm)) {
1267
            return Phar::SHA1;
1268
        }
1269
1270
        if (is_string($raw->algorithm)) {
1271
            if (false === defined('Phar::'.$raw->algorithm)) {
1272
                throw new InvalidArgumentException(
1273
                    sprintf(
1274
                        'The signing algorithm "%s" is not supported.',
1275
                        $raw->algorithm
1276
                    )
1277
                );
1278
            }
1279
1280
            return constant('Phar::'.$raw->algorithm);
1281
        }
1282
1283
        return $raw->algorithm;
1284
    }
1285
1286
    private static function retrieveStubBannerContents(stdClass $raw): ?string
1287
    {
1288
        if (false === array_key_exists('banner', (array) $raw)) {
1289
            return self::DEFAULT_BANNER;
1290
        }
1291
1292
        if (null === $raw->banner) {
1293
            return null;
1294
        }
1295
1296
        $banner = $raw->banner;
1297
1298
        if (is_array($banner)) {
1299
            $banner = implode("\n", $banner);
1300
        }
1301
1302
        return $banner;
1303
    }
1304
1305
    private static function retrieveStubBannerPath(stdClass $raw, string $basePath): ?string
1306
    {
1307
        if (false === isset($raw->{'banner-file'})) {
1308
            return null;
1309
        }
1310
1311
        $bannerFile = make_path_absolute($raw->{'banner-file'}, $basePath);
1312
1313
        Assertion::file($bannerFile);
1314
1315
        return $bannerFile;
1316
    }
1317
1318
    private static function normalizeStubBannerContents(?string $contents): ?string
1319
    {
1320
        if (null === $contents) {
1321
            return null;
1322
        }
1323
1324
        $banner = explode("\n", $contents);
1325
        $banner = array_map('trim', $banner);
1326
1327
        return implode("\n", $banner);
1328
    }
1329
1330
    private static function retrieveStubPath(stdClass $raw, string $basePath): ?string
1331
    {
1332
        if (isset($raw->stub) && is_string($raw->stub)) {
1333
            $stubPath = make_path_absolute($raw->stub, $basePath);
1334
1335
            Assertion::file($stubPath);
1336
1337
            return $stubPath;
1338
        }
1339
1340
        return null;
1341
    }
1342
1343
    private static function retrieveIsInterceptFileFuncs(stdClass $raw): bool
1344
    {
1345
        if (isset($raw->intercept)) {
1346
            return $raw->intercept;
1347
        }
1348
1349
        return false;
1350
    }
1351
1352
    private static function retrieveIsPrivateKeyPrompt(stdClass $raw): bool
1353
    {
1354
        if (isset($raw->{'key-pass'})
1355
            && (true === $raw->{'key-pass'})) {
1356
            return true;
1357
        }
1358
1359
        return false;
1360
    }
1361
1362
    private static function retrieveIsStubGenerated(stdClass $raw): bool
1363
    {
1364
        if (isset($raw->stub) && (true === $raw->stub)) {
1365
            return true;
1366
        }
1367
1368
        return false;
1369
    }
1370
1371
    /**
1372
     * Runs a Git command on the repository.
1373
     *
1374
     * @param string $command the command
1375
     *
1376
     * @return string the trimmed output from the command
1377
     */
1378
    private static function runGitCommand(string $command, string $file): string
1379
    {
1380
        $path = dirname($file);
1381
1382
        $process = new Process($command, $path);
1383
1384
        if (0 === $process->run()) {
1385
            return trim($process->getOutput());
1386
        }
1387
1388
        throw new RuntimeException(
1389
            sprintf(
1390
                'The tag or commit hash could not be retrieved from "%s": %s',
1391
                $path,
1392
                $process->getErrorOutput()
1393
            )
1394
        );
1395
    }
1396
1397
    private static function createPhpCompactor(stdClass $raw): Compactor
1398
    {
1399
        // TODO: false === not set; check & add test/doc
1400
        $tokenizer = new Tokenizer();
1401
1402
        if (false === empty($raw->annotations) && isset($raw->annotations->ignore)) {
1403
            $tokenizer->ignore(
1404
                (array) $raw->annotations->ignore
1405
            );
1406
        }
1407
1408
        return new Php($tokenizer);
1409
    }
1410
}
1411