Passed
Push — master ( 5154e0...001968 )
by Théo
02:07
created

Configuration::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 67
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 67
rs 9.2815
c 0
b 0
f 0
cc 1
eloc 34
nc 1
nop 28

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 function array_values;
18
use Assert\Assertion;
19
use Closure;
20
use DateTimeImmutable;
21
use Herrera\Annotations\Tokenizer;
22
use Herrera\Box\Compactor\Php as LegacyPhp;
23
use Humbug\PhpScoper\Configuration as PhpScoperConfiguration;
24
use InvalidArgumentException;
25
use function iter\flatMap;
26
use function iter\values;
27
use KevinGH\Box\Compactor\Php;
28
use KevinGH\Box\Compactor\PhpScoper as PhpScoperCompactor;
29
use KevinGH\Box\Composer\ComposerConfiguration;
30
use KevinGH\Box\Json\Json;
31
use KevinGH\Box\PhpScoper\SimpleScoper;
32
use Phar;
33
use RuntimeException;
34
use Seld\JsonLint\ParsingException;
35
use SplFileInfo;
36
use stdClass;
37
use Symfony\Component\Finder\Finder;
38
use Symfony\Component\Process\Process;
39
use function array_filter;
40
use function array_key_exists;
41
use function array_map;
42
use function array_merge;
43
use function array_unique;
44
use function file_exists;
45
use function Humbug\PhpScoper\create_scoper;
46
use function is_file;
47
use function is_readable;
48
use function iter\chain;
49
use function iter\fn\method;
50
use function iter\map;
51
use function iter\toArray;
52
use function iterator_to_array;
53
use function KevinGH\Box\FileSystem\canonicalize;
54
use function KevinGH\Box\FileSystem\file_contents;
55
use function KevinGH\Box\FileSystem\longest_common_base_path;
56
use function KevinGH\Box\FileSystem\make_path_absolute;
57
use function KevinGH\Box\FileSystem\make_path_relative;
58
use function preg_match;
59
use function substr;
60
use function uniqid;
61
62
/**
63
 * @private
64
 */
65
final class Configuration
66
{
67
    private const DEFAULT_ALIAS = 'test.phar';
68
    private const DEFAULT_MAIN_SCRIPT = 'index.php';
69
    private const DEFAULT_DATETIME_FORMAT = 'Y-m-d H:i:s';
70
    private const DEFAULT_REPLACEMENT_SIGIL = '@';
71
    private const DEFAULT_SHEBANG = '#!/usr/bin/env php';
72
    private const DEFAULT_BANNER = <<<'BANNER'
73
Generated by Humbug Box.
74
75
@link https://github.com/humbug/box
76
BANNER;
77
    private const FILES_SETTINGS = [
78
        'files',
79
        'files-bin',
80
        'directories',
81
        'directories-bin',
82
        'finder',
83
        'finder-bin',
84
    ];
85
    private const PHP_SCOPER_CONFIG = 'scoper.inc.php';
86
87
    private $file;
88
    private $fileMode;
89
    private $alias;
90
    private $basePath;
91
    private $composerJson;
92
    private $composerLock;
93
    private $files;
94
    private $binaryFiles;
95
    private $compactors;
96
    private $compressionAlgorithm;
97
    private $mainScriptPath;
98
    private $mainScriptContents;
99
    private $map;
100
    private $fileMapper;
101
    private $metadata;
102
    private $tmpOutputPath;
103
    private $outputPath;
104
    private $privateKeyPassphrase;
105
    private $privateKeyPath;
106
    private $isPrivateKeyPrompt;
107
    private $processedReplacements;
108
    private $shebang;
109
    private $signingAlgorithm;
110
    private $stubBannerContents;
111
    private $stubBannerPath;
112
    private $stubPath;
113
    private $isInterceptFileFuncs;
114
    private $isStubGenerated;
115
    private $checkRequirements;
116
117
    /**
118
     * @param null|string     $file
119
     * @param null|string     $alias
120
     * @param string          $basePath              Utility to private the base path used and be able to retrieve a path relative to it (the base path)
121
     * @param null[]|string[] $composerJson
122
     * @param null[]|string[] $composerLock
123
     * @param SplFileInfo[]   $files                 List of files
124
     * @param SplFileInfo[]   $binaryFiles           List of binary files
125
     * @param Compactor[]     $compactors            List of file contents compactors
126
     * @param null|int        $compressionAlgorithm  Compression algorithm constant value. See the \Phar class constants
127
     * @param null|int        $fileMode              File mode in octal form
128
     * @param string          $mainScriptPath        The main script file path
129
     * @param string          $mainScriptContents    The processed content of the main script file
130
     * @param MapFile         $fileMapper            Utility to map the files from outside and inside the PHAR
131
     * @param mixed           $metadata              The PHAR Metadata
132
     * @param string          $tmpOutputPath
133
     * @param string          $outputPath
134
     * @param null|string     $privateKeyPassphrase
135
     * @param null|string     $privateKeyPath
136
     * @param bool            $isPrivateKeyPrompt    If the user should be prompted for the private key passphrase
137
     * @param array           $processedReplacements The processed list of replacement placeholders and their values
138
     * @param null|string     $shebang               The shebang line
139
     * @param int             $signingAlgorithm      The PHAR siging algorithm. See \Phar constants
140
     * @param null|string     $stubBannerContents    The stub banner comment
141
     * @param null|string     $stubBannerPath        The path to the stub banner comment file
142
     * @param null|string     $stubPath              The PHAR stub file path
143
     * @param bool            $isInterceptFileFuncs  Whether or not Phar::interceptFileFuncs() should be used
144
     * @param bool            $isStubGenerated       Whether or not if the PHAR stub should be generated
145
     * @param bool            $checkRequirements     Whether the PHAR will check the application requirements before running
146
     */
147
    private function __construct(
148
        ?string $file,
149
        string $alias,
150
        string $basePath,
151
        array $composerJson,
152
        array $composerLock,
153
        array $files,
154
        array $binaryFiles,
155
        array $compactors,
156
        ?int $compressionAlgorithm,
157
        ?int $fileMode,
158
        string $mainScriptPath,
159
        string $mainScriptContents,
160
        MapFile $fileMapper,
161
        $metadata,
162
        string $tmpOutputPath,
163
        string $outputPath,
164
        ?string $privateKeyPassphrase,
165
        ?string $privateKeyPath,
166
        bool $isPrivateKeyPrompt,
167
        array $processedReplacements,
168
        ?string $shebang,
169
        int $signingAlgorithm,
170
        ?string $stubBannerContents,
171
        ?string $stubBannerPath,
172
        ?string $stubPath,
173
        bool $isInterceptFileFuncs,
174
        bool $isStubGenerated,
175
        bool $checkRequirements
176
    ) {
177
        Assertion::nullOrInArray(
178
            $compressionAlgorithm,
179
            get_phar_compression_algorithms(),
180
            sprintf(
181
                'Invalid compression algorithm "%%s", use one of "%s" instead.',
182
                implode('", "', array_keys(get_phar_compression_algorithms()))
183
            )
184
        );
185
186
        $this->file = $file;
187
        $this->alias = $alias;
188
        $this->basePath = $basePath;
189
        $this->composerJson = $composerJson;
190
        $this->composerLock = $composerLock;
191
        $this->files = $files;
192
        $this->binaryFiles = $binaryFiles;
193
        $this->compactors = $compactors;
194
        $this->compressionAlgorithm = $compressionAlgorithm;
195
        $this->fileMode = $fileMode;
196
        $this->mainScriptPath = $mainScriptPath;
197
        $this->mainScriptContents = $mainScriptContents;
198
        $this->fileMapper = $fileMapper;
199
        $this->metadata = $metadata;
200
        $this->tmpOutputPath = $tmpOutputPath;
201
        $this->outputPath = $outputPath;
202
        $this->privateKeyPassphrase = $privateKeyPassphrase;
203
        $this->privateKeyPath = $privateKeyPath;
204
        $this->isPrivateKeyPrompt = $isPrivateKeyPrompt;
205
        $this->processedReplacements = $processedReplacements;
206
        $this->shebang = $shebang;
207
        $this->signingAlgorithm = $signingAlgorithm;
208
        $this->stubBannerContents = $stubBannerContents;
209
        $this->stubBannerPath = $stubBannerPath;
210
        $this->stubPath = $stubPath;
211
        $this->isInterceptFileFuncs = $isInterceptFileFuncs;
212
        $this->isStubGenerated = $isStubGenerated;
213
        $this->checkRequirements = $checkRequirements;
214
    }
215
216
    public static function create(?string $file, stdClass $raw): self
217
    {
218
        $alias = self::retrieveAlias($raw);
219
220
        $basePath = self::retrieveBasePath($file, $raw);
221
222
        $composerFiles = self::retrieveComposerFiles($basePath);
223
224
        $mainScriptPath = self::retrieveMainScriptPath($raw, $basePath, $composerFiles[0][1]);
225
        $mainScriptContents = self::retrieveMainScriptContents($mainScriptPath);
226
227
        [$tmpOutputPath, $outputPath] = self::retrieveOutputPath($raw, $basePath, $mainScriptPath);
228
229
        $composerJson = $composerFiles[0];
230
        $composerLock = $composerFiles[1];
231
232
        $devPackages = ComposerConfiguration::retrieveDevPackages($basePath, $composerJson[1], $composerLock[1]);
233
234
        [$excludedPaths, $blacklistFilter] = self::retrieveBlacklistFilter($raw, $basePath, $tmpOutputPath, $outputPath);
235
236
        if (self::shouldRetrieveAllFiles($file, $raw)) {
237
            $filesAggregate = self::retrieveAllFiles($basePath, $mainScriptPath, $blacklistFilter, $excludedPaths, $devPackages);
238
            $binaryFilesAggregate = [];
239
        } else {
240
            $files = self::retrieveFiles($raw, 'files', $basePath, $composerFiles);
241
242
            $directories = self::retrieveDirectories($raw, 'directories', $basePath, $blacklistFilter, $excludedPaths);
243
            $filesFromFinders = self::retrieveFilesFromFinders($raw, 'finder', $basePath, $blacklistFilter, $devPackages);
244
245
            $filesAggregate = self::retrieveFilesAggregate($files, $directories, ...$filesFromFinders);
246
247
            $binaryFiles = self::retrieveFiles($raw, 'files-bin', $basePath);
248
            $binaryDirectories = self::retrieveDirectories($raw, 'directories-bin', $basePath, $blacklistFilter, $excludedPaths);
249
            $binaryFilesFromFinders = self::retrieveFilesFromFinders($raw, 'finder-bin', $basePath, $blacklistFilter, $devPackages);
250
251
            $binaryFilesAggregate = self::retrieveFilesAggregate($binaryFiles, $binaryDirectories, ...$binaryFilesFromFinders);
252
        }
253
254
        $compactors = self::retrieveCompactors($raw, $basePath);
255
        $compressionAlgorithm = self::retrieveCompressionAlgorithm($raw);
256
257
        $fileMode = self::retrieveFileMode($raw);
258
259
        $map = self::retrieveMap($raw);
260
        $fileMapper = new MapFile($map);
261
262
        $metadata = self::retrieveMetadata($raw);
263
264
        $privateKeyPassphrase = self::retrievePrivateKeyPassphrase($raw);
265
        $privateKeyPath = self::retrievePrivateKeyPath($raw);
266
        $isPrivateKeyPrompt = self::retrieveIsPrivateKeyPrompt($raw);
267
268
        $replacements = self::retrieveReplacements($raw);
269
        $processedReplacements = self::retrieveProcessedReplacements($replacements, $raw, $file);
270
271
        $shebang = self::retrieveShebang($raw);
272
273
        $signingAlgorithm = self::retrieveSigningAlgorithm($raw);
274
275
        $stubBannerContents = self::retrieveStubBannerContents($raw);
276
        $stubBannerPath = self::retrieveStubBannerPath($raw, $basePath);
277
278
        if (null !== $stubBannerPath) {
279
            $stubBannerContents = file_contents($stubBannerPath);
280
        }
281
282
        $stubBannerContents = self::normalizeStubBannerContents($stubBannerContents);
283
284
        $stubPath = self::retrieveStubPath($raw, $basePath);
285
286
        $isInterceptFileFuncs = self::retrieveIsInterceptFileFuncs($raw);
287
        $isStubGenerated = self::retrieveIsStubGenerated($raw, $stubPath);
288
289
        $checkRequirements = self::retrieveCheckRequirements(
290
            $raw,
291
            null !== $composerLock[0],
292
            $isStubGenerated
293
        );
294
295
        return new self(
296
            $file,
297
            $alias,
298
            $basePath,
299
            $composerJson,
300
            $composerLock,
301
            $filesAggregate,
302
            $binaryFilesAggregate,
303
            $compactors,
304
            $compressionAlgorithm,
305
            $fileMode,
306
            $mainScriptPath,
307
            $mainScriptContents,
308
            $fileMapper,
309
            $metadata,
310
            $tmpOutputPath,
311
            $outputPath,
312
            $privateKeyPassphrase,
313
            $privateKeyPath,
314
            $isPrivateKeyPrompt,
315
            $processedReplacements,
316
            $shebang,
317
            $signingAlgorithm,
318
            $stubBannerContents,
319
            $stubBannerPath,
320
            $stubPath,
321
            $isInterceptFileFuncs,
322
            $isStubGenerated,
323
            $checkRequirements
324
        );
325
    }
326
327
    public function getFile(): ?string
328
    {
329
        return $this->file;
330
    }
331
332
    public function getAlias(): string
333
    {
334
        return $this->alias;
335
    }
336
337
    public function getBasePath(): string
338
    {
339
        return $this->basePath;
340
    }
341
342
    public function getComposerJson(): ?string
343
    {
344
        return $this->composerJson[0];
345
    }
346
347
    public function getComposerJsonDecodedContents(): ?array
348
    {
349
        return $this->composerJson[1];
350
    }
351
352
    public function getComposerLock(): ?string
353
    {
354
        return $this->composerLock[0];
355
    }
356
357
    public function getComposerLockDecodedContents(): ?array
358
    {
359
        return $this->composerLock[1];
360
    }
361
362
    /**
363
     * @return string[]
364
     */
365
    public function getFiles(): array
366
    {
367
        return $this->files;
368
    }
369
370
    /**
371
     * @return string[]
372
     */
373
    public function getBinaryFiles(): array
374
    {
375
        return $this->binaryFiles;
376
    }
377
378
    /**
379
     * @return Compactor[] the list of compactors
380
     */
381
    public function getCompactors(): array
382
    {
383
        return $this->compactors;
384
    }
385
386
    public function getCompressionAlgorithm(): ?int
387
    {
388
        return $this->compressionAlgorithm;
389
    }
390
391
    public function getFileMode(): ?int
392
    {
393
        return $this->fileMode;
394
    }
395
396
    public function getMainScriptPath(): string
397
    {
398
        return $this->mainScriptPath;
399
    }
400
401
    public function getMainScriptContents(): string
402
    {
403
        return $this->mainScriptContents;
404
    }
405
406
    public function checkRequirements(): bool
407
    {
408
        return $this->checkRequirements;
409
    }
410
411
    public function getTmpOutputPath(): string
412
    {
413
        return $this->tmpOutputPath;
414
    }
415
416
    public function getOutputPath(): string
417
    {
418
        return $this->outputPath;
419
    }
420
421
    /**
422
     * @return string[]
423
     */
424
    public function getMap(): array
425
    {
426
        return $this->fileMapper->getMap();
427
    }
428
429
    public function getFileMapper(): MapFile
430
    {
431
        return $this->fileMapper;
432
    }
433
434
    /**
435
     * @return mixed
436
     */
437
    public function getMetadata()
438
    {
439
        return $this->metadata;
440
    }
441
442
    public function getPrivateKeyPassphrase(): ?string
443
    {
444
        return $this->privateKeyPassphrase;
445
    }
446
447
    public function getPrivateKeyPath(): ?string
448
    {
449
        return $this->privateKeyPath;
450
    }
451
452
    public function isPrivateKeyPrompt(): bool
453
    {
454
        return $this->isPrivateKeyPrompt;
455
    }
456
457
    public function getProcessedReplacements(): array
458
    {
459
        return $this->processedReplacements;
460
    }
461
462
    public function getShebang(): ?string
463
    {
464
        return $this->shebang;
465
    }
466
467
    public function getSigningAlgorithm(): int
468
    {
469
        return $this->signingAlgorithm;
470
    }
471
472
    public function getStubBannerContents(): ?string
473
    {
474
        return $this->stubBannerContents;
475
    }
476
477
    public function getStubBannerPath(): ?string
478
    {
479
        return $this->stubBannerPath;
480
    }
481
482
    public function getStubPath(): ?string
483
    {
484
        return $this->stubPath;
485
    }
486
487
    public function isInterceptFileFuncs(): bool
488
    {
489
        return $this->isInterceptFileFuncs;
490
    }
491
492
    public function isStubGenerated(): bool
493
    {
494
        return $this->isStubGenerated;
495
    }
496
497
    private static function retrieveAlias(stdClass $raw): string
498
    {
499
        if (false === isset($raw->alias)) {
500
            return uniqid('box-auto-generated-alias-', false).'.phar';
501
        }
502
503
        $alias = trim($raw->alias);
504
505
        Assertion::notEmpty($alias, 'A PHAR alias cannot be empty when provided.');
506
507
        return $alias;
508
    }
509
510
    private static function retrieveBasePath(?string $file, stdClass $raw): string
511
    {
512
        if (null === $file) {
513
            return getcwd();
514
        }
515
516
        if (false === isset($raw->{'base-path'})) {
517
            return realpath(dirname($file));
518
        }
519
520
        $basePath = trim($raw->{'base-path'});
521
522
        Assertion::directory(
523
            $basePath,
524
            'The base path "%s" is not a directory or does not exist.'
525
        );
526
527
        return realpath($basePath);
528
    }
529
530
    private static function shouldRetrieveAllFiles(?string $file, stdClass $raw): bool
531
    {
532
        if (null === $file) {
533
            return true;
534
        }
535
536
        // TODO: config should be casted into an array: it is easier to do and we need an array in several places now
537
        $rawConfig = (array) $raw;
538
539
        foreach (self::FILES_SETTINGS as $key) {
540
            if (array_key_exists($key, $rawConfig)) {
541
                return false;
542
            }
543
        }
544
545
        return true;
546
    }
547
548
    private static function retrieveBlacklistFilter(stdClass $raw, string $basePath, string ...$excludedPaths): array
549
    {
550
        $blacklist = self::retrieveBlacklist($raw, $basePath, ...$excludedPaths);
551
552
        $blacklistFilter = function (SplFileInfo $file) use ($blacklist): ?bool {
553
            if ($file->isLink()) {
554
                return false;
555
            }
556
557
            if (false === $file->getRealPath()) {
558
                return false;
559
            }
560
561
            if (in_array($file->getRealPath(), $blacklist, true)) {
562
                return false;
563
            }
564
565
            return null;
566
        };
567
568
        return [$blacklist, $blacklistFilter];
569
    }
570
571
    /**
572
     * @param stdClass $raw
573
     * @param string   $basePath
574
     * @param string[] $excludedPaths
575
     *
576
     * @return string[]
577
     */
578
    private static function retrieveBlacklist(stdClass $raw, string $basePath, string ...$excludedPaths): array
579
    {
580
        /** @var string[] $blacklist */
581
        $blacklist = array_merge($excludedPaths, $raw->blacklist ?? []);
582
583
        $normalizedBlacklist = [];
584
585
        foreach ($blacklist as $file) {
586
            $normalizedBlacklist[] = self::normalizePath($file, $basePath);
587
            $normalizedBlacklist[] = canonicalize(make_path_relative(trim($file), $basePath));
588
        }
589
590
        return array_unique($normalizedBlacklist);
591
    }
592
593
    /**
594
     * @return SplFileInfo[]
595
     */
596
    private static function retrieveFiles(stdClass $raw, string $key, string $basePath, array $composerFiles = []): array
597
    {
598
        $files = [];
599
600
        if (isset($composerFiles[0][0])) {
601
            $files[] = $composerFiles[0][0];
602
        }
603
604
        if (isset($composerFiles[1][1])) {
605
            $files[] = $composerFiles[1][0];
606
        }
607
608
        if (false === isset($raw->{$key})) {
609
            return [];
610
        }
611
612
        $files = array_merge((array) $raw->{$key}, $files);
613
614
        Assertion::allString($files);
615
616
        $normalizePath = function (string $file) use ($basePath, $key): SplFileInfo {
617
            $file = self::normalizePath($file, $basePath);
618
619
            if (is_link($file)) {
620
                // TODO: add this to baberlei/assert
621
                throw new InvalidArgumentException(
622
                    sprintf(
623
                        'Cannot add the link "%s": links are not supported.',
624
                        $file
625
                    )
626
                );
627
            }
628
629
            Assertion::file(
630
                $file,
631
                sprintf(
632
                    '"%s" must contain a list of existing files. Could not find "%%s".',
633
                    $key
634
                )
635
            );
636
637
            return new SplFileInfo($file);
638
        };
639
640
        return array_map($normalizePath, $files);
641
    }
642
643
    /**
644
     * @param stdClass $raw
645
     * @param string   $key             Config property name
646
     * @param string   $basePath
647
     * @param Closure  $blacklistFilter
648
     * @param string[] $excludedPaths
649
     *
650
     * @return iterable|SplFileInfo[]
651
     */
652
    private static function retrieveDirectories(
653
        stdClass $raw,
654
        string $key,
655
        string $basePath,
656
        Closure $blacklistFilter,
657
        array $excludedPaths
658
    ): iterable {
659
        $directories = self::retrieveDirectoryPaths($raw, $key, $basePath);
660
661
        if ([] !== $directories) {
662
            $finder = Finder::create()
663
                ->files()
664
                ->filter($blacklistFilter)
665
                ->ignoreVCS(true)
666
                ->in($directories)
667
            ;
668
669
            foreach ($excludedPaths as $excludedPath) {
670
                $finder->notPath($excludedPath);
671
            }
672
673
            return $finder;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $finder returns the type Symfony\Component\Finder\Finder which is incompatible with the documented return type iterable|SplFileInfo[].
Loading history...
674
        }
675
676
        return [];
677
    }
678
679
    /**
680
     * @param stdClass $raw
681
     * @param string   $key
682
     * @param string   $basePath
683
     * @param Closure  $blacklistFilter
684
     * @param string[] $devPackages
685
     *
686
     * @return iterable[]|SplFileInfo[][]
687
     */
688
    private static function retrieveFilesFromFinders(
689
        stdClass $raw,
690
        string $key,
691
        string $basePath,
692
        Closure $blacklistFilter,
693
        array $devPackages
694
    ): array {
695
        if (isset($raw->{$key})) {
696
            return self::processFinders($raw->{$key}, $basePath, $blacklistFilter, $devPackages);
697
        }
698
699
        return [];
700
    }
701
702
    /**
703
     * @param iterable[]|SplFileInfo[][] $fileIterators
704
     *
705
     * @return SplFileInfo[]
706
     */
707
    private static function retrieveFilesAggregate(iterable ...$fileIterators): array
708
    {
709
        $files = [];
710
711
        foreach ($fileIterators as $fileIterator) {
712
            foreach ($fileIterator as $file) {
713
                $files[$file->getPathname()] = $file;
714
            }
715
        }
716
717
        return array_values($files);
718
    }
719
720
    /**
721
     * @param array    $findersConfig
722
     * @param string   $basePath
723
     * @param Closure  $blacklistFilter
724
     * @param string[] $devPackages
725
     *
726
     * @return Finder[]|SplFileInfo[][]
727
     */
728
    private static function processFinders(
729
        array $findersConfig,
730
        string $basePath,
731
        Closure $blacklistFilter,
732
        array $devPackages
733
    ): array {
734
        $processFinderConfig = function (stdClass $config) use ($basePath, $blacklistFilter, $devPackages) {
735
            return self::processFinder($config, $basePath, $blacklistFilter, $devPackages);
736
        };
737
738
        return array_map($processFinderConfig, $findersConfig);
739
    }
740
741
    /**
742
     * @param stdClass $config
743
     * @param string   $basePath
744
     * @param Closure  $blacklistFilter
745
     * @param string[] $devPackages
746
     *
747
     * @return Finder|SplFileInfo[]
748
     */
749
    private static function processFinder(
750
        stdClass $config,
751
        string $basePath,
752
        Closure $blacklistFilter,
753
        array $devPackages
754
    ): Finder {
755
        $finder = Finder::create()
756
            ->files()
757
            ->filter($blacklistFilter)
758
            ->filter(
759
                function (SplFileInfo $fileInfo) use ($devPackages): bool {
760
                    foreach ($devPackages as $devPackage) {
761
                        if ($devPackage === longest_common_base_path([$devPackage, $fileInfo->getRealPath()])) {
762
                            // File belongs to the dev package
763
                            return false;
764
                        }
765
                    }
766
767
                    return true;
768
                }
769
            )
770
            ->ignoreVCS(true)
771
        ;
772
773
        $normalizedConfig = (function (array $config, Finder $finder): array {
774
            $normalizedConfig = [];
775
776
            foreach ($config as $method => $arguments) {
777
                $method = trim($method);
778
                $arguments = (array) $arguments;
779
780
                Assertion::methodExists(
781
                    $method,
782
                    $finder,
783
                    'The method "Finder::%s" does not exist.'
784
                );
785
786
                $normalizedConfig[$method] = $arguments;
787
            }
788
789
            krsort($normalizedConfig);
790
791
            return $normalizedConfig;
792
        })((array) $config, $finder);
793
794
        $createNormalizedDirectories = function (string $directory) use ($basePath): ?string {
795
            $directory = self::normalizePath($directory, $basePath);
796
797
            if (is_link($directory)) {
798
                // TODO: add this to baberlei/assert
799
                throw new InvalidArgumentException(
800
                    sprintf(
801
                        'Cannot append the link "%s" to the Finder: links are not supported.',
802
                        $directory
803
                    )
804
                );
805
            }
806
807
            Assertion::directory($directory);
808
809
            return $directory;
810
        };
811
812
        $normalizeFileOrDirectory = function (string &$fileOrDirectory) use ($basePath): void {
813
            $fileOrDirectory = self::normalizePath($fileOrDirectory, $basePath);
814
815
            if (is_link($fileOrDirectory)) {
816
                // TODO: add this to baberlei/assert
817
                throw new InvalidArgumentException(
818
                    sprintf(
819
                        'Cannot append the link "%s" to the Finder: links are not supported.',
820
                        $fileOrDirectory
821
                    )
822
                );
823
            }
824
825
            // TODO: add this to baberlei/assert
826
            if (false === file_exists($fileOrDirectory)) {
827
                throw new InvalidArgumentException(
828
                    sprintf(
829
                        'Path "%s" was expected to be a file or directory. It may be a symlink (which are unsupported).',
830
                        $fileOrDirectory
831
                    )
832
                );
833
            }
834
835
            // TODO: add fileExists (as file or directory) to Assert
836
            if (false === is_file($fileOrDirectory)) {
837
                Assertion::directory($fileOrDirectory);
838
            } else {
839
                Assertion::file($fileOrDirectory);
840
            }
841
        };
842
843
        foreach ($normalizedConfig as $method => $arguments) {
844
            if ('in' === $method) {
845
                $normalizedConfig[$method] = $arguments = array_map($createNormalizedDirectories, $arguments);
846
            }
847
848
            if ('exclude' === $method) {
849
                $arguments = array_unique(array_map('trim', $arguments));
850
            }
851
852
            if ('append' === $method) {
853
                array_walk($arguments, $normalizeFileOrDirectory);
854
855
                $arguments = [$arguments];
856
            }
857
858
            foreach ($arguments as $argument) {
859
                $finder->$method($argument);
860
            }
861
        }
862
863
        return $finder;
864
    }
865
866
    /**
867
     * @param string   $basePath
868
     * @param string   $mainScriptPath
869
     * @param Closure  $blacklistFilter
870
     * @param string[] $excludedPaths
871
     * @param string[] $devPackages
872
     *
873
     * @return SplFileInfo[]
874
     */
875
    private static function retrieveAllFiles(
876
        string $basePath,
877
        string $mainScriptPath,
878
        Closure $blacklistFilter,
879
        array $excludedPaths,
880
        array $devPackages
881
    ): array {
882
        $relativeDevPackages = array_map(
883
            function (string $packagePath) use ($basePath): string {
884
                return make_path_relative($packagePath, $basePath);
885
            },
886
            $devPackages
887
        );
888
889
        $finder = Finder::create()
890
            ->files()
891
            ->in($basePath)
892
            ->notPath(make_path_relative($mainScriptPath, $basePath))
893
            ->filter($blacklistFilter)
894
            ->exclude($relativeDevPackages)
895
            ->ignoreVCS(true)
896
        ;
897
898
        $excludedPaths = array_unique(
899
            array_filter(
900
                array_map(
901
                    function (string $path) use ($basePath): string {
902
                        return make_path_relative($path, $basePath);
903
                    },
904
                    $excludedPaths
905
                ),
906
                function (string $path): bool {
907
                    return '..' !== substr($path, 0, 2);
908
                }
909
            )
910
        );
911
912
        foreach ($excludedPaths as $excludedPath) {
913
            $finder->notPath($excludedPath);
914
        }
915
916
        return array_unique(
917
            toArray(
918
                map(
919
                    method('getRealPath'),
920
                    $finder
921
                )
922
            )
923
        );
924
    }
925
926
    /**
927
     * @param stdClass $raw
928
     * @param string   $key      Config property name
929
     * @param string   $basePath
930
     *
931
     * @return string[]
932
     */
933
    private static function retrieveDirectoryPaths(stdClass $raw, string $key, string $basePath): array
934
    {
935
        if (false === isset($raw->{$key})) {
936
            return [];
937
        }
938
939
        $directories = $raw->{$key};
940
941
        $normalizeDirectory = function (string $directory) use ($basePath, $key): string {
942
            $directory = self::normalizePath($directory, $basePath);
943
944
            if (is_link($directory)) {
945
                // TODO: add this to baberlei/assert
946
                throw new InvalidArgumentException(
947
                    sprintf(
948
                        'Cannot add the link "%s": links are not supported.',
949
                        $directory
950
                    )
951
                );
952
            }
953
954
            Assertion::directory(
955
                $directory,
956
                sprintf(
957
                    '"%s" must contain a list of existing directories. Could not find "%%s".',
958
                    $key
959
                )
960
            );
961
962
            return $directory;
963
        };
964
965
        return array_map($normalizeDirectory, $directories);
966
    }
967
968
    private static function normalizePath(string $file, string $basePath): string
969
    {
970
        return make_path_absolute(trim($file), $basePath);
971
    }
972
973
    /**
974
     * @return Compactor[]
975
     */
976
    private static function retrieveCompactors(stdClass $raw, string $basePath): array
977
    {
978
        if (false === isset($raw->compactors)) {
979
            return [];
980
        }
981
982
        $compactorClasses = array_unique((array) $raw->compactors);
983
984
        return array_map(
985
            function (string $class) use ($raw, $basePath): Compactor {
986
                Assertion::classExists($class, 'The compactor class "%s" does not exist.');
987
                Assertion::implementsInterface($class, Compactor::class, 'The class "%s" is not a compactor class.');
988
989
                if (Php::class === $class || LegacyPhp::class === $class) {
990
                    return self::createPhpCompactor($raw);
991
                }
992
993
                if (PhpScoperCompactor::class === $class) {
994
                    $phpScoperConfig = self::retrievePhpScoperConfig($raw, $basePath);
995
996
                    return new PhpScoperCompactor(
997
                        new SimpleScoper(
998
                            create_scoper(),
999
                            uniqid('_HumbugBox', false),
1000
                            $phpScoperConfig->getWhitelist(),
1001
                            $phpScoperConfig->getPatchers()
1002
                        )
1003
                    );
1004
                }
1005
1006
                return new $class();
1007
            },
1008
            $compactorClasses
1009
        );
1010
    }
1011
1012
    private static function retrieveCompressionAlgorithm(stdClass $raw): ?int
1013
    {
1014
        if (false === isset($raw->compression)) {
1015
            return null;
1016
        }
1017
1018
        $knownAlgorithmNames = array_keys(get_phar_compression_algorithms());
1019
1020
        Assertion::inArray(
1021
            $raw->compression,
1022
            $knownAlgorithmNames,
1023
            sprintf(
1024
                'Invalid compression algorithm "%%s", use one of "%s" instead.',
1025
                implode('", "', $knownAlgorithmNames)
1026
            )
1027
        );
1028
1029
        $value = get_phar_compression_algorithms()[$raw->compression];
1030
1031
        // Phar::NONE is not valid for compressFiles()
1032
        if (Phar::NONE === $value) {
1033
            return null;
1034
        }
1035
1036
        return $value;
1037
    }
1038
1039
    private static function retrieveFileMode(stdClass $raw): ?int
1040
    {
1041
        if (isset($raw->chmod)) {
1042
            return intval($raw->chmod, 8);
1043
        }
1044
1045
        return null;
1046
    }
1047
1048
    private static function retrieveMainScriptPath(stdClass $raw, string $basePath, ?array $decodedJsonContents): string
1049
    {
1050
        if (isset($raw->main)) {
1051
            $main = $raw->main;
1052
        } else {
1053
            if (null === $decodedJsonContents
1054
                || false === array_key_exists('bin', $decodedJsonContents)
1055
                || false === $main = current($decodedJsonContents['bin'])
1056
            ) {
1057
                $main = self::DEFAULT_MAIN_SCRIPT;
1058
            }
1059
        }
1060
1061
        return self::normalizePath($main, $basePath);
1062
    }
1063
1064
    private static function retrieveMainScriptContents(string $mainScriptPath): string
1065
    {
1066
        $contents = file_contents($mainScriptPath);
1067
1068
        // Remove the shebang line: the shebang line in a PHAR should be located in the stub file which is the real
1069
        // PHAR entry point file.
1070
        return preg_replace('/^#!.*\s*/', '', $contents);
1071
    }
1072
1073
    private static function retrieveComposerFiles(string $basePath): array
1074
    {
1075
        $retrieveFileAndContents = function (string $file): array {
1076
            $json = new Json();
1077
1078
            if (false === file_exists($file) || false === is_file($file) || false === is_readable($file)) {
1079
                return [null, null];
1080
            }
1081
1082
            try {
1083
                $contents = $json->decodeFile($file, true);
1084
            } catch (ParsingException $exception) {
1085
                throw new InvalidArgumentException(
1086
                    sprintf(
1087
                        'Expected the file "%s" to be a valid composer.json file but an error has been found: %s',
1088
                        $file,
1089
                        $exception->getMessage()
1090
                    ),
1091
                    0,
1092
                    $exception
1093
                );
1094
            }
1095
1096
            return [$file, $contents];
1097
        };
1098
1099
        [$composerJson, $composerJsonContents] = $retrieveFileAndContents(canonicalize($basePath.'/composer.json'));
1100
        [$composerLock, $composerLockContents] = $retrieveFileAndContents(canonicalize($basePath.'/composer.lock'));
1101
1102
        return [
1103
            [$composerJson, $composerJsonContents],
1104
            [$composerLock, $composerLockContents],
1105
        ];
1106
    }
1107
1108
    /**
1109
     * @return string[][]
1110
     */
1111
    private static function retrieveMap(stdClass $raw): array
1112
    {
1113
        if (false === isset($raw->map)) {
1114
            return [];
1115
        }
1116
1117
        $map = [];
1118
1119
        foreach ((array) $raw->map as $item) {
1120
            $processed = [];
1121
1122
            foreach ($item as $match => $replace) {
1123
                $processed[canonicalize(trim($match))] = canonicalize(trim($replace));
1124
            }
1125
1126
            if (isset($processed['_empty_'])) {
1127
                $processed[''] = $processed['_empty_'];
1128
1129
                unset($processed['_empty_']);
1130
            }
1131
1132
            $map[] = $processed;
1133
        }
1134
1135
        return $map;
1136
    }
1137
1138
    /**
1139
     * @return mixed
1140
     */
1141
    private static function retrieveMetadata(stdClass $raw)
1142
    {
1143
        if (isset($raw->metadata)) {
1144
            if (is_object($raw->metadata)) {
1145
                return (array) $raw->metadata;
1146
            }
1147
1148
            return $raw->metadata;
1149
        }
1150
1151
        return null;
1152
    }
1153
1154
    /**
1155
     * @return string[] The first element is the temporary output path and the second the real one
1156
     */
1157
    private static function retrieveOutputPath(stdClass $raw, string $basePath, string $mainScriptPath): array
1158
    {
1159
        if (isset($raw->output)) {
1160
            $path = $raw->output;
1161
        } else {
1162
            if (1 === preg_match('/^(?<main>.*?)(?:\.[\p{L}\d]+)?$/', $mainScriptPath, $matches)) {
1163
                $path = $matches['main'].'.phar';
1164
            } else {
1165
                // Last resort, should not happen
1166
                $path = self::DEFAULT_ALIAS;
1167
            }
1168
        }
1169
1170
        $tmp = $real = self::normalizePath($path, $basePath);
1171
1172
        if ('.phar' !== substr($real, -5)) {
1173
            $tmp .= '.phar';
1174
        }
1175
1176
        return [$tmp, $real];
1177
    }
1178
1179
    private static function retrievePrivateKeyPassphrase(stdClass $raw): ?string
1180
    {
1181
        // TODO: add check to not allow this setting without the private key path
1182
        if (isset($raw->{'key-pass'})
1183
            && is_string($raw->{'key-pass'})
1184
        ) {
1185
            return $raw->{'key-pass'};
1186
        }
1187
1188
        return null;
1189
    }
1190
1191
    private static function retrievePrivateKeyPath(stdClass $raw): ?string
1192
    {
1193
        // TODO: If passed need to check its existence
1194
        // Also need
1195
1196
        if (isset($raw->key)) {
1197
            return $raw->key;
1198
        }
1199
1200
        return null;
1201
    }
1202
1203
    private static function retrieveReplacements(stdClass $raw): array
1204
    {
1205
        // TODO: add exmample in the doc
1206
        // Add checks against the values
1207
        if (isset($raw->replacements)) {
1208
            return (array) $raw->replacements;
1209
        }
1210
1211
        return [];
1212
    }
1213
1214
    private static function retrieveProcessedReplacements(
1215
        array $replacements,
1216
        stdClass $raw,
1217
        ?string $file
1218
    ): array {
1219
        if (null === $file) {
1220
            return [];
1221
        }
1222
1223
        if (null !== ($git = self::retrieveGitHashPlaceholder($raw))) {
1224
            $replacements[$git] = self::retrieveGitHash($file);
1225
        }
1226
1227
        if (null !== ($git = self::retrieveGitShortHashPlaceholder($raw))) {
1228
            $replacements[$git] = self::retrieveGitHash($file, true);
1229
        }
1230
1231
        if (null !== ($git = self::retrieveGitTagPlaceholder($raw))) {
1232
            $replacements[$git] = self::retrieveGitTag($file);
1233
        }
1234
1235
        if (null !== ($git = self::retrieveGitVersionPlaceholder($raw))) {
1236
            $replacements[$git] = self::retrieveGitVersion($file);
1237
        }
1238
1239
        if (null !== ($date = self::retrieveDatetimeNowPlaceHolder($raw))) {
1240
            $replacements[$date] = self::retrieveDatetimeNow(
1241
                self::retrieveDatetimeFormat($raw)
1242
            );
1243
        }
1244
1245
        $sigil = self::retrieveReplacementSigil($raw);
1246
1247
        foreach ($replacements as $key => $value) {
1248
            unset($replacements[$key]);
1249
            $replacements["$sigil$key$sigil"] = $value;
1250
        }
1251
1252
        return $replacements;
1253
    }
1254
1255
    private static function retrieveGitHashPlaceholder(stdClass $raw): ?string
1256
    {
1257
        if (isset($raw->{'git-commit'})) {
1258
            return $raw->{'git-commit'};
1259
        }
1260
1261
        return null;
1262
    }
1263
1264
    /**
1265
     * @param string $file
1266
     * @param bool   $short Use the short version
1267
     *
1268
     * @return string the commit hash
1269
     */
1270
    private static function retrieveGitHash(string $file, bool $short = false): string
1271
    {
1272
        return self::runGitCommand(
1273
            sprintf(
1274
                'git log --pretty="%s" -n1 HEAD',
1275
                $short ? '%h' : '%H'
1276
            ),
1277
            $file
1278
        );
1279
    }
1280
1281
    private static function retrieveGitShortHashPlaceholder(stdClass $raw): ?string
1282
    {
1283
        if (isset($raw->{'git-commit-short'})) {
1284
            return $raw->{'git-commit-short'};
1285
        }
1286
1287
        return null;
1288
    }
1289
1290
    private static function retrieveGitTagPlaceholder(stdClass $raw): ?string
1291
    {
1292
        if (isset($raw->{'git-tag'})) {
1293
            return $raw->{'git-tag'};
1294
        }
1295
1296
        return null;
1297
    }
1298
1299
    private static function retrieveGitTag(string $file): ?string
1300
    {
1301
        return self::runGitCommand('git describe --tags HEAD', $file);
1302
    }
1303
1304
    private static function retrieveGitVersionPlaceholder(stdClass $raw): ?string
1305
    {
1306
        if (isset($raw->{'git-version'})) {
1307
            return $raw->{'git-version'};
1308
        }
1309
1310
        return null;
1311
    }
1312
1313
    private static function retrieveGitVersion(string $file): ?string
1314
    {
1315
        // TODO: check if is still relevant as IMO we are better off using OcramiusVersionPackage
1316
        // to avoid messing around with that
1317
1318
        try {
1319
            return self::retrieveGitTag($file);
1320
        } catch (RuntimeException $exception) {
1321
            try {
1322
                return self::retrieveGitHash($file, true);
1323
            } catch (RuntimeException $exception) {
1324
                throw new RuntimeException(
1325
                    sprintf(
1326
                        'The tag or commit hash could not be retrieved from "%s": %s',
1327
                        dirname($file),
1328
                        $exception->getMessage()
1329
                    ),
1330
                    0,
1331
                    $exception
1332
                );
1333
            }
1334
        }
1335
    }
1336
1337
    private static function retrieveDatetimeNowPlaceHolder(stdClass $raw): ?string
1338
    {
1339
        // TODO: double check why this is done and how it is used it's not completely clear to me.
1340
        // Also make sure the documentation is up to date after.
1341
        // Instead of having two sistinct doc entries for `datetime` and `datetime-format`, it would
1342
        // be better to have only one element IMO like:
1343
        //
1344
        // "datetime": {
1345
        //   "value": "val",
1346
        //   "format": "Y-m-d"
1347
        // }
1348
        //
1349
        // Also add a check that one cannot be provided without the other. Or maybe it should? I guess
1350
        // if the datetime format is the default one it's ok; but in any case the format should not
1351
        // be added without the datetime value...
1352
1353
        if (isset($raw->{'datetime'})) {
1354
            return $raw->{'datetime'};
1355
        }
1356
1357
        return null;
1358
    }
1359
1360
    private static function retrieveDatetimeNow(string $format)
1361
    {
1362
        $now = new DateTimeImmutable('now');
1363
1364
        $datetime = $now->format($format);
1365
1366
        if (!$datetime) {
1367
            throw new InvalidArgumentException(
1368
                sprintf(
1369
                    '""%s" is not a valid PHP date format',
1370
                    $format
1371
                )
1372
            );
1373
        }
1374
1375
        return $datetime;
1376
    }
1377
1378
    private static function retrieveDatetimeFormat(stdClass $raw): string
1379
    {
1380
        if (isset($raw->{'datetime_format'})) {
1381
            return $raw->{'datetime_format'};
1382
        }
1383
1384
        return self::DEFAULT_DATETIME_FORMAT;
1385
    }
1386
1387
    private static function retrieveReplacementSigil(stdClass $raw)
1388
    {
1389
        if (isset($raw->{'replacement-sigil'})) {
1390
            return $raw->{'replacement-sigil'};
1391
        }
1392
1393
        return self::DEFAULT_REPLACEMENT_SIGIL;
1394
    }
1395
1396
    private static function retrieveShebang(stdClass $raw): ?string
1397
    {
1398
        if (false === array_key_exists('shebang', (array) $raw)) {
1399
            return self::DEFAULT_SHEBANG;
1400
        }
1401
1402
        if (null === $raw->shebang) {
1403
            return null;
1404
        }
1405
1406
        $shebang = trim($raw->shebang);
1407
1408
        Assertion::notEmpty($shebang, 'The shebang should not be empty.');
1409
        Assertion::true(
1410
            '#!' === substr($shebang, 0, 2),
1411
            sprintf(
1412
                'The shebang line must start with "#!". Got "%s" instead',
1413
                $shebang
1414
            )
1415
        );
1416
1417
        return $shebang;
1418
    }
1419
1420
    private static function retrieveSigningAlgorithm(stdClass $raw): int
1421
    {
1422
        // TODO: trigger warning: if no signing algorithm is given provided we are not in dev mode
1423
        // TODO: trigger a warning if the signing algorithm used is weak
1424
        // TODO: no longer accept strings & document BC break
1425
        if (false === isset($raw->algorithm)) {
1426
            return Phar::SHA1;
1427
        }
1428
1429
        if (false === defined('Phar::'.$raw->algorithm)) {
1430
            throw new InvalidArgumentException(
1431
                sprintf(
1432
                    'The signing algorithm "%s" is not supported.',
1433
                    $raw->algorithm
1434
                )
1435
            );
1436
        }
1437
1438
        return constant('Phar::'.$raw->algorithm);
1439
    }
1440
1441
    private static function retrieveStubBannerContents(stdClass $raw): ?string
1442
    {
1443
        if (false === array_key_exists('banner', (array) $raw)) {
1444
            return self::DEFAULT_BANNER;
1445
        }
1446
1447
        if (null === $raw->banner) {
1448
            return null;
1449
        }
1450
1451
        $banner = $raw->banner;
1452
1453
        if (is_array($banner)) {
1454
            $banner = implode("\n", $banner);
1455
        }
1456
1457
        return $banner;
1458
    }
1459
1460
    private static function retrieveStubBannerPath(stdClass $raw, string $basePath): ?string
1461
    {
1462
        if (false === isset($raw->{'banner-file'})) {
1463
            return null;
1464
        }
1465
1466
        $bannerFile = make_path_absolute($raw->{'banner-file'}, $basePath);
1467
1468
        Assertion::file($bannerFile);
1469
1470
        return $bannerFile;
1471
    }
1472
1473
    private static function normalizeStubBannerContents(?string $contents): ?string
1474
    {
1475
        if (null === $contents) {
1476
            return null;
1477
        }
1478
1479
        $banner = explode("\n", $contents);
1480
        $banner = array_map('trim', $banner);
1481
1482
        return implode("\n", $banner);
1483
    }
1484
1485
    private static function retrieveStubPath(stdClass $raw, string $basePath): ?string
1486
    {
1487
        if (isset($raw->stub) && is_string($raw->stub)) {
1488
            $stubPath = make_path_absolute($raw->stub, $basePath);
1489
1490
            Assertion::file($stubPath);
1491
1492
            return $stubPath;
1493
        }
1494
1495
        return null;
1496
    }
1497
1498
    private static function retrieveIsInterceptFileFuncs(stdClass $raw): bool
1499
    {
1500
        if (isset($raw->intercept)) {
1501
            return $raw->intercept;
1502
        }
1503
1504
        return false;
1505
    }
1506
1507
    private static function retrieveIsPrivateKeyPrompt(stdClass $raw): bool
1508
    {
1509
        return isset($raw->{'key-pass'}) && (true === $raw->{'key-pass'});
1510
    }
1511
1512
    private static function retrieveIsStubGenerated(stdClass $raw, ?string $stubPath): bool
1513
    {
1514
        return null === $stubPath && (false === isset($raw->stub) || false !== $raw->stub);
1515
    }
1516
1517
    private static function retrieveCheckRequirements(stdClass $raw, bool $hasComposerLock, bool $generateStub): bool
1518
    {
1519
        // TODO: emit warning when stub is not generated and check requirements is explicitly set to true
1520
        // TODO: emit warning when no composer lock is found but check requirements is explicitely set to true
1521
        if (false === $hasComposerLock) {
1522
            return false;
1523
        }
1524
1525
        return $raw->{'check-requirements'} ?? true;
1526
    }
1527
1528
    private static function retrievePhpScoperConfig(stdClass $raw, string $basePath): PhpScoperConfiguration
1529
    {
1530
        if (!isset($raw->{'php-scoper'})) {
1531
            $configFilePath = make_path_absolute(self::PHP_SCOPER_CONFIG, $basePath);
1532
1533
            return file_exists($configFilePath)
1534
                ? PhpScoperConfiguration::load($configFilePath)
1535
                : PhpScoperConfiguration::load()
1536
             ;
1537
        }
1538
1539
        $configFile = $raw->phpScoper;
1540
1541
        Assertion::string($configFile);
1542
1543
        $configFilePath = make_path_absolute($configFile, $basePath);
1544
1545
        Assertion::file($configFilePath);
1546
        Assertion::readable($configFilePath);
1547
1548
        return PhpScoperConfiguration::load($configFilePath);
1549
    }
1550
1551
    /**
1552
     * Runs a Git command on the repository.
1553
     *
1554
     * @param string $command the command
1555
     *
1556
     * @return string the trimmed output from the command
1557
     */
1558
    private static function runGitCommand(string $command, string $file): string
1559
    {
1560
        $path = dirname($file);
1561
1562
        $process = new Process($command, $path);
1563
1564
        if (0 === $process->run()) {
1565
            return trim($process->getOutput());
1566
        }
1567
1568
        throw new RuntimeException(
1569
            sprintf(
1570
                'The tag or commit hash could not be retrieved from "%s": %s',
1571
                $path,
1572
                $process->getErrorOutput()
1573
            )
1574
        );
1575
    }
1576
1577
    private static function createPhpCompactor(stdClass $raw): Compactor
1578
    {
1579
        // TODO: false === not set; check & add test/doc
1580
        $tokenizer = new Tokenizer();
1581
1582
        if (false === empty($raw->annotations) && isset($raw->annotations->ignore)) {
1583
            $tokenizer->ignore(
1584
                (array) $raw->annotations->ignore
1585
            );
1586
        }
1587
1588
        return new Php($tokenizer);
1589
    }
1590
}
1591