Passed
Push — master ( c6d404...885f37 )
by Théo
02:15
created

Compile::executeCommand()   B

Complexity

Conditions 6
Paths 64

Size

Total Lines 58
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 30
c 1
b 0
f 0
nc 64
nop 1
dl 0
loc 58
rs 8.8177

How to fix   Long Method   

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:

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 Humbug\PhpScoper\Symbol\SymbolsRegistry;
19
use function array_map;
20
use function array_search;
21
use function array_shift;
22
use function count;
23
use function decoct;
24
use function explode;
25
use function file_exists;
26
use function filesize;
27
use function get_class;
28
use Humbug\PhpScoper\Whitelist;
29
use function implode;
30
use function is_callable;
31
use function is_string;
32
use KevinGH\Box\Box;
33
use const KevinGH\Box\BOX_ALLOW_XDEBUG;
0 ignored issues
show
Bug introduced by
The constant KevinGH\Box\BOX_ALLOW_XDEBUG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
34
use function KevinGH\Box\bump_open_file_descriptor_limit;
35
use function KevinGH\Box\check_php_settings;
36
use KevinGH\Box\Compactor\Compactor;
37
use KevinGH\Box\Composer\ComposerConfiguration;
38
use KevinGH\Box\Composer\ComposerOrchestrator;
39
use KevinGH\Box\Configuration\Configuration;
40
use KevinGH\Box\Console\IO\IO;
41
use KevinGH\Box\Console\Logger\CompilerLogger;
42
use KevinGH\Box\Console\MessageRenderer;
43
use function KevinGH\Box\disable_parallel_processing;
44
use function KevinGH\Box\FileSystem\chmod;
45
use function KevinGH\Box\FileSystem\dump_file;
46
use function KevinGH\Box\FileSystem\make_path_relative;
47
use function KevinGH\Box\FileSystem\remove;
48
use function KevinGH\Box\FileSystem\rename;
49
use function KevinGH\Box\format_size;
50
use function KevinGH\Box\format_time;
51
use function KevinGH\Box\get_phar_compression_algorithms;
52
use KevinGH\Box\MapFile;
53
use KevinGH\Box\RequirementChecker\RequirementsDumper;
54
use KevinGH\Box\StubGenerator;
55
use function memory_get_peak_usage;
56
use function memory_get_usage;
57
use function microtime;
58
use const PHP_EOL;
59
use function putenv;
60
use RuntimeException;
61
use function sprintf;
62
use stdClass;
63
use Symfony\Component\Console\Input\InputOption;
64
use Symfony\Component\Console\Input\StringInput;
65
use Symfony\Component\Console\Output\OutputInterface;
66
use Symfony\Component\Console\Question\Question;
67
use function var_export;
68
use Webmozart\Assert\Assert;
69
70
/**
71
 * @final
72
 * @private
73
 */
74
class Compile extends ConfigurableBaseCommand
75
{
76
    use ChangeableWorkingDirectory;
77
78
    private const HELP = <<<'HELP'
79
The <info>%command.name%</info> command will compile code in a new PHAR based on a variety of settings.
80
<comment>
81
  This command relies on a configuration file for loading
82
  PHAR packaging settings. If a configuration file is not
83
  specified through the <info>--config|-c</info> option, one of
84
  the following files will be used (in order): <info>box.json</info>,
85
  <info>box.json.dist</info>
86
</comment>
87
The configuration file is actually a JSON object saved to a file. For more
88
information check the documentation online:
89
<comment>
90
  https://github.com/humbug/box
91
</comment>
92
HELP;
93
94
    private const DEBUG_OPTION = 'debug';
95
    private const NO_PARALLEL_PROCESSING_OPTION = 'no-parallel';
96
    private const NO_RESTART_OPTION = 'no-restart';
97
    private const DEV_OPTION = 'dev';
98
    private const NO_CONFIG_OPTION = 'no-config';
99
    private const WITH_DOCKER_OPTION = 'with-docker';
100
101
    private const DEBUG_DIR = '.box_dump';
102
103
    /**
104
     * {@inheritdoc}
105
     */
106
    protected function configure(): void
107
    {
108
        parent::configure();
109
110
        $this->setName('compile');
111
        $this->setDescription('🔨  Compiles an application into a PHAR');
112
        $this->setHelp(self::HELP);
113
114
        $this->addOption(
115
            self::DEBUG_OPTION,
116
            null,
117
            InputOption::VALUE_NONE,
118
            'Dump the files added to the PHAR in a `'.self::DEBUG_DIR.'` directory'
119
        );
120
        $this->addOption(
121
            self::NO_PARALLEL_PROCESSING_OPTION,
122
            null,
123
            InputOption::VALUE_NONE,
124
            'Disable the parallel processing'
125
        );
126
        $this->addOption(
127
            self::NO_RESTART_OPTION,
128
            null,
129
            InputOption::VALUE_NONE,
130
            'Do not restart the PHP process. Box restarts the process by default to disable xdebug and set `phar.readonly=0`'
131
        );
132
        $this->addOption(
133
            self::DEV_OPTION,
134
            null,
135
            InputOption::VALUE_NONE,
136
            'Skips the compression step'
137
        );
138
        $this->addOption(
139
            self::NO_CONFIG_OPTION,
140
            null,
141
            InputOption::VALUE_NONE,
142
            'Ignore the config file even when one is specified with the --config option'
143
        );
144
        $this->addOption(
145
            self::WITH_DOCKER_OPTION,
146
            null,
147
            InputOption::VALUE_NONE,
148
            'Generates a Dockerfile'
149
        );
150
151
        $this->configureWorkingDirOption();
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157
    protected function executeCommand(IO $io): int
158
    {
159
        $input = $io->getInput();
160
161
        if ($input->getOption(self::NO_RESTART_OPTION)) {
162
            putenv(BOX_ALLOW_XDEBUG.'=1');
163
        }
164
165
        if ($debug = $input->getOption(self::DEBUG_OPTION)) {
166
            $io->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
167
        }
168
169
        check_php_settings($io);
170
171
        if ($input->getOption(self::NO_PARALLEL_PROCESSING_OPTION)) {
172
            disable_parallel_processing();
173
            $io->writeln('<info>[debug] Disabled parallel processing</info>', OutputInterface::VERBOSITY_DEBUG);
174
        }
175
176
        $this->changeWorkingDirectory($input);
177
178
        $io->writeln($this->getApplication()->getHelp());
179
        $io->newLine();
180
181
        $config = $input->getOption(self::NO_CONFIG_OPTION)
182
            ? Configuration::create(null, new stdClass())
183
            : $this->getConfig($io, true)
184
        ;
185
        $path = $config->getOutputPath();
186
187
        $logger = new CompilerLogger($io);
188
189
        $startTime = microtime(true);
190
191
        $logger->logStartBuilding($path);
192
193
        $this->removeExistingArtifacts($config, $logger, $debug);
194
195
        // Adding files might result in opening a lot of files. Either because not parallelized or when creating the
196
        // workers for parallelization.
197
        // As a result, we bump the file descriptor to an arbitrary number to ensure this process can run correctly
198
        $restoreLimit = bump_open_file_descriptor_limit(2048, $io);
199
200
        try {
201
            $box = $this->createPhar($config, $logger, $io, $debug);
202
        } finally {
203
            $restoreLimit();
204
        }
205
206
        $this->correctPermissions($path, $config, $logger);
207
208
        $this->logEndBuilding($config, $logger, $io, $box, $path, $startTime);
0 ignored issues
show
Bug introduced by
It seems like $startTime can also be of type string; however, parameter $startTime of KevinGH\Box\Console\Comm...mpile::logEndBuilding() does only seem to accept double, 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

208
        $this->logEndBuilding($config, $logger, $io, $box, $path, /** @scrutinizer ignore-type */ $startTime);
Loading history...
209
210
        if ($input->getOption(self::WITH_DOCKER_OPTION)) {
211
            $this->generateDockerFile($io);
212
        }
213
214
        return 0;
215
    }
216
217
    private function createPhar(
218
        Configuration $config,
219
        CompilerLogger $logger,
220
        IO $io,
221
        bool $debug
222
    ): Box {
223
        $box = Box::create($config->getTmpOutputPath());
224
225
        $box->startBuffering();
226
227
        $this->registerReplacementValues($config, $box, $logger);
228
        $this->registerCompactors($config, $box, $logger);
229
        $this->registerFileMapping($config, $box, $logger);
230
231
        // Registering the main script _before_ adding the rest if of the files is _very_ important. The temporary
232
        // file used for debugging purposes and the Composer dump autoloading will not work correctly otherwise.
233
        $main = $this->registerMainScript($config, $box, $logger);
234
235
        $check = $this->registerRequirementsChecker($config, $box, $logger);
236
237
        $this->addFiles($config, $box, $logger, $io);
238
239
        $this->registerStub($config, $box, $main, $check, $logger);
240
        $this->configureMetadata($config, $box, $logger);
241
242
        $this->commit($box, $config, $logger);
243
244
        $this->checkComposerFiles($box, $config, $logger);
245
246
        if ($debug) {
247
            $box->getPhar()->extractTo(self::DEBUG_DIR, null, true);
248
        }
249
250
        $this->configureCompressionAlgorithm(
251
            $config,
252
            $box,
253
            $io->getInput()->getOption(self::DEV_OPTION),
254
            $io,
255
            $logger
256
        );
257
258
        $this->signPhar($config, $box, $config->getTmpOutputPath(), $io, $logger);
259
260
        if ($config->getTmpOutputPath() !== $config->getOutputPath()) {
261
            rename($config->getTmpOutputPath(), $config->getOutputPath());
262
        }
263
264
        return $box;
265
    }
266
267
    private function removeExistingArtifacts(Configuration $config, CompilerLogger $logger, bool $debug): void
268
    {
269
        $path = $config->getOutputPath();
270
271
        if ($debug) {
272
            remove(self::DEBUG_DIR);
273
274
            dump_file(
275
                self::DEBUG_DIR.'/.box_configuration',
276
                ConfigurationExporter::export($config)
277
            );
278
        }
279
280
        if (false === file_exists($path)) {
281
            return;
282
        }
283
284
        $logger->log(
285
            CompilerLogger::QUESTION_MARK_PREFIX,
286
            sprintf(
287
                'Removing the existing PHAR "%s"',
288
                $path
289
            )
290
        );
291
292
        remove($path);
293
    }
294
295
    private function registerReplacementValues(Configuration $config, Box $box, CompilerLogger $logger): void
296
    {
297
        $values = $config->getReplacements();
298
299
        if ([] === $values) {
300
            return;
301
        }
302
303
        $logger->log(
304
            CompilerLogger::QUESTION_MARK_PREFIX,
305
            'Setting replacement values'
306
        );
307
308
        foreach ($values as $key => $value) {
309
            $logger->log(
310
                CompilerLogger::PLUS_PREFIX,
311
                sprintf(
312
                    '%s: %s',
313
                    $key,
314
                    $value
315
                )
316
            );
317
        }
318
319
        $box->registerPlaceholders($values);
320
    }
321
322
    private function registerCompactors(Configuration $config, Box $box, CompilerLogger $logger): void
323
    {
324
        $compactors = $config->getCompactors();
325
326
        if (0 === count($compactors)) {
327
            $logger->log(
328
                CompilerLogger::QUESTION_MARK_PREFIX,
329
                'No compactor to register'
330
            );
331
332
            return;
333
        }
334
335
        $logger->log(
336
            CompilerLogger::QUESTION_MARK_PREFIX,
337
            'Registering compactors'
338
        );
339
340
        $logCompactors = static function (Compactor $compactor) use ($logger): void {
341
            $compactorClassParts = explode('\\', get_class($compactor));
342
343
            if (0 === strpos($compactorClassParts[0], '_HumbugBox')) {
344
                // Keep the non prefixed class name for the user
345
                array_shift($compactorClassParts);
346
            }
347
348
            $logger->log(
349
                CompilerLogger::PLUS_PREFIX,
350
                implode('\\', $compactorClassParts)
351
            );
352
        };
353
354
        array_map($logCompactors, $compactors->toArray());
355
356
        $box->registerCompactors($compactors);
357
    }
358
359
    private function registerFileMapping(Configuration $config, Box $box, CompilerLogger $logger): void
360
    {
361
        $fileMapper = $config->getFileMapper();
362
363
        $this->logMap($fileMapper, $logger);
364
365
        $box->registerFileMapping($fileMapper);
366
    }
367
368
    private function addFiles(Configuration $config, Box $box, CompilerLogger $logger, IO $io): void
369
    {
370
        $logger->log(CompilerLogger::QUESTION_MARK_PREFIX, 'Adding binary files');
371
372
        $count = count($config->getBinaryFiles());
373
374
        $box->addFiles($config->getBinaryFiles(), true);
375
376
        $logger->log(
377
            CompilerLogger::CHEVRON_PREFIX,
378
            0 === $count
379
                ? 'No file found'
380
                : sprintf('%d file(s)', $count)
381
        );
382
383
        $logger->log(
384
            CompilerLogger::QUESTION_MARK_PREFIX,
385
            sprintf(
386
                'Auto-discover files? %s',
387
                $config->hasAutodiscoveredFiles() ? 'Yes' : 'No'
388
            )
389
        );
390
        $logger->log(
391
            CompilerLogger::QUESTION_MARK_PREFIX,
392
            sprintf(
393
                'Exclude dev files? %s',
394
                $config->excludeDevFiles() ? 'Yes' : 'No'
395
            )
396
        );
397
        $logger->log(CompilerLogger::QUESTION_MARK_PREFIX, 'Adding files');
398
399
        $count = count($config->getFiles());
400
401
        try {
402
            $box->addFiles($config->getFiles(), false);
403
        } catch (MultiReasonException $exception) {
404
            // This exception is handled a different way to give me meaningful feedback to the user
405
            foreach ($exception->getReasons() as $reason) {
406
                $io->error($reason);
407
            }
408
409
            throw $exception;
410
        }
411
412
        $logger->log(
413
            CompilerLogger::CHEVRON_PREFIX,
414
            0 === $count
415
                ? 'No file found'
416
                : sprintf('%d file(s)', $count)
417
        );
418
    }
419
420
    private function registerMainScript(Configuration $config, Box $box, CompilerLogger $logger): ?string
421
    {
422
        if (false === $config->hasMainScript()) {
423
            $logger->log(
424
                CompilerLogger::QUESTION_MARK_PREFIX,
425
                'No main script path configured'
426
            );
427
428
            return null;
429
        }
430
431
        $main = $config->getMainScriptPath();
432
433
        $logger->log(
434
            CompilerLogger::QUESTION_MARK_PREFIX,
435
            sprintf(
436
                'Adding main file: %s',
437
                $main
438
            )
439
        );
440
441
        $localMain = $box->addFile(
442
            $main,
443
            $config->getMainScriptContents()
444
        );
445
446
        $relativeMain = make_path_relative($main, $config->getBasePath());
447
448
        if ($localMain !== $relativeMain) {
449
            $logger->log(
450
                CompilerLogger::CHEVRON_PREFIX,
451
                $localMain
452
            );
453
        }
454
455
        return $localMain;
456
    }
457
458
    private function registerRequirementsChecker(Configuration $config, Box $box, CompilerLogger $logger): bool
459
    {
460
        if (false === $config->checkRequirements()) {
461
            $logger->log(
462
                CompilerLogger::QUESTION_MARK_PREFIX,
463
                'Skip requirements checker'
464
            );
465
466
            return false;
467
        }
468
469
        $logger->log(
470
            CompilerLogger::QUESTION_MARK_PREFIX,
471
            'Adding requirements checker'
472
        );
473
474
        $checkFiles = RequirementsDumper::dump(
475
            $config->getDecodedComposerJsonContents() ?? [],
476
            $config->getDecodedComposerLockContents() ?? [],
477
            $config->getCompressionAlgorithm()
478
        );
479
480
        foreach ($checkFiles as $fileWithContents) {
481
            [$file, $contents] = $fileWithContents;
482
483
            $box->addFile('.box/'.$file, $contents, true);
484
        }
485
486
        return true;
487
    }
488
489
    private function registerStub(
490
        Configuration $config,
491
        Box $box,
492
        ?string $main,
493
        bool $checkRequirements,
494
        CompilerLogger $logger
495
    ): void {
496
        if ($config->isStubGenerated()) {
497
            $logger->log(
498
                CompilerLogger::QUESTION_MARK_PREFIX,
499
                'Generating new stub'
500
            );
501
502
            $stub = $this->createStub($config, $main, $checkRequirements, $logger);
503
504
            $box->getPhar()->setStub($stub);
505
506
            return;
507
        }
508
509
        if (null !== ($stub = $config->getStubPath())) {
510
            $logger->log(
511
                CompilerLogger::QUESTION_MARK_PREFIX,
512
                sprintf(
513
                    'Using stub file: %s',
514
                    $stub
515
                )
516
            );
517
518
            $box->registerStub($stub);
519
520
            return;
521
        }
522
523
        $aliasWasAdded = $box->getPhar()->setAlias($config->getAlias());
524
525
        Assert::true(
526
            $aliasWasAdded,
527
            sprintf(
528
                'The alias "%s" is invalid. See Phar::setAlias() documentation for more information.',
529
                $config->getAlias()
530
            )
531
        );
532
533
        $box->getPhar()->setDefaultStub($main);
534
535
        $logger->log(
536
            CompilerLogger::QUESTION_MARK_PREFIX,
537
            'Using default stub'
538
        );
539
    }
540
541
    private function configureMetadata(Configuration $config, Box $box, CompilerLogger $logger): void
542
    {
543
        if (null !== ($metadata = $config->getMetadata())) {
544
            $logger->log(
545
                CompilerLogger::QUESTION_MARK_PREFIX,
546
                'Setting metadata'
547
            );
548
549
            if (is_callable($metadata)) {
550
                $metadata = $metadata();
551
            }
552
553
            $logger->log(
554
                CompilerLogger::MINUS_PREFIX,
555
                is_string($metadata) ? $metadata : var_export($metadata, true)
556
            );
557
558
            $box->getPhar()->setMetadata($metadata);
559
        }
560
    }
561
562
    private function commit(Box $box, Configuration $config, CompilerLogger $logger): void
563
    {
564
        $message = $config->dumpAutoload()
565
            ? 'Dumping the Composer autoloader'
566
            : 'Skipping dumping the Composer autoloader'
567
        ;
568
569
        $logger->log(CompilerLogger::QUESTION_MARK_PREFIX, $message);
570
571
        $excludeDevFiles = $config->excludeDevFiles();
572
        $io = $logger->getIO();
573
574
        $box->endBuffering(
575
            $config->dumpAutoload()
576
                ? static function (SymbolsRegistry $symbolsRegistry, string $prefix) use ($excludeDevFiles, $io): void {
577
                    ComposerOrchestrator::dumpAutoload(
578
                        $symbolsRegistry,
579
                        $prefix,
580
                        $excludeDevFiles,
581
                        $io
582
                    );
583
                }
584
                : null
585
        );
586
    }
587
588
    private function checkComposerFiles(Box $box, Configuration $config, CompilerLogger $logger): void
589
    {
590
        $message = $config->excludeComposerFiles()
591
            ? 'Removing the Composer dump artefacts'
592
            : 'Keep the Composer dump artefacts'
593
        ;
594
595
        $logger->log(CompilerLogger::QUESTION_MARK_PREFIX, $message);
596
597
        if ($config->excludeComposerFiles()) {
598
            $box->removeComposerArtefacts(
599
                ComposerConfiguration::retrieveVendorDir(
600
                    $config->getDecodedComposerJsonContents() ?? []
601
                )
602
            );
603
        }
604
    }
605
606
    private function configureCompressionAlgorithm(
607
        Configuration $config,
608
        Box $box,
609
        bool $dev,
610
        IO $io,
611
        CompilerLogger $logger
612
    ): void {
613
        if (null === ($algorithm = $config->getCompressionAlgorithm())) {
614
            $logger->log(
615
                CompilerLogger::QUESTION_MARK_PREFIX,
616
                'No compression'
617
            );
618
619
            return;
620
        }
621
622
        if ($dev) {
623
            $logger->log(CompilerLogger::QUESTION_MARK_PREFIX, 'Dev mode detected: skipping the compression');
624
625
            return;
626
        }
627
628
        $logger->log(
629
            CompilerLogger::QUESTION_MARK_PREFIX,
630
            sprintf(
631
                'Compressing with the algorithm "<comment>%s</comment>"',
632
                (string) array_search($algorithm, get_phar_compression_algorithms(), true)
633
            )
634
        );
635
636
        $restoreLimit = bump_open_file_descriptor_limit(count($box), $io);
637
638
        try {
639
            $extension = $box->compress($algorithm);
640
641
            if (null !== $extension) {
642
                $logger->log(
643
                    CompilerLogger::CHEVRON_PREFIX,
644
                    sprintf(
645
                        '<info>Warning: the extension "%s" will now be required to execute the PHAR</info>',
646
                        $extension
647
                    )
648
                );
649
            }
650
        } catch (RuntimeException $exception) {
651
            $io->error($exception->getMessage());
652
653
            // Continue: the compression failure should not result in completely bailing out the compilation process
654
        } finally {
655
            $restoreLimit();
656
        }
657
    }
658
659
    private function signPhar(
660
        Configuration $config,
661
        Box $box,
662
        string $path,
663
        IO $io,
664
        CompilerLogger $logger
665
    ): void {
666
        // Sign using private key when applicable
667
        remove($path.'.pubkey');
668
669
        $key = $config->getPrivateKeyPath();
670
671
        if (null === $key) {
672
            $box->getPhar()->setSignatureAlgorithm(
673
                $config->getSigningAlgorithm()
674
            );
675
676
            return;
677
        }
678
679
        $logger->log(
680
            CompilerLogger::QUESTION_MARK_PREFIX,
681
            'Signing using a private key'
682
        );
683
684
        $passphrase = $config->getPrivateKeyPassphrase();
685
686
        if ($config->promptForPrivateKey()) {
687
            if (false === $io->isInteractive()) {
688
                throw new RuntimeException(
689
                    sprintf(
690
                        'Accessing to the private key "%s" requires a passphrase but none provided. Either '
691
                        .'provide one or run this command in interactive mode.',
692
                        $key
693
                    )
694
                );
695
            }
696
697
            $question = new Question('Private key passphrase');
698
            $question->setHidden(false);
699
            $question->setHiddenFallback(false);
700
701
            $passphrase = $io->askQuestion($question);
702
703
            $io->writeln('');
704
        }
705
706
        $box->signUsingFile($key, $passphrase);
707
    }
708
709
    private function correctPermissions(string $path, Configuration $config, CompilerLogger $logger): void
710
    {
711
        if (null !== ($chmod = $config->getFileMode())) {
712
            $logger->log(
713
                CompilerLogger::QUESTION_MARK_PREFIX,
714
                sprintf(
715
                    'Setting file permissions to <comment>%s</comment>',
716
                    '0'.decoct($chmod)
717
                )
718
            );
719
720
            chmod($path, $chmod);
721
        }
722
    }
723
724
    private function createStub(
725
        Configuration $config,
726
        ?string $main,
727
        bool $checkRequirements,
728
        CompilerLogger $logger
729
    ): string {
730
        $stub = StubGenerator::create()
731
            ->alias($config->getAlias())
732
            ->index($main)
733
            ->intercept($config->isInterceptFileFuncs())
734
            ->checkRequirements($checkRequirements)
735
        ;
736
737
        if (null !== ($shebang = $config->getShebang())) {
738
            $logger->log(
739
                CompilerLogger::MINUS_PREFIX,
740
                sprintf(
741
                    'Using shebang line: %s',
742
                    $shebang
743
                )
744
            );
745
746
            $stub->shebang($shebang);
747
        } else {
748
            $logger->log(
749
                CompilerLogger::MINUS_PREFIX,
750
                'No shebang line'
751
            );
752
        }
753
754
        if (null !== ($bannerPath = $config->getStubBannerPath())) {
755
            $logger->log(
756
                CompilerLogger::MINUS_PREFIX,
757
                sprintf(
758
                    'Using custom banner from file: %s',
759
                    $bannerPath
760
                )
761
            );
762
763
            $stub->banner($config->getStubBannerContents());
764
        } elseif (null !== ($banner = $config->getStubBannerContents())) {
765
            $logger->log(
766
                CompilerLogger::MINUS_PREFIX,
767
                'Using banner:'
768
            );
769
770
            $bannerLines = explode("\n", $banner);
771
772
            foreach ($bannerLines as $bannerLine) {
773
                $logger->log(
774
                    CompilerLogger::CHEVRON_PREFIX,
775
                    $bannerLine
776
                );
777
            }
778
779
            $stub->banner($banner);
780
        }
781
782
        return $stub->generate();
783
    }
784
785
    private function logMap(MapFile $fileMapper, CompilerLogger $logger): void
786
    {
787
        $map = $fileMapper->getMap();
788
789
        if ([] === $map) {
790
            return;
791
        }
792
793
        $logger->log(
794
            CompilerLogger::QUESTION_MARK_PREFIX,
795
            'Mapping paths'
796
        );
797
798
        foreach ($map as $item) {
799
            foreach ($item as $match => $replace) {
800
                if ('' === $match) {
801
                    $match = '(all)';
802
                    $replace .= '/';
803
                }
804
805
                $logger->log(
806
                    CompilerLogger::MINUS_PREFIX,
807
                    sprintf(
808
                        '%s <info>></info> %s',
809
                        $match,
810
                        $replace
811
                    )
812
                );
813
            }
814
        }
815
    }
816
817
    private function logEndBuilding(
818
        Configuration $config,
819
        CompilerLogger $logger,
820
        IO $io,
821
        Box $box,
822
        string $path,
823
        float $startTime
824
    ): void {
825
        $logger->log(
826
            CompilerLogger::STAR_PREFIX,
827
            'Done.'
828
        );
829
        $io->newLine();
830
831
        MessageRenderer::render($io, $config->getRecommendations(), $config->getWarnings());
832
833
        $io->comment(
834
            sprintf(
835
                'PHAR: %s (%s)',
836
                $box->count() > 1 ? $box->count().' files' : $box->count().' file',
837
                format_size(
838
                    filesize($path)
839
                )
840
            )
841
            .PHP_EOL
842
            .'You can inspect the generated PHAR with the "<comment>info</comment>" command.'
843
        );
844
845
        $io->comment(
846
            sprintf(
847
                '<info>Memory usage: %s (peak: %s), time: %s<info>',
848
                format_size(memory_get_usage()),
849
                format_size(memory_get_peak_usage()),
850
                format_time(microtime(true) - $startTime)
851
            )
852
        );
853
    }
854
855
    private function generateDockerFile(OutputInterface $output): void
856
    {
857
        $generateDockerFileCommand = $this->getApplication()->find('docker');
858
859
        Assert::isInstanceOf($generateDockerFileCommand, GenerateDockerFile::class);
860
861
        $input = new StringInput('');
862
        $input->setInteractive(false);
863
864
        $generateDockerFileCommand->run($input, $output);
865
    }
866
}
867