Completed
Pull Request — master (#32)
by Théo
02:37
created

Configuration::retrieveMapper()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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