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