Passed
Push — master ( 22acbe...a6e730 )
by Théo
02:08
created

Build   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 500
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 44
dl 0
loc 500
rs 8.3396
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 9 1
A registerFileMapping() 0 9 1
B execute() 0 37 1
B registerCompactors() 0 28 2
A loadBootstrapFile() 0 17 2
B addFiles() 0 26 3
B createPhar() 0 26 1
B setReplacementValues() 0 25 3
A removeExistingPhar() 0 17 2
A correctPermissions() 0 9 2
A configureMetadata() 0 14 3
B createStub() 0 54 5
B registerMainScript() 0 27 2
A configureCompressionAlgorithm() 0 16 2
B signPhar() 0 53 5
B logMap() 0 26 5
B registerStub() 0 39 4

How to fix   Complexity   

Complex Class

Complex classes like Build 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 Build, 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 Assert\Assertion;
18
use KevinGH\Box\Box;
19
use KevinGH\Box\Compactor;
20
use KevinGH\Box\Configuration;
21
use KevinGH\Box\Console\Logger\BuildLogger;
22
use KevinGH\Box\MapFile;
23
use KevinGH\Box\StubGenerator;
24
use RuntimeException;
25
use Symfony\Component\Console\Helper\QuestionHelper;
26
use Symfony\Component\Console\Input\InputInterface;
27
use Symfony\Component\Console\Output\OutputInterface;
28
use Symfony\Component\Console\Question\Question;
29
use Symfony\Component\Console\Style\SymfonyStyle;
30
use function KevinGH\Box\FileSystem\chmod;
31
use function KevinGH\Box\FileSystem\make_path_relative;
32
use function KevinGH\Box\FileSystem\remove;
33
use function KevinGH\Box\formatted_filesize;
34
use function KevinGH\Box\get_phar_compression_algorithms;
35
36
final class Build extends Configurable
37
{
38
    use ChangeableWorkingDirectory;
39
40
    private const HELP = <<<'HELP'
41
The <info>%command.name%</info> command will build a new PHAR based on a variety of settings.
42
<comment>
43
  This command relies on a configuration file for loading
44
  PHAR packaging settings. If a configuration file is not
45
  specified through the <info>--config|-c</info> option, one of
46
  the following files will be used (in order): <info>box.json</info>,
47
  <info>box.json.dist</info>
48
</comment>
49
The configuration file is actually a JSON object saved to a file. For more
50
information check the documentation online:
51
<comment>
52
  https://github.com/humbug/box
53
</comment>
54
HELP;
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    protected function configure(): void
60
    {
61
        parent::configure();
62
63
        $this->setName('build');
64
        $this->setDescription('Builds a new PHAR');
65
        $this->setHelp(self::HELP);
66
67
        $this->configureWorkingDirOption();
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    protected function execute(InputInterface $input, OutputInterface $output): void
74
    {
75
        $this->changeWorkingDirectory($input);
76
77
        $io = new SymfonyStyle($input, $output);
78
79
        $io->writeln($this->getApplication()->getHelp());
80
        $io->writeln('');
81
82
        $config = $this->getConfig($input, $output, true);
83
        $path = $config->getOutputPath();
84
85
        $logger = new BuildLogger($io);
86
87
        $startTime = microtime(true);
88
89
        $this->loadBootstrapFile($config, $logger);
90
        $this->removeExistingPhar($config, $logger);
91
92
        $logger->logStartBuilding($path);
93
94
        $this->createPhar($path, $config, $input, $output, $logger);
95
96
        $this->correctPermissions($path, $config, $logger);
97
98
        $logger->log(
99
            BuildLogger::STAR_PREFIX,
100
            'Done.'
101
        );
102
103
        $io->comment(
104
            sprintf(
105
                "<info>PHAR size: %s\nMemory usage: %.2fMB (peak: %.2fMB), time: %.2fs<info>",
106
                formatted_filesize($path),
107
                round(memory_get_usage() / 1024 / 1024, 2),
108
                round(memory_get_peak_usage() / 1024 / 1024, 2),
109
                round(microtime(true) - $startTime, 2)
110
            )
111
        );
112
    }
113
114
    private function createPhar(
115
        string $path,
116
        Configuration $config,
117
        InputInterface $input,
118
        OutputInterface $output,
119
        BuildLogger $logger
120
    ): void {
121
        $box = Box::create($path);
122
123
        $box->getPhar()->startBuffering();
124
125
        $this->setReplacementValues($config, $box, $logger);
126
        $this->registerCompactors($config, $box, $logger);
127
        $this->registerFileMapping($config, $box, $logger);
128
129
        $main = $this->registerMainScript($config, $box, $logger);
130
131
        $this->addFiles($config, $box, $logger);
132
133
        $this->registerStub($config, $box, $main, $logger);
134
        $this->configureMetadata($config, $box, $logger);
135
        $this->configureCompressionAlgorithm($config, $box, $logger);
136
137
        $box->getPhar()->stopBuffering();
138
139
        $this->signPhar($config, $box, $path, $input, $output, $logger);
140
    }
141
142
    private function loadBootstrapFile(Configuration $config, BuildLogger $logger): void
143
    {
144
        $file = $config->getBootstrapFile();
145
146
        if (null === $file) {
147
            return;
148
        }
149
150
        $logger->log(
151
            BuildLogger::QUESTION_MARK_PREFIX,
152
            sprintf(
153
                'Loading the bootstrap file "%s"',
154
                $file
155
            )
156
        );
157
158
        $config->loadBootstrap();
159
    }
160
161
    private function removeExistingPhar(Configuration $config, BuildLogger $logger): void
162
    {
163
        $path = $config->getOutputPath();
164
165
        if (false === file_exists($path)) {
166
            return;
167
        }
168
169
        $logger->log(
170
            BuildLogger::QUESTION_MARK_PREFIX,
171
            sprintf(
172
                'Removing the existing PHAR "%s"',
173
                $path
174
            )
175
        );
176
177
        remove($path);
178
    }
179
180
    private function setReplacementValues(Configuration $config, Box $box, BuildLogger $logger): void
181
    {
182
        $values = $config->getProcessedReplacements();
183
184
        if ([] === $values) {
185
            return;
186
        }
187
188
        $logger->log(
189
            BuildLogger::QUESTION_MARK_PREFIX,
190
            'Setting replacement values'
191
        );
192
193
        foreach ($values as $key => $value) {
194
            $logger->log(
195
                BuildLogger::PLUS_PREFIX,
196
                sprintf(
197
                    '%s: %s',
198
                    $key,
199
                    $value
200
                )
201
            );
202
        }
203
204
        $box->registerPlaceholders($values);
205
    }
206
207
    private function registerCompactors(Configuration $config, Box $box, BuildLogger $logger): void
208
    {
209
        $compactors = $config->getCompactors();
210
211
        if ([] === $compactors) {
212
            $logger->log(
213
                BuildLogger::QUESTION_MARK_PREFIX,
214
                'No compactor to register'
215
            );
216
217
            return;
218
        }
219
220
        $logger->log(
221
            BuildLogger::QUESTION_MARK_PREFIX,
222
            'Registering compactors'
223
        );
224
225
        $logCompactors = function (Compactor $compactor) use ($logger): void {
226
            $logger->log(
227
                BuildLogger::PLUS_PREFIX,
228
                get_class($compactor)
229
            );
230
        };
231
232
        array_map($logCompactors, $compactors);
233
234
        $box->registerCompactors($compactors);
235
    }
236
237
    private function registerFileMapping(Configuration $config, Box $box, BuildLogger $logger): void
238
    {
239
        $fileMapper = $config->getFileMapper();
240
241
        $this->logMap($fileMapper, $logger);
242
243
        $box->registerFileMapping(
244
            $config->getBasePathRetriever(),
245
            $fileMapper
246
        );
247
    }
248
249
    private function addFiles(Configuration $config, Box $box, BuildLogger $logger): void
250
    {
251
        $logger->log(BuildLogger::QUESTION_MARK_PREFIX, 'Adding binary files');
252
253
        $count = count($config->getBinaryFiles());
254
255
        $box->addFiles($config->getBinaryFiles(), true);
256
257
        $logger->log(
258
            BuildLogger::CHEVRON_PREFIX,
259
            0 === $count
260
                ? 'No file found'
261
                : sprintf('%d file(s)', $count)
262
        );
263
264
        $logger->log(BuildLogger::QUESTION_MARK_PREFIX, 'Adding files');
265
266
        $count = count($config->getFiles());
267
268
        $box->addFiles($config->getFiles(), false);
269
270
        $logger->log(
271
            BuildLogger::CHEVRON_PREFIX,
272
            0 === $count
273
                ? 'No file found'
274
                : sprintf('%d file(s)', $count)
275
        );
276
    }
277
278
    private function registerMainScript(Configuration $config, Box $box, BuildLogger $logger): ?string
279
    {
280
        $main = $config->getMainScriptPath();
281
282
        $logger->log(
283
            BuildLogger::QUESTION_MARK_PREFIX,
284
            sprintf(
285
                'Adding main file: %s',
286
                $main
287
            )
288
        );
289
290
        $localMain = $box->addFile(
291
            $main,
292
            $config->getMainScriptContents()
293
        );
294
295
        $relativeMain = make_path_relative($main, $config->getBasePath());
296
297
        if ($localMain !== $relativeMain) {
298
            $logger->log(
299
                BuildLogger::CHEVRON_PREFIX,
300
                $localMain
301
            );
302
        }
303
304
        return $localMain;
305
    }
306
307
    private function registerStub(Configuration $config, Box $box, string $main, BuildLogger $logger): void
308
    {
309
        if (true === $config->isStubGenerated()) {
310
            $logger->log(
311
                BuildLogger::QUESTION_MARK_PREFIX,
312
                'Generating new stub'
313
            );
314
315
            $stub = $this->createStub($config, $main, $logger);
316
317
            $box->getPhar()->setStub($stub->generate());
318
        } elseif (null !== ($stub = $config->getStubPath())) {
319
            $logger->log(
320
                BuildLogger::QUESTION_MARK_PREFIX,
321
                sprintf(
322
                    'Using stub file: %s',
323
                    $stub
324
                )
325
            );
326
327
            $box->registerStub($stub);
328
        } else {
329
            if (null !== $config->getAlias()) {
330
                $aliasWasAdded = $box->getPhar()->setAlias($config->getAlias());
331
332
                Assertion::true(
333
                    $aliasWasAdded,
334
                    sprintf(
335
                        'The alias "%s" is invalid. See Phar::setAlias() documentation for more information.',
336
                        $config->getAlias()
337
                    )
338
                );
339
            }
340
341
            $box->getPhar()->setDefaultStub($main);
342
343
            $logger->log(
344
                BuildLogger::QUESTION_MARK_PREFIX,
345
                'Using default stub'
346
            );
347
        }
348
    }
349
350
    private function configureMetadata(Configuration $config, Box $box, BuildLogger $logger): void
351
    {
352
        if (null !== ($metadata = $config->getMetadata())) {
353
            $logger->log(
354
                BuildLogger::QUESTION_MARK_PREFIX,
355
                'Setting metadata'
356
            );
357
358
            $logger->log(
359
                BuildLogger::MINUS_PREFIX,
360
                is_string($metadata) ? $metadata : var_export($metadata, true)
361
            );
362
363
            $box->getPhar()->setMetadata($metadata);
364
        }
365
    }
366
367
    private function configureCompressionAlgorithm(Configuration $config, Box $box, BuildLogger $logger): void
368
    {
369
        if (null !== ($algorithm = $config->getCompressionAlgorithm())) {
370
            $logger->log(
371
                BuildLogger::QUESTION_MARK_PREFIX,
372
                sprintf(
373
                    'Compressing with the algorithm "<comment>%s</comment>"',
374
                    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

374
                    /** @scrutinizer ignore-type */ array_search($algorithm, get_phar_compression_algorithms(), true)
Loading history...
375
                )
376
            );
377
378
            $box->getPhar()->compressFiles($algorithm);
379
        } else {
380
            $logger->log(
381
                BuildLogger::QUESTION_MARK_PREFIX,
382
                '<error>No compression</error>'
383
            );
384
        }
385
    }
386
387
    private function signPhar(
388
        Configuration $config,
389
        Box $box,
390
        string $path,
391
        InputInterface $input,
392
        OutputInterface $output,
393
        BuildLogger $logger
394
    ): void {
395
        // sign using private key, if applicable
396
        //TODO: check that out
397
        remove($path.'.pubkey');
398
399
        $key = $config->getPrivateKeyPath();
400
401
        if (null === $key) {
402
            if (null !== ($algorithm = $config->getSigningAlgorithm())) {
0 ignored issues
show
introduced by
The condition null !== $algorithm = $c...->getSigningAlgorithm() can never be false.
Loading history...
403
                $box->getPhar()->setSignatureAlgorithm($algorithm);
404
            }
405
406
            return;
407
        }
408
409
        $logger->log(
410
            BuildLogger::QUESTION_MARK_PREFIX,
411
            'Signing using a private key'
412
        );
413
414
        $passphrase = $config->getPrivateKeyPassphrase();
415
416
        if ($config->isPrivateKeyPrompt()) {
417
            if (false === $input->isInteractive()) {
418
                throw new RuntimeException(
419
                    sprintf(
420
                        'Accessing to the private key "%s" requires a passphrase but none provided. Either '
421
                        .'provide one or run this command in interactive mode.',
422
                        $key
423
                    )
424
                );
425
            }
426
427
            /** @var $dialog QuestionHelper */
428
            $dialog = $this->getHelper('question');
429
430
            $question = new Question('Private key passphrase:');
431
            $question->setHidden(false);
432
            $question->setHiddenFallback(false);
433
434
            $passphrase = $dialog->ask($input, $output, $question);
435
436
            $output->writeln('');
437
        }
438
439
        $box->signUsingFile($key, $passphrase);
440
    }
441
442
    private function correctPermissions(string $path, Configuration $config, BuildLogger $logger): void
443
    {
444
        if (null !== ($chmod = $config->getFileMode())) {
445
            $logger->log(
446
                BuildLogger::QUESTION_MARK_PREFIX,
447
                "Setting file permissions to <comment>$chmod</comment>"
448
            );
449
450
            chmod($path, $chmod);
451
        }
452
    }
453
454
    private function createStub(Configuration $config, ?string $main, BuildLogger $logger): StubGenerator
455
    {
456
        $stub = StubGenerator::create()
457
            ->alias($config->getAlias())
458
            ->index($main)
459
            ->intercept($config->isInterceptFileFuncs())
460
        ;
461
462
        if (null !== ($shebang = $config->getShebang())) {
463
            $logger->log(
464
                BuildLogger::MINUS_PREFIX,
465
                sprintf(
466
                    'Using shebang line: %s',
467
                    $shebang
468
                )
469
            );
470
471
            $stub->shebang($shebang);
472
        } else {
473
            $logger->log(
474
                BuildLogger::MINUS_PREFIX,
475
                'No shebang line'
476
            );
477
        }
478
479
        if (null !== ($bannerPath = $config->getStubBannerPath())) {
480
            $logger->log(
481
                BuildLogger::MINUS_PREFIX,
482
                sprintf(
483
                    'Using custom banner from file: %s',
484
                    $bannerPath
485
                )
486
            );
487
488
            $stub->banner($config->getStubBannerContents());
489
        } elseif (null !== ($banner = $config->getStubBannerContents())) {
490
            $logger->log(
491
                BuildLogger::MINUS_PREFIX,
492
                'Using banner:'
493
            );
494
495
            $bannerLines = explode("\n", $banner);
496
497
            foreach ($bannerLines as $bannerLine) {
498
                $logger->log(
499
                    BuildLogger::CHEVRON_PREFIX,
500
                    $bannerLine
501
                );
502
            }
503
504
            $stub->banner($banner);
505
        }
506
507
        return $stub;
508
    }
509
510
    private function logMap(MapFile $fileMapper, BuildLogger $logger): void
511
    {
512
        $map = $fileMapper->getMap();
513
514
        if ([] === $map) {
515
            return;
516
        }
517
518
        $logger->log(
519
            BuildLogger::QUESTION_MARK_PREFIX,
520
            'Mapping paths'
521
        );
522
523
        foreach ($map as $item) {
524
            foreach ($item as $match => $replace) {
525
                if ('' === $match) {
526
                    $match = '(all)';
527
                    $replace .= '/';
528
                }
529
530
                $logger->log(
531
                    BuildLogger::MINUS_PREFIX,
532
                    sprintf(
533
                        '%s <info>></info> %s',
534
                        $match,
535
                        $replace
536
                    )
537
                );
538
            }
539
        }
540
    }
541
}
542