Passed
Pull Request — master (#39)
by Théo
02:13
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
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 = self::retrieveAlias($raw);
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 retrieveAlias(stdClass $raw): string
515
    {
516
        $alias = $raw->alias ?? self::DEFAULT_ALIAS;
517
518
        Assertion::string($alias, 'Expected PHAR alias to be a string, got "%s" instead.');
519
520
        $alias = trim($alias);
521
522
        Assertion::notEmpty($alias, 'A PHAR alias cannot be empty.');
523
524
        return $alias;
525
    }
526
527
    private static function retrieveBasePath(string $file, stdClass $raw): string
528
    {
529
        if (isset($raw->{'base-path'})) {
530
            if (false === is_dir($raw->{'base-path'})) {
531
                throw new InvalidArgumentException(
532
                    sprintf(
533
                        'The base path "%s" is not a directory or does not exist.',
534
                        $raw->{'base-path'}
535
                    )
536
                );
537
            }
538
539
            return realpath($raw->{'base-path'});
540
        }
541
542
        return realpath(dirname($file));
543
    }
544
545
    /**
546
     * @return SplFileInfo[]
547
     */
548
    private static function retrieveBinaryDirectories(stdClass $raw, string $basePath): array
549
    {
550
        if (isset($raw->{'directories-bin'})) {
551
            $directories = (array) $raw->{'directories-bin'};
552
553
            array_walk(
554
                $directories,
555
                function (&$directory) use ($basePath): void {
556
                    $directory = $basePath
557
                        .DIRECTORY_SEPARATOR
558
                        .canonicalize($directory);
559
                }
560
            );
561
562
            return $directories;
563
        }
564
565
        return [];
566
    }
567
568
    /**
569
     * @param SplFileInfo[] $binaryDirectories
570
     * @param Closure       $blacklistFilter
571
     *
572
     * @return null|iterable|SplFileInfo[] the iterator
573
     */
574
    private static function retrieveBinaryDirectoriesIterator(array $binaryDirectories, Closure $blacklistFilter): ?iterable
575
    {
576
        if ([] !== $binaryDirectories) {
577
            return Finder::create()
578
                ->files()
579
                ->filter($blacklistFilter)
580
                ->ignoreVCS(true)
581
                ->in($binaryDirectories);
582
        }
583
584
        return null;
585
    }
586
587
    /**
588
     * @return SplFileInfo[] the list of paths
589
     */
590
    private static function retrieveBinaryFiles(stdClass $raw, string $basePath): array
591
    {
592
        if (isset($raw->{'files-bin'})) {
593
            $files = [];
594
595
            foreach ((array) $raw->{'files-bin'} as $file) {
596
                $files[] = new SplFileInfo(
597
                    $basePath.DIRECTORY_SEPARATOR.canonicalize($file)
598
                );
599
            }
600
601
            return $files;
602
        }
603
604
        return [];
605
    }
606
607
    /**
608
     * @return null|iterable|SplFileInfo[] the iterator
609
     */
610
    private static function retrieveBinaryFilesIterator(array $binaryFiles): ?iterable
611
    {
612
        if ([] !== $binaryFiles) {
613
            return new ArrayIterator($binaryFiles);
614
        }
615
616
        return null;
617
    }
618
619
    /**
620
     * @return string[]
621
     */
622
    private static function retrieveBlacklist(stdClass $raw): array
623
    {
624
        // TODO: only allow arrays if is set unlike the doc which says it can be a string
625
        if (isset($raw->blacklist)) {
626
            $blacklist = (array) $raw->blacklist;
627
628
            array_walk(
629
                $blacklist,
630
                function (&$file): void {
631
                    $file = canonicalize($file);
632
                }
633
            );
634
635
            return $blacklist;
636
        }
637
638
        return [];
639
    }
640
641
    /**
642
     * @param string   $basePath
643
     * @param string[] $blacklist
644
     *
645
     * @return Closure
646
     */
647
    private static function retrieveBlacklistFilter(string $basePath, array $blacklist): Closure
648
    {
649
        $base = sprintf(
650
            '/^%s/',
651
            preg_quote($basePath.DIRECTORY_SEPARATOR, '/')
652
        );
653
654
        return function (SplFileInfo $file) use ($base, $blacklist): ?bool {
655
            $path = canonicalize(
656
                preg_replace($base, '', $file->getPathname())
657
            );
658
659
            if (in_array($path, $blacklist, true)) {
660
                return false;
661
            }
662
663
            return null;
664
        };
665
    }
666
667
    /**
668
     * @param stdClass $raw
669
     * @param string   $basePath
670
     * @param Closure  $blacklistFilter
671
     *
672
     * @return iterable[]|SplFileInfo[][]
673
     */
674
    private static function retrieveBinaryIterators(stdClass $raw, string $basePath, Closure $blacklistFilter): array
675
    {
676
        if (isset($raw->{'finder-bin'})) {
677
            return self::processFinders($raw->{'finder-bin'}, $basePath, $blacklistFilter);
678
        }
679
680
        return [];
681
    }
682
683
    /**
684
     * @param stdClass $raw
685
     * @param string   $basePath
686
     *
687
     * @return string[]
688
     */
689
    private static function retrieveDirectories(stdClass $raw, string $basePath): array
690
    {
691
        // TODO: do not accept strings unlike the doc says and document that BC break
692
        // Need to do the same for bin, directories-bin, files, ~~directories~~
693
        if (isset($raw->directories)) {
694
            $directories = (array) $raw->directories;
695
696
            array_walk(
697
                $directories,
698
                function (&$directory) use ($basePath): void {
699
                    $directory = $basePath
700
                        .DIRECTORY_SEPARATOR
701
                        .rtrim(canonicalize($directory), DIRECTORY_SEPARATOR);
702
                }
703
            );
704
705
            return $directories;
706
        }
707
708
        return [];
709
    }
710
711
    /**
712
     * @param string[] $directories
713
     * @param Closure  $blacklistFilter
714
     *
715
     * @return null|iterable|SplFileInfo[]
716
     */
717
    private static function retrieveDirectoriesIterator(array $directories, Closure $blacklistFilter): ?iterable
718
    {
719
        if ([] !== $directories) {
720
            return Finder::create()
721
                ->files()
722
                ->filter($blacklistFilter)
723
                ->ignoreVCS(true)
724
                ->in($directories)
725
            ;
726
        }
727
728
        return null;
729
    }
730
731
    /**
732
     * @return SplFileInfo[]
733
     */
734
    private static function retrieveFiles(stdClass $raw, string $basePath): array
735
    {
736
        if (false === isset($raw->files)) {
737
            return [];
738
        }
739
740
        $files = [];
741
742
        foreach ((array) $raw->files as $file) {
743
            $file = new SplFileInfo(
744
                $path = $basePath.DIRECTORY_SEPARATOR.canonicalize($file)
745
            );
746
747
            if (false === $file->isFile()) {
748
                throw new InvalidArgumentException(
749
                    sprintf(
750
                        'The file "%s" does not exist or is not a file.',
751
                        $path
752
                    )
753
                );
754
            }
755
756
            $files[] = $file;
757
        }
758
759
        return $files;
760
    }
761
762
    /**
763
     * @param SplFileInfo[] $files
764
     *
765
     * @return null|iterable|SplFileInfo[]
766
     */
767
    private static function retrieveFilesIterator(array $files): ?iterable
768
    {
769
        if ([] !== $files) {
770
            return new ArrayIterator($files);
771
        }
772
773
        return null;
774
    }
775
776
    /**
777
     * @param stdClass $raw
778
     * @param string   $basePath
779
     * @param Closure  $blacklistFilter
780
     *
781
     * @return iterable[]|SplFileInfo[][]
782
     */
783
    private static function retrieveFilesIterators(stdClass $raw, string $basePath, Closure $blacklistFilter): array
784
    {
785
        // TODO: double check the way all files are included to make sure none is missing if one element is
786
        // misconfigured
787
        if (isset($raw->finder)) {
788
            return self::processFinders($raw->finder, $basePath, $blacklistFilter);
789
        }
790
791
        return [];
792
    }
793
794
    /**
795
     * @param array   $findersConfig   the configuration
796
     * @param string  $basePath
797
     * @param Closure $blacklistFilter
798
     *
799
     * @return Finder[]
800
     */
801
    private static function processFinders(array $findersConfig, string $basePath, Closure $blacklistFilter): array
802
    {
803
        // TODO: add an example in the doc about the finder and a mention in `finder-bin` to look at finder
804
805
        $processFinderConfig = function ($methods) use ($basePath, $blacklistFilter): Finder {
806
            $finder = Finder::create()
807
                ->files()
808
                ->filter($blacklistFilter)
809
                ->ignoreVCS(true)
810
            ;
811
812
            if (isset($methods->in)) {
813
                $methods->in = (array) $methods->in;
814
815
                array_walk(
816
                    $methods->in,
817
                    function (&$directory) use ($basePath): void {
818
                        $directory = canonicalize(
819
                            $basePath.DIRECTORY_SEPARATOR.$directory
820
                        );
821
                    }
822
                );
823
            }
824
825
            foreach ($methods as $method => $arguments) {
826
                if (false === method_exists($finder, $method)) {
827
                    throw new InvalidArgumentException(
828
                        sprintf(
829
                            'The method "Finder::%s" does not exist.',
830
                            $method
831
                        )
832
                    );
833
                }
834
835
                $arguments = (array) $arguments;
836
837
                foreach ($arguments as $argument) {
838
                    $finder->$method($argument);
839
                }
840
            }
841
842
            return $finder;
843
        };
844
845
        return array_map($processFinderConfig, $findersConfig);
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 = $raw->bootstrap;
857
858
        if (false === is_absolute($file)) {
859
            $file = canonicalize(
860
                $basePath.DIRECTORY_SEPARATOR.$file
861
            );
862
        }
863
864
        if (false === file_exists($file)) {
865
            throw new InvalidArgumentException(
866
                sprintf(
867
                    'The bootstrap path "%s" is not a file or does not exist.',
868
                    $file
869
                )
870
            );
871
        }
872
873
        return $file;
874
    }
875
876
    /**
877
     * @return Compactor[]
878
     */
879
    private static function retrieveCompactors(stdClass $raw): array
880
    {
881
        // TODO: only accept arrays when set unlike the doc says (it allows a string).
882
        if (false === isset($raw->compactors)) {
883
            return [];
884
        }
885
886
        $compactors = [];
887
888
        foreach ((array) $raw->compactors as $class) {
889
            if (false === class_exists($class)) {
890
                throw new InvalidArgumentException(
891
                    sprintf(
892
                        'The compactor class "%s" does not exist.',
893
                        $class
894
                    )
895
                );
896
            }
897
898
            if (Php::class === $class || LegacyPhp::class === $class) {
899
                $compactor = self::createPhpCompactor($raw);
900
            } else {
901
                $compactor = new $class();
902
            }
903
904
            if (false === ($compactor instanceof Compactor)) {
905
                throw new InvalidArgumentException(
906
                    sprintf(
907
                        'The class "%s" is not a compactor class.',
908
                        $class
909
                    )
910
                );
911
            }
912
913
            $compactors[] = $compactor;
914
        }
915
916
        return $compactors;
917
    }
918
919
    private static function retrieveCompressionAlgorithm(stdClass $raw): ?int
920
    {
921
        // TODO: if in dev mode (when added), do not comment about the compression.
922
        // If not, add a warning to notify the user if no compression algorithm is used
923
        // provided the PHAR is not configured for web purposes.
924
        // If configured for the web, add a warning when a compression algorithm is used
925
        // as this can result in an overhead. Add a doc link explaining this.
926
        //
927
        // Unlike the doc: do not accept integers and document this BC break.
928
        if (false === isset($raw->compression)) {
929
            return null;
930
        }
931
932
        if (false === is_string($raw->compression)) {
933
            Assertion::integer(
934
                $raw->compression,
935
                'Expected compression to be an algorithm name, found %s instead.'
936
            );
937
938
            return $raw->compression;
939
        }
940
941
        $knownAlgorithmNames = array_keys(get_phar_compression_algorithms());
942
943
        Assertion::inArray(
944
            $raw->compression,
945
            $knownAlgorithmNames,
946
            sprintf(
947
                'Invalid compression algorithm "%%s", use one of "%s" instead.',
948
                implode('", "', $knownAlgorithmNames)
949
            )
950
        );
951
952
        $value = get_phar_compression_algorithms()[$raw->compression];
953
954
        // Phar::NONE is not valid for compressFiles()
955
        if (Phar::NONE === $value) {
956
            return null;
957
        }
958
959
        return $value;
960
    }
961
962
    private static function retrieveFileMode(stdClass $raw): ?int
963
    {
964
        if (isset($raw->chmod)) {
965
            return intval($raw->chmod, 8);
966
        }
967
968
        return null;
969
    }
970
971
    private static function retrieveMainScriptPath(stdClass $raw): ?string
972
    {
973
        // TODO: check if is used for the web as well when web is set to true
974
        // If that the case make this field mandatory otherwise adjust the check
975
        // rules accordinly to ensure we do not have an empty PHAR
976
        if (isset($raw->main)) {
977
            return canonicalize($raw->main);
978
        }
979
980
        return null;
981
    }
982
983
    private static function retrieveMainScriptContents(?string $mainScriptPath, string $basePath): ?string
984
    {
985
        if (null === $mainScriptPath) {
986
            return null;
987
        }
988
989
        $mainScriptPath = $basePath.DIRECTORY_SEPARATOR.$mainScriptPath;
990
991
        Assertion::readable($mainScriptPath);
992
993
        $contents = file_get_contents($mainScriptPath);
994
995
        // Remove the shebang line
996
        return preg_replace('/^#!.*\s*/', '', $contents);
997
    }
998
999
    /**
1000
     * @return string[][]
1001
     */
1002
    private static function retrieveMap(stdClass $raw): array
1003
    {
1004
        if (false === isset($raw->map)) {
1005
            return [];
1006
        }
1007
1008
        $map = [];
1009
1010
        foreach ((array) $raw->map as $item) {
1011
            $processed = [];
1012
1013
            foreach ($item as $match => $replace) {
1014
                $processed[canonicalize(trim($match))] = canonicalize(trim($replace));
1015
            }
1016
1017
            if (isset($processed['_empty_'])) {
1018
                $processed[''] = $processed['_empty_'];
1019
1020
                unset($processed['_empty_']);
1021
            }
1022
1023
            $map[] = $processed;
1024
        }
1025
1026
        return $map;
1027
    }
1028
1029
    /**
1030
     * @return mixed
1031
     */
1032
    private static function retrieveMetadata(stdClass $raw)
1033
    {
1034
        // TODO: the doc currently say this can be any value; check if true
1035
        // and if not add checks accordingly
1036
        //
1037
        // Also review the doc as I don't find it very helpful...
1038
        if (isset($raw->metadata)) {
1039
            if (is_object($raw->metadata)) {
1040
                return (array) $raw->metadata;
1041
            }
1042
1043
            return $raw->metadata;
1044
        }
1045
1046
        return null;
1047
    }
1048
1049
    private static function retrieveMimetypeMapping(stdClass $raw): array
1050
    {
1051
        // TODO: this parameter is not clear to me: review usage, doc & checks
1052
        if (isset($raw->mimetypes)) {
1053
            return (array) $raw->mimetypes;
1054
        }
1055
1056
        return [];
1057
    }
1058
1059
    private static function retrieveMungVariables(stdClass $raw): array
1060
    {
1061
        // TODO: this parameter is not clear to me: review usage, doc & checks
1062
        // TODO: add error/warning if used when web is not enabled
1063
        if (isset($raw->mung)) {
1064
            return (array) $raw->mung;
1065
        }
1066
1067
        return [];
1068
    }
1069
1070
    private static function retrieveNotFoundScriptPath(stdClass $raw): ?string
1071
    {
1072
        // TODO: this parameter is not clear to me: review usage, doc & checks
1073
        // TODO: add error/warning if used when web is not enabled
1074
        if (isset($raw->{'not-found'})) {
1075
            return $raw->{'not-found'};
1076
        }
1077
1078
        return null;
1079
    }
1080
1081
    private static function retrieveOutputPath(stdClass $raw, string $file): string
1082
    {
1083
        // TODO: make this path relative to the base path like everything else
1084
        // otherwise this is really confusing. This is a BC break that needs to be
1085
        // documented though (and update the doc accordingly as well)
1086
        $base = getcwd().DIRECTORY_SEPARATOR;
1087
1088
        if (isset($raw->output)) {
1089
            $path = $raw->output;
1090
1091
            if (false === is_absolute($path)) {
1092
                $path = canonicalize($base.$path);
1093
            }
1094
        } else {
1095
            $path = $base.self::DEFAULT_ALIAS;
1096
        }
1097
1098
        if (false !== strpos($path, '@'.'git-version@')) {
1099
            $gitVersion = self::retrieveGitVersion($file);
1100
1101
            $path = str_replace('@'.'git-version@', $gitVersion, $path);
1102
        }
1103
1104
        return $path;
1105
    }
1106
1107
    private static function retrievePrivateKeyPassphrase(stdClass $raw): ?string
1108
    {
1109
        // TODO: add check to not allow this setting without the private key path
1110
        if (isset($raw->{'key-pass'})
1111
            && is_string($raw->{'key-pass'})
1112
        ) {
1113
            return $raw->{'key-pass'};
1114
        }
1115
1116
        return null;
1117
    }
1118
1119
    private static function retrievePrivateKeyPath(stdClass $raw): ?string
1120
    {
1121
        // TODO: If passed need to check its existence
1122
        // Also need
1123
1124
        if (isset($raw->key)) {
1125
            return $raw->key;
1126
        }
1127
1128
        return null;
1129
    }
1130
1131
    private static function retrieveReplacements(stdClass $raw): array
1132
    {
1133
        // TODO: add exmample in the doc
1134
        // Add checks against the values
1135
        if (isset($raw->replacements)) {
1136
            return (array) $raw->replacements;
1137
        }
1138
1139
        return [];
1140
    }
1141
1142
    private static function retrieveProcessedReplacements(
1143
        array $replacements,
1144
        stdClass $raw,
1145
        string $file
1146
    ): array {
1147
        if (null !== ($git = self::retrieveGitHashPlaceholder($raw))) {
1148
            $replacements[$git] = self::retrieveGitHash($file);
1149
        }
1150
1151
        if (null !== ($git = self::retrieveGitShortHashPlaceholder($raw))) {
1152
            $replacements[$git] = self::retrieveGitHash($file, true);
1153
        }
1154
1155
        if (null !== ($git = self::retrieveGitTagPlaceholder($raw))) {
1156
            $replacements[$git] = self::retrieveGitTag($file);
1157
        }
1158
1159
        if (null !== ($git = self::retrieveGitVersionPlaceholder($raw))) {
1160
            $replacements[$git] = self::retrieveGitVersion($file);
1161
        }
1162
1163
        if (null !== ($date = self::retrieveDatetimeNowPlaceHolder($raw))) {
1164
            $replacements[$date] = self::retrieveDatetimeNow(
1165
                self::retrieveDatetimeFormat($raw)
1166
            );
1167
        }
1168
1169
        $sigil = self::retrieveReplacementSigil($raw);
1170
1171
        foreach ($replacements as $key => $value) {
1172
            unset($replacements[$key]);
1173
            $replacements["$sigil$key$sigil"] = $value;
1174
        }
1175
1176
        return $replacements;
1177
    }
1178
1179
    private static function retrieveGitHashPlaceholder(stdClass $raw): ?string
1180
    {
1181
        if (isset($raw->{'git-commit'})) {
1182
            return $raw->{'git-commit'};
1183
        }
1184
1185
        return null;
1186
    }
1187
1188
    /**
1189
     * @param string $file
1190
     * @param bool   $short Use the short version
1191
     *
1192
     * @return string the commit hash
1193
     */
1194
    private static function retrieveGitHash(string $file, bool $short = false): string
1195
    {
1196
        return self::runGitCommand(
1197
            sprintf(
1198
                'git log --pretty="%s" -n1 HEAD',
1199
                $short ? '%h' : '%H'
1200
            ),
1201
            $file
1202
        );
1203
    }
1204
1205
    private static function retrieveGitShortHashPlaceholder(stdClass $raw): ?string
1206
    {
1207
        if (isset($raw->{'git-commit-short'})) {
1208
            return $raw->{'git-commit-short'};
1209
        }
1210
1211
        return null;
1212
    }
1213
1214
    private static function retrieveGitTagPlaceholder(stdClass $raw): ?string
1215
    {
1216
        if (isset($raw->{'git-tag'})) {
1217
            return $raw->{'git-tag'};
1218
        }
1219
1220
        return null;
1221
    }
1222
1223
    private static function retrieveGitTag(string $file): ?string
1224
    {
1225
        return self::runGitCommand('git describe --tags HEAD', $file);
1226
    }
1227
1228
    private static function retrieveGitVersionPlaceholder(stdClass $raw): ?string
1229
    {
1230
        if (isset($raw->{'git-version'})) {
1231
            return $raw->{'git-version'};
1232
        }
1233
1234
        return null;
1235
    }
1236
1237
    private static function retrieveGitVersion(string $file): ?string
1238
    {
1239
        // TODO: check if is still relevant as IMO we are better off using OcramiusVersionPackage
1240
        // to avoid messing around with that
1241
1242
        try {
1243
            return self::retrieveGitTag($file);
1244
        } catch (RuntimeException $exception) {
1245
            try {
1246
                return self::retrieveGitHash($file, true);
1247
            } catch (RuntimeException $exception) {
1248
                throw new RuntimeException(
1249
                    sprintf(
1250
                        'The tag or commit hash could not be retrieved from "%s": %s',
1251
                        dirname($file),
1252
                        $exception->getMessage()
1253
                    ),
1254
                    0,
1255
                    $exception
1256
                );
1257
            }
1258
        }
1259
    }
1260
1261
    private static function retrieveDatetimeNowPlaceHolder(stdClass $raw): ?string
1262
    {
1263
        // TODO: double check why this is done and how it is used it's not completely clear to me.
1264
        // Also make sure the documentation is up to date after.
1265
        // Instead of having two sistinct doc entries for `datetime` and `datetime-format`, it would
1266
        // be better to have only one element IMO like:
1267
        //
1268
        // "datetime": {
1269
        //   "value": "val",
1270
        //   "format": "Y-m-d"
1271
        // }
1272
        //
1273
        // Also add a check that one cannot be provided without the other. Or maybe it should? I guess
1274
        // if the datetime format is the default one it's ok; but in any case the format should not
1275
        // be added without the datetime value...
1276
1277
        if (isset($raw->{'datetime'})) {
1278
            return $raw->{'datetime'};
1279
        }
1280
1281
        return null;
1282
    }
1283
1284
    private static function retrieveDatetimeNow(string $format)
1285
    {
1286
        $now = new DateTimeImmutable('now');
1287
1288
        $datetime = $now->format($format);
1289
1290
        if (!$datetime) {
1291
            throw new InvalidArgumentException(
1292
                sprintf(
1293
                    '""%s" is not a valid PHP date format',
1294
                    $format
1295
                )
1296
            );
1297
        }
1298
1299
        return $datetime;
1300
    }
1301
1302
    private static function retrieveDatetimeFormat(stdClass $raw): string
1303
    {
1304
        if (isset($raw->{'datetime_format'})) {
1305
            return $raw->{'datetime_format'};
1306
        }
1307
1308
        return self::DEFAULT_DATETIME_FORMAT;
1309
    }
1310
1311
    private static function retrieveReplacementSigil(stdClass $raw)
1312
    {
1313
        if (isset($raw->{'replacement-sigil'})) {
1314
            return $raw->{'replacement-sigil'};
1315
        }
1316
1317
        return self::DEFAULT_REPLACEMENT_SIGIL;
1318
    }
1319
1320
    private static function retrieveShebang(stdClass $raw): ?string
1321
    {
1322
        // TODO: unlike the doc says do not allow empty strings.
1323
        // Leverage `Assertion` here?
1324
        if (false === isset($raw->shebang)) {
1325
            return null;
1326
        }
1327
1328
        if (('' === $raw->shebang) || (false === $raw->shebang)) {
1329
            return '';
1330
        }
1331
1332
        $shebang = trim($raw->shebang);
1333
1334
        if ('#!' !== substr($shebang, 0, 2)) {
1335
            throw new InvalidArgumentException(
1336
                sprintf(
1337
                    'The shebang line must start with "#!": %s',
1338
                    $shebang
1339
                )
1340
            );
1341
        }
1342
1343
        return $shebang;
1344
    }
1345
1346
    private static function retrieveSigningAlgorithm(stdClass $raw): int
1347
    {
1348
        // TODO: trigger warning: if no signing algorithm is given provided we are not in dev mode
1349
        // TODO: trigger a warning if the signing algorithm used is weak
1350
        // TODO: no longer accept strings & document BC break
1351
        if (false === isset($raw->algorithm)) {
1352
            return Phar::SHA1;
1353
        }
1354
1355
        if (is_string($raw->algorithm)) {
1356
            if (false === defined('Phar::'.$raw->algorithm)) {
1357
                throw new InvalidArgumentException(
1358
                    sprintf(
1359
                        'The signing algorithm "%s" is not supported.',
1360
                        $raw->algorithm
1361
                    )
1362
                );
1363
            }
1364
1365
            return constant('Phar::'.$raw->algorithm);
1366
        }
1367
1368
        return $raw->algorithm;
1369
    }
1370
1371
    private static function retrieveStubBanner(stdClass $raw): ?string
1372
    {
1373
        if (isset($raw->{'banner'})) {
1374
            return $raw->{'banner'};
1375
        }
1376
1377
        return null;
1378
    }
1379
1380
    private static function retrieveStubBannerPath(stdClass $raw): ?string
1381
    {
1382
        // TODO: if provided check its existence here or should it be defered to later?
1383
        // Works case this check can be duplicated...
1384
        //
1385
        // Check if is relative to base path: if not make it so (may be a BC break to document).
1386
        // Once checked, a mention in the doc that this path is relative to base-path (unless
1387
        // absolute).
1388
        // Check that the path is not provided if a banner is already provided.
1389
        if (isset($raw->{'banner-file'})) {
1390
            return canonicalize($raw->{'banner-file'});
1391
        }
1392
1393
        return null;
1394
    }
1395
1396
    private static function retrieveStubBannerFromFile(string $basePath, ?string $stubBannerPath): ?string
1397
    {
1398
        // TODO: Add checks
1399
        // TODO: The documentation is not clear enough IMO
1400
        if (null == $stubBannerPath) {
1401
            return null;
1402
        }
1403
1404
        $stubBannerPath = $basePath.DIRECTORY_SEPARATOR.$stubBannerPath;
1405
1406
        if (false === ($contents = @file_get_contents($stubBannerPath))) {
1407
            $errors = error_get_last();
1408
1409
            if (null === $errors) {
1410
                $errors = ['message' => 'failed to get contents of "'.$stubBannerPath.'""'];
1411
            }
1412
1413
            throw new InvalidArgumentException($errors['message']);
1414
        }
1415
1416
        return $contents;
1417
    }
1418
1419
    private static function retrieveStubPath(stdClass $raw): ?string
1420
    {
1421
        if (isset($raw->stub) && is_string($raw->stub)) {
1422
            return $raw->stub;
1423
        }
1424
1425
        return null;
1426
    }
1427
1428
    private static function retrieveIsExtractable(stdClass $raw): bool
1429
    {
1430
        // TODO: look it up, really not clear to me neither is the doc
1431
        if (isset($raw->extract)) {
1432
            return $raw->extract;
1433
        }
1434
1435
        return false;
1436
    }
1437
1438
    private static function retrieveIsInterceptFileFuncs(stdClass $raw): bool
1439
    {
1440
        if (isset($raw->intercept)) {
1441
            return $raw->intercept;
1442
        }
1443
1444
        return false;
1445
    }
1446
1447
    private static function retrieveIsPrivateKeyPrompt(stdClass $raw): bool
1448
    {
1449
        if (isset($raw->{'key-pass'})
1450
            && (true === $raw->{'key-pass'})) {
1451
            return true;
1452
        }
1453
1454
        return false;
1455
    }
1456
1457
    private static function retrieveIsStubGenerated(stdClass $raw): bool
1458
    {
1459
        if (isset($raw->stub) && (true === $raw->stub)) {
1460
            return true;
1461
        }
1462
1463
        return false;
1464
    }
1465
1466
    private static function retrieveIsWebPhar(stdClass $raw): bool
1467
    {
1468
        // TODO: doc is not clear enough
1469
        // Also check if is compatible web + CLI
1470
        if (isset($raw->web)) {
1471
            return $raw->web;
1472
        }
1473
1474
        return false;
1475
    }
1476
1477
    /**
1478
     * Runs a Git command on the repository.
1479
     *
1480
     * @param string $command the command
1481
     *
1482
     * @return string the trimmed output from the command
1483
     */
1484
    private static function runGitCommand(string $command, string $file): string
1485
    {
1486
        $path = dirname($file);
1487
1488
        $process = new Process($command, $path);
1489
1490
        if (0 === $process->run()) {
1491
            return trim($process->getOutput());
1492
        }
1493
1494
        throw new RuntimeException(
1495
            sprintf(
1496
                'The tag or commit hash could not be retrieved from "%s": %s',
1497
                $path,
1498
                $process->getErrorOutput()
1499
            )
1500
        );
1501
    }
1502
1503
    private static function createPhpCompactor(stdClass $raw): Compactor
1504
    {
1505
        // TODO: false === not set; check & add test/doc
1506
        $tokenizer = new Tokenizer();
1507
1508
        if (false === empty($raw->annotations) && isset($raw->annotations->ignore)) {
1509
            $tokenizer->ignore(
1510
                (array) $raw->annotations->ignore
1511
            );
1512
        }
1513
1514
        return new Php($tokenizer);
1515
    }
1516
}
1517