Completed
Pull Request — master (#31)
by Théo
02:18
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 const DIRECTORY_SEPARATOR;
21
use function explode;
22
use Herrera\Annotations\Tokenizer;
23
use Herrera\Box\Compactor\Php as LegacyPhp;
24
use Humbug\PhpScoper\Console\Configuration as PhpScoperConfiguration;
0 ignored issues
show
Bug introduced by
The type Humbug\PhpScoper\Console\Configuration was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
25
use function implode;
26
use InvalidArgumentException;
27
use function is_bool;
28
use function is_array;
29
use function is_string;
30
use KevinGH\Box\Compactor\Php;
31
use KevinGH\Box\Compactor\PhpScoper;
32
use Phar;
33
use RuntimeException;
34
use SplFileInfo;
35
use stdClass;
36
use Symfony\Component\Finder\Finder;
37
use Symfony\Component\Process\Process;
38
use function Humbug\PhpScoper\create_scoper;
0 ignored issues
show
introduced by
The function Humbug\PhpScoper\create_scoper was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
39
use function iter\chain;
40
use function KevinGH\Box\FileSystem\canonicalize;
41
use function KevinGH\Box\FileSystem\file_contents;
42
use function KevinGH\Box\FileSystem\is_absolute_path;
43
use function KevinGH\Box\FileSystem\make_path_absolute;
44
45
final class Configuration
46
{
47
    private const DEFAULT_ALIAS = 'default.phar';
48
    private const DEFAULT_DATETIME_FORMAT = 'Y-m-d H:i:s';
49
    private const DEFAULT_REPLACEMENT_SIGIL = '@';
50
    private const PHP_SCOPER_CONFIG = 'scoper.inc.php';
51
52
    private $fileMode;
53
    private $alias;
54
    private $basePathRetriever;
55
    private $files;
56
    private $binaryFiles;
57
    private $bootstrapFile;
58
    private $compactors;
59
    private $compressionAlgorithm;
60
    private $mainScriptPath;
61
    private $mainScriptContent;
62
    private $map;
63
    private $fileMapper;
64
    private $metadata;
65
    private $outputPath;
66
    private $privateKeyPassphrase;
67
    private $privateKeyPath;
68
    private $isPrivateKeyPrompt;
69
    private $processedReplacements;
70
    private $shebang;
71
    private $signingAlgorithm;
72
    private $stubBannerContents;
73
    private $stubBannerPath;
74
    private $stubPath;
75
    private $isInterceptFileFuncs;
76
    private $isStubGenerated;
77
78
    /**
79
     * @param string|null                   $alias
80
     * @param RetrieveRelativeBasePath $basePathRetriever     Utility to private the base path used and be able to retrieve a path relative to it (the base path)
81
     * @param SplFileInfo[]            $files                 List of files
82
     * @param SplFileInfo[]            $binaryFiles           List of binary files
83
     * @param null|string              $bootstrapFile         The bootstrap file path
84
     * @param Compactor[]              $compactors            List of file contents compactors
85
     * @param null|int                 $compressionAlgorithm  Compression algorithm constant value. See the \Phar class constants
86
     * @param null|int                 $fileMode              File mode in octal form
87
     * @param null|string              $mainScriptPath        The main script file path
88
     * @param null|string              $mainScriptContent     The processed content of the main script file
89
     * @param MapFile                  $fileMapper            Utility to map the files from outside and inside the PHAR
90
     * @param mixed                    $metadata              The PHAR Metadata
91
     * @param string                   $outputPath
92
     * @param null|string              $privateKeyPassphrase
93
     * @param null|string              $privateKeyPath
94
     * @param bool                     $isPrivateKeyPrompt    If the user should be prompted for the private key passphrase
95
     * @param array                    $processedReplacements The processed list of replacement placeholders and their values
96
     * @param null|string              $shebang               The shebang line
97
     * @param int                      $signingAlgorithm      The PHAR siging algorithm. See \Phar constants
98
     * @param string|null              $stubBannerContents            The stub banner comment
99
     * @param null|string              $stubBannerPath        The path to the stub banner comment file
100
     * @param null|string              $stubPath              The PHAR stub file path
101
     * @param bool                     $isInterceptFileFuncs  wether or not Phar::interceptFileFuncs() should be used
102
     * @param bool                     $isStubGenerated       Wether or not if the PHAR stub should be generated
103
     */
104
    private function __construct(
105
        ?string $alias,
106
        RetrieveRelativeBasePath $basePathRetriever,
107
        array $files,
108
        array $binaryFiles,
109
        ?string $bootstrapFile,
110
        array $compactors,
111
        ?int $compressionAlgorithm,
112
        ?int $fileMode,
113
        ?string $mainScriptPath,
114
        ?string $mainScriptContent,
115
        MapFile $fileMapper,
116
        $metadata,
117
        string $outputPath,
118
        ?string $privateKeyPassphrase,
119
        ?string $privateKeyPath,
120
        bool $isPrivateKeyPrompt,
121
        array $processedReplacements,
122
        ?string $shebang,
123
        int $signingAlgorithm,
124
        ?string $stubBannerContents,
125
        ?string $stubBannerPath,
126
        ?string $stubPath,
127
        bool $isInterceptFileFuncs,
128
        bool $isStubGenerated
129
    ) {
130
        Assertion::nullOrInArray(
131
            $compressionAlgorithm,
132
            get_phar_compression_algorithms(),
133
            sprintf(
134
                'Invalid compression algorithm "%%s", use one of "%s" instead.',
135
                implode('", "', array_keys(get_phar_compression_algorithms()))
136
            )
137
        );
138
139
        $this->alias = $alias;
140
        $this->basePathRetriever = $basePathRetriever;
141
        $this->files = $files;
142
        $this->binaryFiles = $binaryFiles;
143
        $this->bootstrapFile = $bootstrapFile;
144
        $this->compactors = $compactors;
145
        $this->compressionAlgorithm = $compressionAlgorithm;
146
        $this->fileMode = $fileMode;
147
        $this->mainScriptPath = $mainScriptPath;
148
        $this->mainScriptContent = $mainScriptContent;
149
        $this->fileMapper = $fileMapper;
150
        $this->metadata = $metadata;
151
        $this->outputPath = $outputPath;
152
        $this->privateKeyPassphrase = $privateKeyPassphrase;
153
        $this->privateKeyPath = $privateKeyPath;
154
        $this->isPrivateKeyPrompt = $isPrivateKeyPrompt;
155
        $this->processedReplacements = $processedReplacements;
156
        $this->shebang = $shebang;
157
        $this->signingAlgorithm = $signingAlgorithm;
158
        $this->stubBannerContents = $stubBannerContents;
159
        $this->stubBannerPath = $stubBannerPath;
160
        $this->stubPath = $stubPath;
161
        $this->isInterceptFileFuncs = $isInterceptFileFuncs;
162
        $this->isStubGenerated = $isStubGenerated;
163
    }
164
165
    public static function create(string $file, stdClass $raw): self
166
    {
167
        $alias = self::retrieveAlias($raw);
168
169
        $basePath = self::retrieveBasePath($file, $raw);
170
        $basePathRetriever = new RetrieveRelativeBasePath($basePath);
171
172
        $blacklistFilter = self::retrieveBlacklistFilter($raw, $basePath);
173
174
        $files = self::retrieveFiles($raw, 'files', $basePath);
175
        $directories = self::retrieveDirectories($raw, 'directories', $basePath, $blacklistFilter);
176
        $filesFromFinders = self::retrieveFilesFromFinders($raw, 'finder', $basePath, $blacklistFilter);
177
178
        $filesAggregate = array_unique(iterator_to_array(chain($files, $directories, ...$filesFromFinders)));
179
180
        $binaryFiles = self::retrieveFiles($raw, 'files-bin', $basePath);
181
        $binaryDirectories = self::retrieveDirectories($raw, 'directories-bin', $basePath, $blacklistFilter);
182
        $binaryFilesFromFinders = self::retrieveFilesFromFinders($raw, 'finder-bin', $basePath, $blacklistFilter);
183
184
        $binaryFilesAggregate = array_unique(iterator_to_array(chain($binaryFiles, $binaryDirectories, ...$binaryFilesFromFinders)));
185
186
        $bootstrapFile = self::retrieveBootstrapFile($raw, $basePath);
187
188
        $compactors = self::retrieveCompactors($raw, $basePath);
189
        $compressionAlgorithm = self::retrieveCompressionAlgorithm($raw);
190
191
        $fileMode = self::retrieveFileMode($raw);
192
193
        $mainScriptPath = self::retrieveMainScriptPath($raw, $basePath);
194
        $mainScriptContent = self::retrieveMainScriptContents($mainScriptPath);
195
196
        $map = self::retrieveMap($raw);
197
        $fileMapper = new MapFile($map);
198
199
        $metadata = self::retrieveMetadata($raw);
200
201
        $outputPath = self::retrieveOutputPath($raw, $file);
202
203
        $privateKeyPassphrase = self::retrievePrivateKeyPassphrase($raw);
204
        $privateKeyPath = self::retrievePrivateKeyPath($raw);
205
        $isPrivateKeyPrompt = self::retrieveIsPrivateKeyPrompt($raw);
206
207
        $replacements = self::retrieveReplacements($raw);
208
        $processedReplacements = self::retrieveProcessedReplacements($replacements, $raw, $file);
209
210
        $shebang = self::retrieveShebang($raw);
211
212
        $signingAlgorithm = self::retrieveSigningAlgorithm($raw);
213
214
        $stubBannerContents = self::retrieveStubBannerContents($raw);
215
        $stubBannerPath = self::retrieveStubBannerPath($raw, $basePath);
216
217
        if (null !== $stubBannerPath) {
218
            $stubBannerContents = file_contents($stubBannerPath);
219
        }
220
221
        $stubBannerContents = self::normalizeStubBannerContents($stubBannerContents);
222
223
        $stubPath = self::retrieveStubPath($raw, $basePath);
224
225
        $isInterceptFileFuncs = self::retrieveIsInterceptFileFuncs($raw);
226
        $isStubGenerated = self::retrieveIsStubGenerated($raw);
227
228
        return new self(
229
            $alias,
230
            $basePathRetriever,
231
            $filesAggregate,
232
            $binaryFilesAggregate,
233
            $bootstrapFile,
234
            $compactors,
235
            $compressionAlgorithm,
236
            $fileMode,
237
            $mainScriptPath,
238
            $mainScriptContent,
239
            $fileMapper,
240
            $metadata,
241
            $outputPath,
242
            $privateKeyPassphrase,
243
            $privateKeyPath,
244
            $isPrivateKeyPrompt,
245
            $processedReplacements,
246
            $shebang,
247
            $signingAlgorithm,
248
            $stubBannerContents,
249
            $stubBannerPath,
250
            $stubPath,
251
            $isInterceptFileFuncs,
252
            $isStubGenerated
253
        );
254
    }
255
256
    public function getBasePathRetriever(): RetrieveRelativeBasePath
257
    {
258
        return $this->basePathRetriever;
259
    }
260
261
    public function getAlias(): ?string
262
    {
263
        return $this->alias;
264
    }
265
266
    public function getBasePath(): string
267
    {
268
        return $this->basePathRetriever->getBasePath();
269
    }
270
271
    /**
272
     * @return SplFileInfo[]
273
     */
274
    public function getFiles(): array
275
    {
276
        return $this->files;
277
    }
278
279
    /**
280
     * @return SplFileInfo[]
281
     */
282
    public function getBinaryFiles(): array
283
    {
284
        return $this->binaryFiles;
285
    }
286
287
    public function getBootstrapFile(): ?string
288
    {
289
        return $this->bootstrapFile;
290
    }
291
292
    public function loadBootstrap(): void
293
    {
294
        $file = $this->bootstrapFile;
295
296
        if (null !== $file) {
297
            include $file;
298
        }
299
    }
300
301
    /**
302
     * @return Compactor[] the list of compactors
303
     */
304
    public function getCompactors(): array
305
    {
306
        return $this->compactors;
307
    }
308
309
    public function getCompressionAlgorithm(): ?int
310
    {
311
        return $this->compressionAlgorithm;
312
    }
313
314
    public function getFileMode(): ?int
315
    {
316
        return $this->fileMode;
317
    }
318
319
    public function getMainScriptPath(): ?string
320
    {
321
        return $this->mainScriptPath;
322
    }
323
324
    public function getMainScriptContent(): ?string
325
    {
326
        return $this->mainScriptContent;
327
    }
328
329
    public function getOutputPath(): string
330
    {
331
        return $this->outputPath;
332
    }
333
334
    /**
335
     * @return string[]
336
     */
337
    public function getMap(): array
338
    {
339
        return $this->fileMapper->getMap();
340
    }
341
342
    public function getFileMapper(): MapFile
343
    {
344
        return $this->fileMapper;
345
    }
346
347
    /**
348
     * @return mixed
349
     */
350
    public function getMetadata()
351
    {
352
        return $this->metadata;
353
    }
354
355
    public function getPrivateKeyPassphrase(): ?string
356
    {
357
        return $this->privateKeyPassphrase;
358
    }
359
360
    public function getPrivateKeyPath(): ?string
361
    {
362
        return $this->privateKeyPath;
363
    }
364
365
    public function isPrivateKeyPrompt(): bool
366
    {
367
        return $this->isPrivateKeyPrompt;
368
    }
369
370
    public function getProcessedReplacements(): array
371
    {
372
        return $this->processedReplacements;
373
    }
374
375
    public function getShebang(): ?string
376
    {
377
        return $this->shebang;
378
    }
379
380
    public function getSigningAlgorithm(): int
381
    {
382
        return $this->signingAlgorithm;
383
    }
384
385
    public function getStubBannerContents(): ?string
386
    {
387
        return $this->stubBannerContents;
388
    }
389
390
    public function getStubBannerPath(): ?string
391
    {
392
        return $this->stubBannerPath;
393
    }
394
395
    public function getStubPath(): ?string
396
    {
397
        return $this->stubPath;
398
    }
399
400
    public function isInterceptFileFuncs(): bool
401
    {
402
        return $this->isInterceptFileFuncs;
403
    }
404
405
    public function isStubGenerated(): bool
406
    {
407
        return $this->isStubGenerated;
408
    }
409
410
    private static function retrieveAlias(stdClass $raw): ?string
411
    {
412
        if (false === isset($raw->alias)) {
413
            return null;
414
        }
415
416
        $alias = trim($raw->alias);
417
418
        Assertion::notEmpty($alias, 'A PHAR alias cannot be empty when provided.');
419
420
        return $alias;
421
    }
422
423
    private static function retrieveBasePath(string $file, stdClass $raw): string
424
    {
425
        if (false === isset($raw->{'base-path'})) {
426
            return realpath(dirname($file));
427
        }
428
429
        $basePath = trim($raw->{'base-path'});
430
431
        Assertion::directory(
432
            $basePath,
433
            'The base path "%s" is not a directory or does not exist.'
434
        );
435
436
        return realpath($basePath);
437
    }
438
439
    /**
440
     * @param stdClass $raw
441
     * @param string   $basePath
442
     *
443
     * @return Closure
444
     */
445
    private static function retrieveBlacklistFilter(stdClass $raw, string $basePath): Closure
446
    {
447
        $blacklist = self::retrieveBlacklist($raw, $basePath);
448
449
        return function (SplFileInfo $file) use ($blacklist): ?bool {
450
            if (in_array($file->getRealPath(), $blacklist, true)) {
451
                return false;
452
            }
453
454
            return null;
455
        };
456
    }
457
458
    /**
459
     * @return string[]
460
     */
461
    private static function retrieveBlacklist(stdClass $raw, string $basePath): array
462
    {
463
        if (false === isset($raw->blacklist)) {
464
            return [];
465
        }
466
467
        $blacklist = $raw->blacklist;
468
469
        $normalizePath = function ($file) use ($basePath): string {
470
            return self::normalizeFilePath($file, $basePath);
471
        };
472
473
        return array_map($normalizePath, $blacklist);
474
    }
475
476
    /**
477
     * @param stdClass $raw
478
     * @param string   $key      Config property name
479
     * @param string   $basePath
480
     *
481
     * @return SplFileInfo[]
482
     */
483
    private static function retrieveFiles(stdClass $raw, string $key, string $basePath): array
484
    {
485
        if (false === isset($raw->{$key})) {
486
            return [];
487
        }
488
489
        $files = (array) $raw->{$key};
490
491
        Assertion::allString($files);
492
493
        $normalizePath = function (string $file) use ($basePath, $key): SplFileInfo {
494
            $file = self::normalizeFilePath($file, $basePath);
495
496
            Assertion::file(
497
                $file,
498
                sprintf(
499
                    '"%s" must contain a list of existing files. Could not find "%%s".',
500
                    $key
501
                )
502
            );
503
504
            return new SplFileInfo($file);
505
        };
506
507
        return array_map($normalizePath, $files);
508
    }
509
510
    /**
511
     * @param stdClass $raw
512
     * @param string   $key             Config property name
513
     * @param string   $basePath
514
     * @param Closure  $blacklistFilter
515
     *
516
     * @return iterable|SplFileInfo[]
517
     */
518
    private static function retrieveDirectories(stdClass $raw, string $key, string $basePath, Closure $blacklistFilter): iterable
519
    {
520
        $directories = self::retrieveDirectoryPaths($raw, $key, $basePath);
521
522
        if ([] !== $directories) {
523
            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...
524
                ->files()
525
                ->filter($blacklistFilter)
526
                ->ignoreVCS(true)
527
                ->in($directories)
528
            ;
529
        }
530
531
        return [];
532
    }
533
534
    /**
535
     * @param stdClass $raw
536
     * @param string   $basePath
537
     * @param Closure  $blacklistFilter
538
     *
539
     * @return iterable[]|SplFileInfo[][]
540
     */
541
    private static function retrieveFilesFromFinders(stdClass $raw, string $key, string $basePath, Closure $blacklistFilter): array
542
    {
543
        if (isset($raw->{$key})) {
544
            return self::processFinders($raw->{$key}, $basePath, $blacklistFilter);
545
        }
546
547
        return [];
548
    }
549
550
    /**
551
     * @param array   $findersConfig
552
     * @param string  $basePath
553
     * @param Closure $blacklistFilter
554
     *
555
     * @return Finder[]|SplFileInfo[][]
556
     */
557
    private static function processFinders(array $findersConfig, string $basePath, Closure $blacklistFilter): array
558
    {
559
        $processFinderConfig = function (stdClass $config) use ($basePath, $blacklistFilter) {
560
            return self::processFinder($config, $basePath, $blacklistFilter);
561
        };
562
563
        return array_map($processFinderConfig, $findersConfig);
564
    }
565
566
    /**
567
     * @param stdClass $config
568
     * @param string $basePath
569
     * @param Closure $blacklistFilter
570
     *
571
     * @return Finder
572
     */
573
    private static function processFinder(stdClass $config, string $basePath, Closure $blacklistFilter): Finder
574
    {
575
        $finder = Finder::create()
576
            ->files()
577
            ->filter($blacklistFilter)
578
            ->ignoreVCS(true)
579
        ;
580
581
        $normalizedConfig = (function (array $config, Finder $finder): array {
582
            $normalizedConfig = [];
583
584
            foreach ($config as $method => $arguments) {
585
                $method = trim($method);
586
                $arguments = (array) $arguments;
587
588
                Assertion::methodExists(
589
                    $method,
590
                    $finder,
591
                    'The method "Finder::%s" does not exist.'
592
                );
593
594
                $normalizedConfig[$method] = $arguments;
595
            }
596
597
            krsort($normalizedConfig);
598
599
            return $normalizedConfig;
600
        })((array) $config, $finder);
601
602
        $createNormalizedDirectories = function (string $directory) use ($basePath): string {
603
            $directory = self::normalizeDirectoryPath($directory, $basePath);
604
605
            Assertion::directory($directory);
606
607
            return $directory;
608
        };
609
610
        $normalizeFileOrDirectory = function (string &$fileOrDirectory) use ($basePath): void {
611
            $fileOrDirectory = self::normalizeDirectoryPath($fileOrDirectory, $basePath);
612
613
            if (false === file_exists($fileOrDirectory)) {
614
                throw new InvalidArgumentException(
615
                    sprintf(
616
                        'Path "%s" was expected to be a file or directory.',
617
                        $fileOrDirectory
618
                    )
619
                );
620
            }
621
622
            //TODO: add fileExists (as file or directory) to Assert
623
            if (false === is_file($fileOrDirectory)) {
624
                Assertion::directory($fileOrDirectory);
625
            } else {
626
                Assertion::file($fileOrDirectory);
627
            }
628
        };
629
630
        foreach ($normalizedConfig as $method => $arguments) {
631
            if ('in' === $method) {
632
                $normalizedConfig[$method] = $arguments = array_map($createNormalizedDirectories, $arguments);
633
            }
634
635
            if ('exclude' === $method) {
636
                $arguments = array_unique(array_map('trim', $arguments));
637
            }
638
639
            if ('append' === $method) {
640
                array_walk($arguments, $normalizeFileOrDirectory);
641
642
                $arguments = [$arguments];
643
            }
644
645
            foreach ($arguments as $argument) {
646
                $finder->$method($argument);
647
            }
648
        }
649
650
        return $finder;
651
    }
652
653
    /**
654
     * @param stdClass $raw
655
     * @param string   $key      Config property name
656
     * @param string   $basePath
657
     *
658
     * @return string[]
659
     */
660
    private static function retrieveDirectoryPaths(stdClass $raw, string $key, string $basePath): array
661
    {
662
        if (false === isset($raw->{$key})) {
663
            return [];
664
        }
665
666
        $directories = $raw->{$key};
667
668
        $normalizeDirectory = function (string $directory) use ($basePath, $key): string {
669
            $directory = self::normalizeDirectoryPath($directory, $basePath);
670
671
            Assertion::directory(
672
                $directory,
673
                sprintf(
674
                    '"%s" must contain a list of existing directories. Could not find "%%s".',
675
                    $key
676
                )
677
            );
678
679
            return $directory;
680
        };
681
682
        return array_map($normalizeDirectory, $directories);
683
    }
684
685
    private static function normalizeFilePath(string $file, string $basePath): string
686
    {
687
        return make_path_absolute(trim($file), $basePath);
688
    }
689
690
    private static function normalizeDirectoryPath(string $directory, string $basePath): string
691
    {
692
        return make_path_absolute(trim($directory), $basePath);
693
    }
694
695
    private static function retrieveBootstrapFile(stdClass $raw, string $basePath): ?string
696
    {
697
        // TODO: deprecate its usage & document this BC break. Compactors will not be configurable
698
        // through that extension point so this is pretty much useless unless proven otherwise.
699
        if (false === isset($raw->bootstrap)) {
700
            return null;
701
        }
702
703
        $file = self::normalizeFilePath($raw->bootstrap, $basePath);
704
705
        Assertion::file($file, 'The bootstrap path "%s" is not a file or does not exist.');
706
707
        return $file;
708
    }
709
710
    /**
711
     * @return Compactor[]
712
     */
713
    private static function retrieveCompactors(stdClass $raw, string $basePath): array
714
    {
715
        // TODO: only accept arrays when set unlike the doc says (it allows a string).
716
        if (false === isset($raw->compactors)) {
717
            return [];
718
        }
719
720
        $compactorClasses = array_unique((array) $raw->compactors);
721
722
        return array_map(
723
            function (string $class) use ($raw, $basePath): Compactor {
724
                Assertion::classExists($class, 'The compactor class "%s" does not exist.');
725
                Assertion::implementsInterface($class, Compactor::class, 'The class "%s" is not a compactor class.');
726
727
                if (Php::class === $class || LegacyPhp::class === $class) {
728
                    return self::createPhpCompactor($raw);
729
                }
730
731
                if (PhpScoper::class === $class) {
732
                    $phpScoperConfig = self::retrievePhpScoperConfig($raw, $basePath);
733
734
                    return new PhpScoper(create_scoper(), $phpScoperConfig);
0 ignored issues
show
Bug introduced by
The function create_scoper was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

734
                    return new PhpScoper(/** @scrutinizer ignore-call */ create_scoper(), $phpScoperConfig);
Loading history...
735
                }
736
737
                return new $class();
738
            },
739
            $compactorClasses
740
        );
741
    }
742
743
    private static function retrieveCompressionAlgorithm(stdClass $raw): ?int
744
    {
745
        // TODO: if in dev mode (when added), do not comment about the compression.
746
        // If not, add a warning to notify the user if no compression algorithm is used
747
        // provided the PHAR is not configured for web purposes.
748
        // If configured for the web, add a warning when a compression algorithm is used
749
        // as this can result in an overhead. Add a doc link explaining this.
750
        //
751
        // Unlike the doc: do not accept integers and document this BC break.
752
        if (false === isset($raw->compression)) {
753
            return null;
754
        }
755
756
        if (false === is_string($raw->compression)) {
757
            Assertion::integer(
758
                $raw->compression,
759
                'Expected compression to be an algorithm name, found %s instead.'
760
            );
761
762
            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...
763
        }
764
765
        $knownAlgorithmNames = array_keys(get_phar_compression_algorithms());
766
767
        Assertion::inArray(
768
            $raw->compression,
769
            $knownAlgorithmNames,
770
            sprintf(
771
                'Invalid compression algorithm "%%s", use one of "%s" instead.',
772
                implode('", "', $knownAlgorithmNames)
773
            )
774
        );
775
776
        $value = get_phar_compression_algorithms()[$raw->compression];
777
778
        // Phar::NONE is not valid for compressFiles()
779
        if (Phar::NONE === $value) {
780
            return null;
781
        }
782
783
        return $value;
784
    }
785
786
    private static function retrieveFileMode(stdClass $raw): ?int
787
    {
788
        if (isset($raw->chmod)) {
789
            return intval($raw->chmod, 8);
790
        }
791
792
        return null;
793
    }
794
795
    private static function retrieveMainScriptPath(stdClass $raw, string $basePath): ?string
796
    {
797
        if (isset($raw->main)) {
798
//            return canonicalize($raw->main);
799
            return make_path_absolute($raw->main, $basePath);
800
        }
801
802
        return null;
803
    }
804
805
    private static function retrieveMainScriptContents(?string $mainScriptPath): ?string
806
    {
807
        if (null === $mainScriptPath) {
808
            return null;
809
        }
810
811
        $contents = file_contents($mainScriptPath);
812
813
        // Remove the shebang line
814
        return preg_replace('/^#!.*\s*/', '', $contents);
815
    }
816
817
    /**
818
     * @return string[][]
819
     */
820
    private static function retrieveMap(stdClass $raw): array
821
    {
822
        if (false === isset($raw->map)) {
823
            return [];
824
        }
825
826
        $map = [];
827
828
        foreach ((array) $raw->map as $item) {
829
            $processed = [];
830
831
            foreach ($item as $match => $replace) {
832
                $processed[canonicalize(trim($match))] = canonicalize(trim($replace));
833
            }
834
835
            if (isset($processed['_empty_'])) {
836
                $processed[''] = $processed['_empty_'];
837
838
                unset($processed['_empty_']);
839
            }
840
841
            $map[] = $processed;
842
        }
843
844
        return $map;
845
    }
846
847
    /**
848
     * @return mixed
849
     */
850
    private static function retrieveMetadata(stdClass $raw)
851
    {
852
        // TODO: the doc currently say this can be any value; check if true
853
        // and if not add checks accordingly
854
        //
855
        // Also review the doc as I don't find it very helpful...
856
        if (isset($raw->metadata)) {
857
            if (is_object($raw->metadata)) {
858
                return (array) $raw->metadata;
859
            }
860
861
            return $raw->metadata;
862
        }
863
864
        return null;
865
    }
866
867
    private static function retrieveOutputPath(stdClass $raw, string $file): string
868
    {
869
        // TODO: make this path relative to the base path like everything else
870
        // otherwise this is really confusing. This is a BC break that needs to be
871
        // documented though (and update the doc accordingly as well)
872
        $base = getcwd().DIRECTORY_SEPARATOR;
873
874
        if (isset($raw->output)) {
875
            $path = $raw->output;
876
877
            if (false === is_absolute_path($path)) {
878
                $path = canonicalize($base.$path);
879
            }
880
        } else {
881
            $path = $base.self::DEFAULT_ALIAS;
882
        }
883
884
        if (false !== strpos($path, '@'.'git-version@')) {
885
            $gitVersion = self::retrieveGitVersion($file);
886
887
            $path = str_replace('@'.'git-version@', $gitVersion, $path);
888
        }
889
890
        return $path;
891
    }
892
893
    private static function retrievePrivateKeyPassphrase(stdClass $raw): ?string
894
    {
895
        // TODO: add check to not allow this setting without the private key path
896
        if (isset($raw->{'key-pass'})
897
            && is_string($raw->{'key-pass'})
898
        ) {
899
            return $raw->{'key-pass'};
900
        }
901
902
        return null;
903
    }
904
905
    private static function retrievePrivateKeyPath(stdClass $raw): ?string
906
    {
907
        // TODO: If passed need to check its existence
908
        // Also need
909
910
        if (isset($raw->key)) {
911
            return $raw->key;
912
        }
913
914
        return null;
915
    }
916
917
    private static function retrieveReplacements(stdClass $raw): array
918
    {
919
        // TODO: add exmample in the doc
920
        // Add checks against the values
921
        if (isset($raw->replacements)) {
922
            return (array) $raw->replacements;
923
        }
924
925
        return [];
926
    }
927
928
    private static function retrieveProcessedReplacements(
929
        array $replacements,
930
        stdClass $raw,
931
        string $file
932
    ): array {
933
        if (null !== ($git = self::retrieveGitHashPlaceholder($raw))) {
934
            $replacements[$git] = self::retrieveGitHash($file);
935
        }
936
937
        if (null !== ($git = self::retrieveGitShortHashPlaceholder($raw))) {
938
            $replacements[$git] = self::retrieveGitHash($file, true);
939
        }
940
941
        if (null !== ($git = self::retrieveGitTagPlaceholder($raw))) {
942
            $replacements[$git] = self::retrieveGitTag($file);
943
        }
944
945
        if (null !== ($git = self::retrieveGitVersionPlaceholder($raw))) {
946
            $replacements[$git] = self::retrieveGitVersion($file);
947
        }
948
949
        if (null !== ($date = self::retrieveDatetimeNowPlaceHolder($raw))) {
950
            $replacements[$date] = self::retrieveDatetimeNow(
951
                self::retrieveDatetimeFormat($raw)
952
            );
953
        }
954
955
        $sigil = self::retrieveReplacementSigil($raw);
956
957
        foreach ($replacements as $key => $value) {
958
            unset($replacements[$key]);
959
            $replacements["$sigil$key$sigil"] = $value;
960
        }
961
962
        return $replacements;
963
    }
964
965
    private static function retrieveGitHashPlaceholder(stdClass $raw): ?string
966
    {
967
        if (isset($raw->{'git-commit'})) {
968
            return $raw->{'git-commit'};
969
        }
970
971
        return null;
972
    }
973
974
    /**
975
     * @param string $file
976
     * @param bool   $short Use the short version
977
     *
978
     * @return string the commit hash
979
     */
980
    private static function retrieveGitHash(string $file, bool $short = false): string
981
    {
982
        return self::runGitCommand(
983
            sprintf(
984
                'git log --pretty="%s" -n1 HEAD',
985
                $short ? '%h' : '%H'
986
            ),
987
            $file
988
        );
989
    }
990
991
    private static function retrieveGitShortHashPlaceholder(stdClass $raw): ?string
992
    {
993
        if (isset($raw->{'git-commit-short'})) {
994
            return $raw->{'git-commit-short'};
995
        }
996
997
        return null;
998
    }
999
1000
    private static function retrieveGitTagPlaceholder(stdClass $raw): ?string
1001
    {
1002
        if (isset($raw->{'git-tag'})) {
1003
            return $raw->{'git-tag'};
1004
        }
1005
1006
        return null;
1007
    }
1008
1009
    private static function retrieveGitTag(string $file): ?string
1010
    {
1011
        return self::runGitCommand('git describe --tags HEAD', $file);
1012
    }
1013
1014
    private static function retrieveGitVersionPlaceholder(stdClass $raw): ?string
1015
    {
1016
        if (isset($raw->{'git-version'})) {
1017
            return $raw->{'git-version'};
1018
        }
1019
1020
        return null;
1021
    }
1022
1023
    private static function retrieveGitVersion(string $file): ?string
1024
    {
1025
        // TODO: check if is still relevant as IMO we are better off using OcramiusVersionPackage
1026
        // to avoid messing around with that
1027
1028
        try {
1029
            return self::retrieveGitTag($file);
1030
        } catch (RuntimeException $exception) {
1031
            try {
1032
                return self::retrieveGitHash($file, true);
1033
            } catch (RuntimeException $exception) {
1034
                throw new RuntimeException(
1035
                    sprintf(
1036
                        'The tag or commit hash could not be retrieved from "%s": %s',
1037
                        dirname($file),
1038
                        $exception->getMessage()
1039
                    ),
1040
                    0,
1041
                    $exception
1042
                );
1043
            }
1044
        }
1045
    }
1046
1047
    private static function retrieveDatetimeNowPlaceHolder(stdClass $raw): ?string
1048
    {
1049
        // TODO: double check why this is done and how it is used it's not completely clear to me.
1050
        // Also make sure the documentation is up to date after.
1051
        // Instead of having two sistinct doc entries for `datetime` and `datetime-format`, it would
1052
        // be better to have only one element IMO like:
1053
        //
1054
        // "datetime": {
1055
        //   "value": "val",
1056
        //   "format": "Y-m-d"
1057
        // }
1058
        //
1059
        // Also add a check that one cannot be provided without the other. Or maybe it should? I guess
1060
        // if the datetime format is the default one it's ok; but in any case the format should not
1061
        // be added without the datetime value...
1062
1063
        if (isset($raw->{'datetime'})) {
1064
            return $raw->{'datetime'};
1065
        }
1066
1067
        return null;
1068
    }
1069
1070
    private static function retrieveDatetimeNow(string $format)
1071
    {
1072
        $now = new DateTimeImmutable('now');
1073
1074
        $datetime = $now->format($format);
1075
1076
        if (!$datetime) {
1077
            throw new InvalidArgumentException(
1078
                sprintf(
1079
                    '""%s" is not a valid PHP date format',
1080
                    $format
1081
                )
1082
            );
1083
        }
1084
1085
        return $datetime;
1086
    }
1087
1088
    private static function retrieveDatetimeFormat(stdClass $raw): string
1089
    {
1090
        if (isset($raw->{'datetime_format'})) {
1091
            return $raw->{'datetime_format'};
1092
        }
1093
1094
        return self::DEFAULT_DATETIME_FORMAT;
1095
    }
1096
1097
    private static function retrieveReplacementSigil(stdClass $raw)
1098
    {
1099
        if (isset($raw->{'replacement-sigil'})) {
1100
            return $raw->{'replacement-sigil'};
1101
        }
1102
1103
        return self::DEFAULT_REPLACEMENT_SIGIL;
1104
    }
1105
1106
    private static function retrieveShebang(stdClass $raw): ?string
1107
    {
1108
        if (false === isset($raw->shebang)) {
1109
            return null;
1110
        }
1111
1112
        $shebang = trim($raw->shebang);
1113
1114
        Assertion::notEmpty($shebang, 'The shebang should not be empty.');
1115
        Assertion::true(
1116
            '#!' === substr($shebang, 0, 2),
1117
            sprintf(
1118
                'The shebang line must start with "#!". Got "%s" instead',
1119
                $shebang
1120
            )
1121
        );
1122
1123
        return $shebang;
1124
    }
1125
1126
    private static function retrieveSigningAlgorithm(stdClass $raw): int
1127
    {
1128
        // TODO: trigger warning: if no signing algorithm is given provided we are not in dev mode
1129
        // TODO: trigger a warning if the signing algorithm used is weak
1130
        // TODO: no longer accept strings & document BC break
1131
        if (false === isset($raw->algorithm)) {
1132
            return Phar::SHA1;
1133
        }
1134
1135
        if (is_string($raw->algorithm)) {
1136
            if (false === defined('Phar::'.$raw->algorithm)) {
1137
                throw new InvalidArgumentException(
1138
                    sprintf(
1139
                        'The signing algorithm "%s" is not supported.',
1140
                        $raw->algorithm
1141
                    )
1142
                );
1143
            }
1144
1145
            return constant('Phar::'.$raw->algorithm);
1146
        }
1147
1148
        return $raw->algorithm;
1149
    }
1150
1151
    private static function retrieveStubBannerContents(stdClass $raw): ?string
1152
    {
1153
        if (false === isset($raw->banner)) {
1154
            return null;
1155
        }
1156
1157
        $banner = $raw->banner;
1158
1159
        if (is_array($banner)) {
1160
            $banner = implode("\n", $banner);
1161
        }
1162
1163
        return $banner;
1164
    }
1165
1166
    private static function retrieveStubBannerPath(stdClass $raw, string $basePath): ?string
1167
    {
1168
        if (false === isset($raw->{'banner-file'})) {
1169
            return null;
1170
        }
1171
1172
        $bannerFile = make_path_absolute($raw->{'banner-file'}, $basePath);
1173
1174
        Assertion::file($bannerFile);
1175
1176
        return $bannerFile;
1177
    }
1178
1179
    private static function normalizeStubBannerContents(?string $contents): ?string
1180
    {
1181
        if (null === $contents) {
1182
            return null;
1183
        }
1184
1185
        $banner = explode("\n", $contents);
1186
        $banner = array_map('trim', $banner);
1187
1188
        return implode("\n", $banner);
1189
    }
1190
1191
    private static function retrieveStubPath(stdClass $raw, string $basePath): ?string
1192
    {
1193
        if (isset($raw->stub) && is_string($raw->stub)) {
1194
            $stubPath = make_path_absolute($raw->stub, $basePath);
1195
1196
            Assertion::file($stubPath);
1197
1198
            return $stubPath;
1199
        }
1200
1201
        return null;
1202
    }
1203
1204
    private static function retrieveIsInterceptFileFuncs(stdClass $raw): bool
1205
    {
1206
        if (isset($raw->intercept)) {
1207
            return $raw->intercept;
1208
        }
1209
1210
        return false;
1211
    }
1212
1213
    private static function retrieveIsPrivateKeyPrompt(stdClass $raw): bool
1214
    {
1215
        if (isset($raw->{'key-pass'})
1216
            && (true === $raw->{'key-pass'})) {
1217
            return true;
1218
        }
1219
1220
        return false;
1221
    }
1222
1223
    private static function retrieveIsStubGenerated(stdClass $raw): bool
1224
    {
1225
        if (isset($raw->stub) && (true === $raw->stub)) {
1226
            return true;
1227
        }
1228
1229
        return false;
1230
    }
1231
1232
    private static function retrievePhpScoperConfig(stdClass $raw, string $basePath): PhpScoperConfiguration
1233
    {
1234
        if (!isset($raw->{'php-scoper'})) {
1235
            $configFilePath = $basePath.DIRECTORY_SEPARATOR.self::PHP_SCOPER_CONFIG;
1236
1237
             return file_exists($configFilePath)
1238
                ? PhpScoperConfiguration::load($configFilePath)
1239
                : PhpScoperConfiguration::load()
1240
             ;
1241
        }
1242
1243
        $configFile = $raw->phpScoper;
1244
1245
        Assertion::string($configFile);
1246
1247
        if (false === self::$fileSystem->isAbsolutePath($configFile)) {
0 ignored issues
show
Bug Best Practice introduced by
The property fileSystem does not exist on KevinGH\Box\Configuration. Did you maybe forget to declare it?
Loading history...
1248
            $configFilePath = $basePath.DIRECTORY_SEPARATOR.$configFile;
1249
        } else {
1250
            $configFilePath = $configFile;
1251
        }
1252
1253
        Assertion::file($configFilePath);
1254
        Assertion::readable($configFilePath);
1255
1256
        return PhpScoperConfiguration::load($configFilePath);
1257
    }
1258
1259
    /**
1260
     * Runs a Git command on the repository.
1261
     *
1262
     * @param string $command the command
1263
     *
1264
     * @return string the trimmed output from the command
1265
     */
1266
    private static function runGitCommand(string $command, string $file): string
1267
    {
1268
        $path = dirname($file);
1269
1270
        $process = new Process($command, $path);
1271
1272
        if (0 === $process->run()) {
1273
            return trim($process->getOutput());
1274
        }
1275
1276
        throw new RuntimeException(
1277
            sprintf(
1278
                'The tag or commit hash could not be retrieved from "%s": %s',
1279
                $path,
1280
                $process->getErrorOutput()
1281
            )
1282
        );
1283
    }
1284
1285
    private static function createPhpCompactor(stdClass $raw): Compactor
1286
    {
1287
        // TODO: false === not set; check & add test/doc
1288
        $tokenizer = new Tokenizer();
1289
1290
        if (false === empty($raw->annotations) && isset($raw->annotations->ignore)) {
1291
            $tokenizer->ignore(
1292
                (array) $raw->annotations->ignore
1293
            );
1294
        }
1295
1296
        return new Php($tokenizer);
1297
    }
1298
}
1299