Passed
Push — master ( d34ee9...c69880 )
by Théo
02:34
created

Compile   F

Complexity

Total Complexity 65

Size/Duplication

Total Lines 676
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 65
dl 0
loc 676
rs 2.8266
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
B registerMainScript() 0 27 2
B createStub() 0 55 5
B execute() 0 54 5
B registerCompactors() 0 34 3
B setReplacementValues() 0 25 3
C checkPhpSettings() 0 53 9
B createPhar() 0 40 3
B signPhar() 0 53 5
A registerFileMapping() 0 9 1
B removeExistingArtefacts() 0 42 4
A registerRequirementsChecker() 0 20 3
A configureMetadata() 0 14 3
B logMap() 0 26 5
A correctPermissions() 0 9 2
B configure() 0 40 1
B registerStub() 0 38 3
B addFiles() 0 35 5
A configureCompressionAlgorithm() 0 18 3

How to fix   Complexity   

Complex Class

Complex classes like Compile often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Compile, and based on these observations, apply Extract Interface, too.

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\Console\Command;
16
17
use Amp\MultiReasonException;
18
use Assert\Assertion;
19
use DateTimeImmutable;
20
use DateTimeZone;
21
use KevinGH\Box\Box;
22
use KevinGH\Box\Compactor;
23
use KevinGH\Box\Configuration;
24
use KevinGH\Box\Console\Logger\BuildLogger;
25
use KevinGH\Box\MapFile;
26
use KevinGH\Box\PhpSettingsHandler;
27
use KevinGH\Box\RequirementChecker\RequirementsDumper;
28
use KevinGH\Box\StubGenerator;
29
use RuntimeException;
30
use stdClass;
31
use Symfony\Component\Console\Helper\QuestionHelper;
32
use Symfony\Component\Console\Input\InputInterface;
33
use Symfony\Component\Console\Input\InputOption;
34
use Symfony\Component\Console\Logger\ConsoleLogger;
35
use Symfony\Component\Console\Output\OutputInterface;
36
use Symfony\Component\Console\Question\Question;
37
use Symfony\Component\Console\Style\SymfonyStyle;
38
use Symfony\Component\VarDumper\Cloner\VarCloner;
39
use Symfony\Component\VarDumper\Dumper\CliDumper;
40
use const DATE_ATOM;
41
use function array_shift;
42
use function count;
43
use function explode;
44
use function function_exists;
45
use function get_class;
46
use function implode;
47
use function ini_get;
48
use function KevinGH\Box\disable_parallel_processing;
49
use function KevinGH\Box\FileSystem\chmod;
50
use function KevinGH\Box\FileSystem\dump_file;
51
use function KevinGH\Box\FileSystem\make_path_relative;
52
use function KevinGH\Box\FileSystem\remove;
53
use function KevinGH\Box\FileSystem\rename;
54
use function KevinGH\Box\formatted_filesize;
55
use function KevinGH\Box\get_phar_compression_algorithms;
56
use function putenv;
57
use function strlen;
58
use function strtolower;
59
use function substr;
60
use function trim;
61
62
/**
63
 * @final
64
 * @private
65
 * TODO: make final when Build is removed
66
 */
67
class Compile extends Configurable
68
{
69
    use ChangeableWorkingDirectory;
70
71
    private const HELP = <<<'HELP'
72
The <info>%command.name%</info> command will compile code in a new PHAR based on a variety of settings.
73
<comment>
74
  This command relies on a configuration file for loading
75
  PHAR packaging settings. If a configuration file is not
76
  specified through the <info>--config|-c</info> option, one of
77
  the following files will be used (in order): <info>box.json</info>,
78
  <info>box.json.dist</info>
79
</comment>
80
The configuration file is actually a JSON object saved to a file. For more
81
information check the documentation online:
82
<comment>
83
  https://github.com/humbug/box
84
</comment>
85
HELP;
86
87
    private const DEBUG_OPTION = 'debug';
88
    private const NO_PARALLEL_PROCESSING_OPTION = 'no-parallel';
89
    private const NO_RESTART_OPTION = 'no-restart';
90
    private const DEV_OPTION = 'dev';
91
    private const NO_CONFIG_OPTION = 'no-config';
92
93
    private const DEBUG_DIR = '.box_dump';
94
95
    /**
96
     * {@inheritdoc}
97
     */
98
    protected function configure(): void
99
    {
100
        parent::configure();
101
102
        $this->setName('compile');
103
        $this->setDescription('Compile an application into a PHAR');
104
        $this->setHelp(self::HELP);
105
106
        $this->addOption(
107
            self::DEBUG_OPTION,
108
            null,
109
            InputOption::VALUE_NONE,
110
            'Dump the files added to the PHAR in a `'.self::DEBUG_DIR.'` directory'
111
        );
112
        $this->addOption(
113
            self::NO_PARALLEL_PROCESSING_OPTION,
114
            null,
115
            InputOption::VALUE_NONE,
116
            'Disable the parallel processing'
117
        );
118
        $this->addOption(
119
            self::NO_RESTART_OPTION,
120
            null,
121
            InputOption::VALUE_NONE,
122
            'Do not restart the PHP process. Box restarts the process by default to disable xdebug and set `phar.readonly=0`'
123
        );
124
        $this->addOption(
125
            self::DEV_OPTION,
126
            null,
127
            InputOption::VALUE_NONE,
128
            'Skips the compression step'
129
        );
130
        $this->addOption(
131
            self::NO_CONFIG_OPTION,
132
            null,
133
            InputOption::VALUE_NONE,
134
            'Ignore the config file even when one is specified with the --config option'
135
        );
136
137
        $this->configureWorkingDirOption();
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    protected function execute(InputInterface $input, OutputInterface $output): void
144
    {
145
        $io = new SymfonyStyle($input, $output);
146
147
        if ($input->getOption(self::NO_RESTART_OPTION)) {
148
            putenv('BOX_ALLOW_XDEBUG=1');
149
        }
150
151
        if ($debug = $input->getOption(self::DEBUG_OPTION)) {
152
            $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
153
        }
154
155
        self::checkPhpSettings($io, $output);
156
157
        if ($input->getOption(self::NO_PARALLEL_PROCESSING_OPTION)) {
158
            disable_parallel_processing();
159
            $io->writeln('<info>[debug] Disabled parallel processing</info>', OutputInterface::VERBOSITY_DEBUG);
160
        }
161
162
        $this->changeWorkingDirectory($input);
163
164
        $io->writeln($this->getApplication()->getHelp());
165
        $io->writeln('');
166
167
        $config = $input->getOption(self::NO_CONFIG_OPTION)
168
            ? Configuration::create(null, new stdClass())
169
            : $this->getConfig($input, $output, true)
170
        ;
171
        $path = $config->getOutputPath();
172
173
        $logger = new BuildLogger($io);
174
175
        $startTime = microtime(true);
176
177
        $this->removeExistingArtefacts($config, $logger, $debug);
178
179
        $logger->logStartBuilding($path);
180
181
        $this->createPhar($config, $input, $output, $logger, $io, $debug);
182
183
        $this->correctPermissions($path, $config, $logger);
184
185
        $logger->log(
186
            BuildLogger::STAR_PREFIX,
187
            'Done.'
188
        );
189
190
        $io->comment(
191
            sprintf(
192
                "<info>PHAR size: %s\nMemory usage: %.2fMB (peak: %.2fMB), time: %.2fs<info>",
193
                formatted_filesize($path),
194
                round(memory_get_usage() / 1024 / 1024, 2),
195
                round(memory_get_peak_usage() / 1024 / 1024, 2),
196
                round(microtime(true) - $startTime, 2)
197
            )
198
        );
199
    }
200
201
    /**
202
     * Taken from Composer.
203
     *
204
     * @see https://github.com/composer/composer/blob/34c371f5f23e25eb9aa54ccc65136cf50930612e/bin/composer#L20-L50
205
     */
206
    private static function checkPhpSettings(SymfonyStyle $io, OutputInterface $output): void
207
    {
208
        if (function_exists('ini_set')) {
209
            $memoryInBytes = function (string $value): string {
210
                $unit = strtolower($value[strlen($value) - 1]);
211
212
                $value = (int) $value;
213
                switch ($unit) {
214
                    case 'g':
215
                        $value *= 1024;
216
                    // no break (cumulative multiplier)
217
                    case 'm':
218
                        $value *= 1024;
219
                    // no break (cumulative multiplier)
220
                    case 'k':
221
                        $value *= 1024;
222
                }
223
224
                return $value;
225
            };
226
227
            $memoryLimit = trim(ini_get('memory_limit'));
228
229
            // Increase memory_limit if it is lower than 500MB
230
            if ($memoryLimit !== '-1' && $memoryInBytes($memoryLimit) < 1024 * 1024 * 512 && false === getenv('BOX_MEMORY_LIMIT')) {
231
                @ini_set('memory_limit', '512M');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ini_set(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

231
                /** @scrutinizer ignore-unhandled */ @ini_set('memory_limit', '512M');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
232
233
                $io->writeln(
234
                    sprintf(
235
                        '<info>[debug] Bumped the memory limit from "%s" to "%s".</info>',
236
                        $memoryLimit,
237
                        '512M'
238
                    ),
239
                    OutputInterface::VERBOSITY_DEBUG
240
                );
241
            }
242
243
            // Set user defined memory limit
244
            if ($newMemoryLimit = getenv('BOX_MEMORY_LIMIT')) {
245
                @ini_set('memory_limit', $newMemoryLimit);
246
247
                $io->writeln(
248
                    sprintf(
249
                        '<info>[debug] Bumped the memory limit from "%s" to BOX_MEMORY_LIMIT="%s".</info>',
250
                        $memoryLimit,
251
                        $newMemoryLimit
252
                    ),
253
                    OutputInterface::VERBOSITY_DEBUG
254
                );
255
            }
256
        }
257
258
        (new PhpSettingsHandler(new ConsoleLogger($output)))->check();
259
    }
260
261
    private function createPhar(
262
        Configuration $config,
263
        InputInterface $input,
264
        OutputInterface $output,
265
        BuildLogger $logger,
266
        SymfonyStyle $io,
267
        bool $debug
268
    ): void {
269
        $box = Box::create(
270
            $config->getTmpOutputPath()
271
        );
272
        $box->getPhar()->startBuffering();
273
274
        $this->setReplacementValues($config, $box, $logger);
275
        $this->registerCompactors($config, $box, $logger);
276
        $this->registerFileMapping($config, $box, $logger);
277
278
        // Registering the main script _before_ adding the rest if of the files is _very_ important. The temporary
279
        // file used for debugging purposes and the Composer dump autoloading will not work correctly otherwise.
280
        $main = $this->registerMainScript($config, $box, $logger);
281
282
        $check = $this->registerRequirementsChecker($config, $box, $logger);
283
284
        $this->addFiles($config, $box, $logger, $io);
285
286
        $this->registerStub($config, $box, $main, $check, $logger);
287
        $this->configureMetadata($config, $box, $logger);
288
289
        $this->configureCompressionAlgorithm($config, $box, $input->getOption(self::DEV_OPTION), $logger);
290
291
        $box->getPhar()->stopBuffering();
292
293
        if ($debug) {
294
            $box->getPhar()->extractTo(self::DEBUG_DIR, null, true);
295
        }
296
297
        $this->signPhar($config, $box, $config->getTmpOutputPath(), $input, $output, $logger);
298
299
        if ($config->getTmpOutputPath() !== $config->getOutputPath()) {
300
            rename($config->getTmpOutputPath(), $config->getOutputPath());
301
        }
302
    }
303
304
    private function removeExistingArtefacts(Configuration $config, BuildLogger $logger, bool $debug): void
305
    {
306
        $path = $config->getOutputPath();
307
308
        if ($debug) {
309
            remove(self::DEBUG_DIR);
310
311
            $date = (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DATE_ATOM);
312
            $file = null !== $config->getFile() ? $config->getFile() : 'No config file';
313
314
            remove(self::DEBUG_DIR);
315
316
            dump_file(
317
                self::DEBUG_DIR.'/.box_configuration',
318
                <<<EOF
319
//
320
// Processed content of the configuration file "$file" dumped for debugging purposes
321
// Time: $date
322
//
323
324
325
EOF
326
                .(new CliDumper())->dump(
327
                    (new VarCloner())->cloneVar($config),
328
                    true
329
                )
330
            );
331
        }
332
333
        if (false === file_exists($path)) {
334
            return;
335
        }
336
337
        $logger->log(
338
            BuildLogger::QUESTION_MARK_PREFIX,
339
            sprintf(
340
                'Removing the existing PHAR "%s"',
341
                $path
342
            )
343
        );
344
345
        remove($path);
346
    }
347
348
    private function setReplacementValues(Configuration $config, Box $box, BuildLogger $logger): void
349
    {
350
        $values = $config->getProcessedReplacements();
351
352
        if ([] === $values) {
353
            return;
354
        }
355
356
        $logger->log(
357
            BuildLogger::QUESTION_MARK_PREFIX,
358
            'Setting replacement values'
359
        );
360
361
        foreach ($values as $key => $value) {
362
            $logger->log(
363
                BuildLogger::PLUS_PREFIX,
364
                sprintf(
365
                    '%s: %s',
366
                    $key,
367
                    $value
368
                )
369
            );
370
        }
371
372
        $box->registerPlaceholders($values);
373
    }
374
375
    private function registerCompactors(Configuration $config, Box $box, BuildLogger $logger): void
376
    {
377
        $compactors = $config->getCompactors();
378
379
        if ([] === $compactors) {
380
            $logger->log(
381
                BuildLogger::QUESTION_MARK_PREFIX,
382
                'No compactor to register'
383
            );
384
385
            return;
386
        }
387
388
        $logger->log(
389
            BuildLogger::QUESTION_MARK_PREFIX,
390
            'Registering compactors'
391
        );
392
393
        $logCompactors = function (Compactor $compactor) use ($logger): void {
394
            $compactorClassParts = explode('\\', get_class($compactor));
395
396
            if ('_HumbugBox' === substr($compactorClassParts[0], 0, strlen('_HumbugBox'))) {
397
                array_shift($compactorClassParts);
398
            }
399
400
            $logger->log(
401
                BuildLogger::PLUS_PREFIX,
402
                implode('\\', $compactorClassParts)
403
            );
404
        };
405
406
        array_map($logCompactors, $compactors);
407
408
        $box->registerCompactors($compactors);
409
    }
410
411
    private function registerFileMapping(Configuration $config, Box $box, BuildLogger $logger): void
412
    {
413
        $fileMapper = $config->getFileMapper();
414
415
        $this->logMap($fileMapper, $logger);
416
417
        $box->registerFileMapping(
418
            $config->getBasePath(),
419
            $fileMapper
420
        );
421
    }
422
423
    private function addFiles(Configuration $config, Box $box, BuildLogger $logger, SymfonyStyle $io): void
424
    {
425
        $logger->log(BuildLogger::QUESTION_MARK_PREFIX, 'Adding binary files');
426
427
        $count = count($config->getBinaryFiles());
428
429
        $box->addFiles($config->getBinaryFiles(), true);
430
431
        $logger->log(
432
            BuildLogger::CHEVRON_PREFIX,
433
            0 === $count
434
                ? 'No file found'
435
                : sprintf('%d file(s)', $count)
436
        );
437
438
        $logger->log(BuildLogger::QUESTION_MARK_PREFIX, 'Adding files');
439
440
        $count = count($config->getFiles());
441
442
        try {
443
            $box->addFiles($config->getFiles(), false, null !== $config->getComposerJson());
444
        } catch (MultiReasonException $exception) {
445
            // This exception is handled a different way to give me meaningful feedback to the user
446
            foreach ($exception->getReasons() as $reason) {
447
                $io->error($reason);
448
            }
449
450
            throw $exception;
451
        }
452
453
        $logger->log(
454
            BuildLogger::CHEVRON_PREFIX,
455
            0 === $count
456
                ? 'No file found'
457
                : sprintf('%d file(s)', $count)
458
        );
459
    }
460
461
    private function registerMainScript(Configuration $config, Box $box, BuildLogger $logger): ?string
462
    {
463
        $main = $config->getMainScriptPath();
464
465
        $logger->log(
466
            BuildLogger::QUESTION_MARK_PREFIX,
467
            sprintf(
468
                'Adding main file: %s',
469
                $main
470
            )
471
        );
472
473
        $localMain = $box->addFile(
474
            $main,
475
            $config->getMainScriptContents()
476
        );
477
478
        $relativeMain = make_path_relative($main, $config->getBasePath());
479
480
        if ($localMain !== $relativeMain) {
481
            $logger->log(
482
                BuildLogger::CHEVRON_PREFIX,
483
                $localMain
484
            );
485
        }
486
487
        return $localMain;
488
    }
489
490
    private function registerRequirementsChecker(Configuration $config, Box $box, BuildLogger $logger): bool
491
    {
492
        if (false === $config->checkRequirements()) {
493
            return false;
494
        }
495
496
        $logger->log(
497
            BuildLogger::QUESTION_MARK_PREFIX,
498
            'Adding requirements checker'
499
        );
500
501
        $checkFiles = RequirementsDumper::dump($config->getComposerLockDecodedContents(), null !== $config->getCompressionAlgorithm());
502
503
        foreach ($checkFiles as $fileWithContents) {
504
            [$file, $contents] = $fileWithContents;
505
506
            $box->addFile('.box/'.$file, $contents, true);
507
        }
508
509
        return true;
510
    }
511
512
    private function registerStub(Configuration $config, Box $box, string $main, bool $checkRequirements, BuildLogger $logger): void
513
    {
514
        if ($config->isStubGenerated()) {
515
            $logger->log(
516
                BuildLogger::QUESTION_MARK_PREFIX,
517
                'Generating new stub'
518
            );
519
520
            $stub = $this->createStub($config, $main, $checkRequirements, $logger);
521
522
            $box->getPhar()->setStub($stub->generate());
523
        } elseif (null !== ($stub = $config->getStubPath())) {
524
            $logger->log(
525
                BuildLogger::QUESTION_MARK_PREFIX,
526
                sprintf(
527
                    'Using stub file: %s',
528
                    $stub
529
                )
530
            );
531
532
            $box->registerStub($stub);
533
        } else {
534
            // TODO: add warning that the check requirements could not be added
535
            $aliasWasAdded = $box->getPhar()->setAlias($config->getAlias());
536
537
            Assertion::true(
538
                $aliasWasAdded,
539
                sprintf(
540
                    'The alias "%s" is invalid. See Phar::setAlias() documentation for more information.',
541
                    $config->getAlias()
542
                )
543
            );
544
545
            $box->getPhar()->setDefaultStub($main);
546
547
            $logger->log(
548
                BuildLogger::QUESTION_MARK_PREFIX,
549
                'Using default stub'
550
            );
551
        }
552
    }
553
554
    private function configureMetadata(Configuration $config, Box $box, BuildLogger $logger): void
555
    {
556
        if (null !== ($metadata = $config->getMetadata())) {
557
            $logger->log(
558
                BuildLogger::QUESTION_MARK_PREFIX,
559
                'Setting metadata'
560
            );
561
562
            $logger->log(
563
                BuildLogger::MINUS_PREFIX,
564
                is_string($metadata) ? $metadata : var_export($metadata, true)
565
            );
566
567
            $box->getPhar()->setMetadata($metadata);
568
        }
569
    }
570
571
    private function configureCompressionAlgorithm(Configuration $config, Box $box, bool $dev, BuildLogger $logger): void
572
    {
573
        if (null !== ($algorithm = $config->getCompressionAlgorithm())) {
574
            $logger->log(
575
                BuildLogger::QUESTION_MARK_PREFIX,
576
                sprintf(
577
                    'Compressing with the algorithm "<comment>%s</comment>"',
578
                    array_search($algorithm, get_phar_compression_algorithms(), true)
1 ignored issue
show
Bug introduced by
It seems like array_search($algorithm,...ion_algorithms(), true) can also be of type false; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

578
                    /** @scrutinizer ignore-type */ array_search($algorithm, get_phar_compression_algorithms(), true)
Loading history...
579
                )
580
            );
581
582
            $box->getPhar()->compressFiles($algorithm);
583
        } else {
584
            $logger->log(
585
                BuildLogger::QUESTION_MARK_PREFIX,
586
                $dev
587
                    ? 'No compression'
588
                    : '<error>No compression</error>'
589
            );
590
        }
591
    }
592
593
    private function signPhar(
594
        Configuration $config,
595
        Box $box,
596
        string $path,
597
        InputInterface $input,
598
        OutputInterface $output,
599
        BuildLogger $logger
600
    ): void {
601
        // sign using private key, if applicable
602
        //TODO: check that out
603
        remove($path.'.pubkey');
604
605
        $key = $config->getPrivateKeyPath();
606
607
        if (null === $key) {
608
            if (null !== ($algorithm = $config->getSigningAlgorithm())) {
0 ignored issues
show
introduced by
The condition null !== $algorithm = $c...->getSigningAlgorithm() is always true.
Loading history...
609
                $box->getPhar()->setSignatureAlgorithm($algorithm);
610
            }
611
612
            return;
613
        }
614
615
        $logger->log(
616
            BuildLogger::QUESTION_MARK_PREFIX,
617
            'Signing using a private key'
618
        );
619
620
        $passphrase = $config->getPrivateKeyPassphrase();
621
622
        if ($config->isPrivateKeyPrompt()) {
623
            if (false === $input->isInteractive()) {
624
                throw new RuntimeException(
625
                    sprintf(
626
                        'Accessing to the private key "%s" requires a passphrase but none provided. Either '
627
                        .'provide one or run this command in interactive mode.',
628
                        $key
629
                    )
630
                );
631
            }
632
633
            /** @var $dialog QuestionHelper */
634
            $dialog = $this->getHelper('question');
635
636
            $question = new Question('Private key passphrase:');
637
            $question->setHidden(false);
638
            $question->setHiddenFallback(false);
639
640
            $passphrase = $dialog->ask($input, $output, $question);
641
642
            $output->writeln('');
643
        }
644
645
        $box->signUsingFile($key, $passphrase);
646
    }
647
648
    private function correctPermissions(string $path, Configuration $config, BuildLogger $logger): void
649
    {
650
        if (null !== ($chmod = $config->getFileMode())) {
651
            $logger->log(
652
                BuildLogger::QUESTION_MARK_PREFIX,
653
                "Setting file permissions to <comment>$chmod</comment>"
654
            );
655
656
            chmod($path, $chmod);
657
        }
658
    }
659
660
    private function createStub(Configuration $config, ?string $main, bool $checkRequirements, BuildLogger $logger): StubGenerator
661
    {
662
        $stub = StubGenerator::create()
663
            ->alias($config->getAlias())
664
            ->index($main)
665
            ->intercept($config->isInterceptFileFuncs())
666
            ->checkRequirements($checkRequirements)
667
        ;
668
669
        if (null !== ($shebang = $config->getShebang())) {
670
            $logger->log(
671
                BuildLogger::MINUS_PREFIX,
672
                sprintf(
673
                    'Using shebang line: %s',
674
                    $shebang
675
                )
676
            );
677
678
            $stub->shebang($shebang);
679
        } else {
680
            $logger->log(
681
                BuildLogger::MINUS_PREFIX,
682
                'No shebang line'
683
            );
684
        }
685
686
        if (null !== ($bannerPath = $config->getStubBannerPath())) {
687
            $logger->log(
688
                BuildLogger::MINUS_PREFIX,
689
                sprintf(
690
                    'Using custom banner from file: %s',
691
                    $bannerPath
692
                )
693
            );
694
695
            $stub->banner($config->getStubBannerContents());
696
        } elseif (null !== ($banner = $config->getStubBannerContents())) {
697
            $logger->log(
698
                BuildLogger::MINUS_PREFIX,
699
                'Using banner:'
700
            );
701
702
            $bannerLines = explode("\n", $banner);
703
704
            foreach ($bannerLines as $bannerLine) {
705
                $logger->log(
706
                    BuildLogger::CHEVRON_PREFIX,
707
                    $bannerLine
708
                );
709
            }
710
711
            $stub->banner($banner);
712
        }
713
714
        return $stub;
715
    }
716
717
    private function logMap(MapFile $fileMapper, BuildLogger $logger): void
718
    {
719
        $map = $fileMapper->getMap();
720
721
        if ([] === $map) {
722
            return;
723
        }
724
725
        $logger->log(
726
            BuildLogger::QUESTION_MARK_PREFIX,
727
            'Mapping paths'
728
        );
729
730
        foreach ($map as $item) {
731
            foreach ($item as $match => $replace) {
732
                if ('' === $match) {
733
                    $match = '(all)';
734
                    $replace .= '/';
735
                }
736
737
                $logger->log(
738
                    BuildLogger::MINUS_PREFIX,
739
                    sprintf(
740
                        '%s <info>></info> %s',
741
                        $match,
742
                        $replace
743
                    )
744
                );
745
            }
746
        }
747
    }
748
}
749