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