Passed
Push — master ( 00b67f...9b5713 )
by Théo
02:00
created

Configuration::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 59
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

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