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

Configuration::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 60
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

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