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