Passed
Pull Request — master (#70)
by Théo
02:18 queued 19s
created

Configuration::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 62
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 62
rs 9.4743
c 0
b 0
f 0
cc 2
eloc 32
nc 2
nop 24

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