Completed
Pull Request — master (#37)
by Théo
31:28 queued 20:15
created

Configuration::__construct()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 79
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 79
rs 8.8701
c 0
b 0
f 0
cc 1
eloc 40
nc 1
nop 34

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