Completed
Pull Request — master (#26)
by Théo
02:11
created

Build   D

Complexity

Total Complexity 55

Size/Duplication

Total Lines 939
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 55
dl 0
loc 939
rs 4.8
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
B registerStub() 0 30 3
B setReplacementValues() 0 27 3
A configure() 0 8 1
B createStub() 0 50 4
C addFilesToBox() 0 52 8
B createPhar() 0 26 1
B execute() 0 43 3
B registerMainScript() 0 36 3
B signPhar() 0 56 6
B alertAboutMappedPaths() 0 28 5
B addFiles() 0 60 5
A removeExistingPhar() 0 18 2
B registerCompactors() 0 31 2
A configureMetadata() 0 16 3
A correctPermissions() 0 10 2
A loadBootstrapFile() 0 18 2
A configureCompressionAlgorithm() 0 18 2

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\Command;
16
17
use KevinGH\Box\Box;
18
use KevinGH\Box\Compactor;
19
use KevinGH\Box\Configuration;
20
use KevinGH\Box\Logger\BuildLogger;
21
use KevinGH\Box\StubGenerator;
22
use RuntimeException;
23
use SplFileInfo;
24
use Symfony\Component\Console\Helper\QuestionHelper;
25
use Symfony\Component\Console\Input\InputInterface;
26
use Symfony\Component\Console\Output\OutputInterface;
27
use Symfony\Component\Console\Question\Question;
28
use Symfony\Component\Console\Style\SymfonyStyle;
29
use Symfony\Component\Filesystem\Filesystem;
30
use function KevinGH\Box\formatted_filesize;
31
use function KevinGH\Box\get_phar_compression_algorithms;
32
33
final class Build extends Configurable
34
{
35
    /**
36
     * {@inheritdoc}
37
     */
38
    protected function configure(): void
39
    {
40
        parent::configure();
41
42
        $this->setName('build');
43
        $this->setDescription('Builds a new PHAR');
44
        $this->setHelp(
45
            <<<HELP
46
The <info>%command.name%</info> command will build a new Phar based on a variety of settings.
47
<comment>
48
  This command relies on a configuration file for loading
49
  Phar packaging settings. If a configuration file is not
50
  specified through the <info>--configuration|-c</info> option, one of
51
  the following files will be used (in order): <info>box.json,
52
  box.json.dist</info>
53
</comment>
54
The configuration file is actually a JSON object saved to a file.
55
Note that all settings are optional.
56
<comment>
57
  {
58
    "algorithm": ?,
59
    "alias": ?,
60
    "banner": ?,
61
    "banner-file": ?,
62
    "base-path": ?,
63
    "blacklist": ?,
64
    "bootstrap": ?,
65
    "chmod": ?,
66
    "compactors": ?,
67
    "compression": ?,
68
    "datetime": ?,
69
    "datetime_format": ?,
70
    "directories": ?,
71
    "directories-bin": ?,
72
    "extract": ?,
73
    "files": ?,
74
    "files-bin": ?,
75
    "finder": ?,
76
    "finder-bin": ?,
77
    "git-version": ?,
78
    "intercept": ?,
79
    "key": ?,
80
    "key-pass": ?,
81
    "main": ?,
82
    "map": ?,
83
    "metadata": ?,
84
    "mimetypes": ?,
85
    "mung": ?,
86
    "not-found": ?,
87
    "output": ?,
88
    "replacements": ?,
89
    "shebang": ?,
90
    "stub": ?,
91
    "web": ?
92
  }
93
</comment>
94
95
96
97
The <info>algorithm</info> <comment>(string, integer)</comment> setting is the signing algorithm to
98
use when the Phar is built <comment>(Phar::setSignatureAlgorithm())</comment>. It can an
99
integer value (the value of the constant), or the name of the Phar
100
constant. The following is a list of the signature algorithms listed
101
on the help page:
102
<comment>
103
  - MD5 (Phar::MD5)
104
  - SHA1 (Phar::SHA1)
105
  - SHA256 (Phar::SHA256)
106
  - SHA512 (Phar::SHA512)
107
  - OPENSSL (Phar::OPENSSL)
108
</comment>
109
The <info>alias</info> <comment>(string)</comment> setting is used when generating a new stub to call
110
the <comment>Phar::mapPhar()</comment> method. This makes it easier to refer to files in
111
the Phar.
112
113
The <info>annotations</info> <comment>(boolean, object)</comment> setting is used to enable compacting
114
annotations in PHP source code. By setting it to <info>true</info>, all Doctrine-style
115
annotations are compacted in PHP files. You may also specify a list of
116
annotations to ignore, which will be stripped while protecting the
117
remaining annotations:
118
<comment>
119
  {
120
      "annotations": {
121
          "ignore": [
122
              "author",
123
              "package",
124
              "version",
125
              "see"
126
          ]
127
      }
128
  }
129
</comment>
130
You may want to see this website for a list of annotations which are
131
commonly ignored:
132
<comment>
133
  https://github.com/herrera-io/php-annotations
134
</comment>
135
The <info>banner</info> <comment>(string)</comment> setting is the banner comment that will be used when
136
a new stub is generated. The value of this setting must not already be
137
enclosed within a comment block, as it will be automatically done for
138
you.
139
140
The <info>banner-file</info> <comment>(string)</comment> setting is like <info>stub-banner</info>, except it is a
141
path to the file that will contain the comment. Like <info>stub-banner</info>, the
142
comment must not already be enclosed in a comment block.
143
144
The <info>base-path</info> <comment>(string)</comment> setting is used to specify where all of the
145
relative file paths should resolve to. This does not, however, alter
146
where the built Phar will be stored <comment>(see: <info>output</info>)</comment>. By default, the
147
base path is the directory containing the configuration file.
148
149
The <info>blacklist</info> <comment>(string, array)</comment> setting is a list of files that must
150
not be added. The files blacklisted are the ones found using the other
151
available configuration settings: <info>directories, directories-bin, files,
152
files-bin, finder, finder-bin</info>. Note that directory separators are
153
automatically corrected to the platform specific version.
154
155
Assuming that the base directory path is <comment>/home/user/project</comment>:
156
<comment>
157
  {
158
      "blacklist": [
159
          "path/to/file/1"
160
          "path/to/file/2"
161
      ],
162
      "directories": ["src"]
163
  }
164
</comment>
165
The following files will be blacklisted:
166
<comment>
167
  - /home/user/project/src/path/to/file/1
168
  - /home/user/project/src/path/to/file/2
169
</comment>
170
But not these files:
171
<comment>
172
  - /home/user/project/src/another/path/to/file/1
173
  - /home/user/project/src/another/path/to/file/2
174
</comment>
175
The <info>bootstrap</info> <comment>(string)</comment> setting allows you to specify a PHP file that
176
will be loaded before the <info>build</info> or <info>add</info> commands are used. This is
177
useful for loading third-party file contents compacting classes that
178
were configured using the <info>compactors</info> setting.
179
180
The <info>chmod</info> <comment>(string)</comment> setting is used to change the file permissions of
181
the newly built Phar. The string contains an octal value: <comment>0755</comment>. You
182
must prefix the mode with zero if you specify the mode in decimal.
183
184
The <info>compactors</info> <comment>(string, array)</comment> setting is a list of file contents
185
compacting classes that must be registered. A file compacting class
186
is used to reduce the size of a specific file type. The following is
187
a simple example:
188
<comment>
189
  use Herrera\\Box\\Compactor\\CompactorInterface;
190
191
  class MyCompactor implements CompactorInterface
192
  {
193
      public function compact(\$contents)
194
      {
195
          return trim(\$contents);
196
      }
197
198
      public function supports(\$file)
199
      {
200
          return (bool) preg_match('/\.txt/', \$file);
201
      }
202
  }
203
</comment>
204
The following compactors are included with Box:
205
<comment>
206
  - Herrera\\Box\\Compactor\\Json
207
  - Herrera\\Box\\Compactor\\Php
208
</comment>
209
The <info>compression</info> <comment>(string, integer)</comment> setting is the compression algorithm
210
to use when the Phar is built. The compression affects the individual
211
files within the Phar, and not the Phar as a whole <comment>(Phar::compressFiles())</comment>.
212
The following is a list of the signature algorithms listed on the help
213
page:
214
<comment>
215
  - BZ2 (Phar::BZ2)
216
  - GZ (Phar::GZ)
217
  - NONE (Phar::NONE)
218
</comment>
219
The <info>directories</info> <comment>(string, array)</comment> setting is a list of directory paths
220
relative to <info>base-path</info>. All files ending in <comment>.php</comment> will be automatically
221
compacted, have their placeholder values replaced, and added to the
222
Phar. Files listed in the <info>blacklist</info> setting will not be added.
223
224
The <info>directories-bin</info> <comment>(string, array)</comment> setting is similar to <info>directories</info>,
225
except all file types are added to the Phar unmodified. This is suitable
226
for directories containing images or other binary data.
227
228
The <info>extract</info> <comment>(boolean)</comment> setting determines whether or not the generated
229
stub should include a class to extract the phar. This class would be
230
used if the phar is not available. (Increases stub file size.)
231
232
The <info>files</info> <comment>(string, array)</comment> setting is a list of files paths relative to
233
<info>base-path</info>. Each file will be compacted, have their placeholder files
234
replaced, and added to the Phar. This setting is not affected by the
235
<info>blacklist</info> setting.
236
237
The <info>files-bin</info> <comment>(string, array)</comment> setting is similar to <info>files</info>, except that
238
all files are added to the Phar unmodified. This is suitable for files
239
such as images or those that contain binary data.
240
241
The <info>finder</info> <comment>(array)</comment> setting is a list of JSON objects. Each object key
242
is a name, and each value an argument for the methods in the
243
<comment>Symfony\\Component\\Finder\\Finder</comment> class. If an array of values is provided
244
for a single key, the method will be called once per value in the array.
245
Note that the paths specified for the "in" method are relative to
246
<info>base-path</info>.
247
248
The <info>finder-bin</info> <comment>(array)</comment> setting performs the same function, except all
249
files found by the finder will be treated as binary files, leaving them
250
unmodified.
251
<comment>
252
It may be useful to know that Box imports files in the following order:
253
254
 - finder
255
 - finder-bin
256
 - directories
257
 - directories-bin
258
 - files
259
 - files-bin
260
</comment>
261
The <info>datetime</info> <comment>(string)</comment> setting is the name of a placeholder value that
262
will be replaced in all non-binary files by the current datetime.
263
264
Example: <comment>2015-01-28 14:55:23</comment>
265
266
The <info>datetime_format</info> <comment>(string)</comment> setting accepts a valid PHP date format. It can be used to change the format for the <info>datetime</info> setting.
267
268
Example: <comment>Y-m-d H:i:s</comment>
269
270
The <info>git-commit</info> <comment>(string)</comment> setting is the name of a placeholder value that
271
will be replaced in all non-binary files by the current Git commit hash
272
of the repository.
273
274
Example: <comment>e558e335f1d165bc24d43fdf903cdadd3c3cbd03</comment>
275
276
The <info>git-commit-short</info> <comment>(string)</comment> setting is the name of a placeholder value
277
that will be replaced in all non-binary files by the current Git short
278
commit hash of the repository.
279
280
Example: <comment>e558e33</comment>
281
282
The <info>git-tag</info> <comment>(string)</comment> setting is the name of a placeholder value that will
283
be replaced in all non-binary files by the current Git tag of the
284
repository.
285
286
Examples:
287
<comment>
288
 - 2.0.0
289
 - 2.0.0-2-ge558e33
290
</comment>
291
The <info>git-version</info> <comment>(string)</comment> setting is the name of a placeholder value that
292
will be replaced in all non-binary files by the one of the following (in
293
order):
294
295
  - The git repository's most recent tag.
296
  - The git repository's current short commit hash.
297
298
The short commit hash will only be used if no tag is available.
299
300
The <info>intercept</info> <comment>(boolean)</comment> setting is used when generating a new stub. If
301
setting is set to <comment>true</comment>, the <comment>Phar::interceptFileFuncs();</comment> method will be
302
called in the stub.
303
304
The <info>key</info> <comment>(string)</comment> setting is used to specify the path to the private key
305
file. The private key file will be used to sign the Phar using the
306
<comment>OPENSSL</comment> signature algorithm. If an absolute path is not provided, the
307
path will be relative to the current working directory.
308
309
The <info>key-pass</info> <comment>(string, boolean)</comment> setting is used to specify the passphrase
310
for the private <info>key</info>. If a <comment>string</comment> is provided, it will be used as is as
311
the passphrase. If <comment>true</comment> is provided, you will be prompted for the
312
passphrase.
313
314
The <info>main</info> <comment>(string)</comment> setting is used to specify the file (relative to
315
<info>base-path</info>) that will be run when the Phar is executed from the command
316
line. If the file was not added by any of the other file adding settings,
317
it will be automatically added after it has been compacted and had its
318
placeholder values replaced. Also, the #! line will be automatically
319
removed if present.
320
321
The <info>map</info> <comment>(array)</comment> setting is used to change where some (or all) files are
322
stored inside the phar. The key is a beginning of the relative path that
323
will be matched against the file being added to the phar. If the key is
324
a match, the matched segment will be replaced with the value. If the key
325
is empty, the value will be prefixed to all paths (except for those
326
already matched by an earlier key).
327
328
<comment>
329
  {
330
    "map": [
331
      { "my/test/path": "src/Test" },
332
      { "": "src/Another" }
333
    ]
334
  }
335
</comment>
336
337
(with the files)
338
339
<comment>
340
  1. my/test/path/file.php
341
  2. my/test/path/some/other.php
342
  3. my/test/another.php
343
</comment>
344
345
(will be stored as)
346
347
<comment>
348
  1. src/Test/file.php
349
  2. src/Test/some/other.php
350
  3. src/Another/my/test/another.php
351
</comment>
352
353
The <info>metadata</info> <comment>(any)</comment> setting can be any value. This value will be stored as
354
metadata that can be retrieved from the built Phar <comment>(Phar::getMetadata())</comment>.
355
356
The <info>mimetypes</info> <comment>(object)</comment> setting is used when generating a new stub. It is
357
a map of file extensions and their mimetypes. To see a list of the default
358
mapping, please visit:
359
360
  <comment>http://www.php.net/manual/en/phar.webphar.php</comment>
361
362
The <info>mung</info> <comment>(array)</comment> setting is used when generating a new stub. It is a list
363
of server variables to modify for the Phar. This setting is only useful
364
when the <info>web</info> setting is enabled.
365
366
The <info>not-found</info> <comment>(string)</comment> setting is used when generating a new stub. It
367
specifies the file that will be used when a file is not found inside the
368
Phar. This setting is only useful when <info>web</info> setting is enabled.
369
370
The <info>output</info> <comment>(string)</comment> setting specifies the file name and path of the newly
371
built Phar. If the value of the setting is not an absolute path, the path
372
will be relative to the current working directory.
373
374
The <info>replacements</info> <comment>(object)</comment> setting is a map of placeholders and their
375
values. The placeholders are replaced in all non-binary files with the
376
specified values.
377
378
The <info>shebang</info> <comment>(string)</comment> setting is used to specify the shebang line used
379
when generating a new stub. By default, this line is used:
380
381
  <comment>#!/usr/bin/env php</comment>
382
383
The shebang line can be removed altogether if <comment>false</comment> or an empty string
384
is provided.
385
386
The <info>stub</info> <comment>(string, boolean)</comment> setting is used to specify the location of a
387
stub file, or if one should be generated. If a path is provided, the stub
388
file will be used as is inside the Phar. If <comment>true</comment> is provided, a new stub
389
will be generated. If <comment>false (or nothing)</comment> is provided, the default stub
390
used by the Phar class will be used.
391
392
The <info>web</info> <comment>(boolean)</comment> setting is used when generating a new stub. If <comment>true</comment> is
393
provided, <comment>Phar::webPhar()</comment> will be called in the stub.
394
HELP
395
        );
396
    }
397
398
    /**
399
     * {@inheritdoc}
400
     */
401
    protected function execute(InputInterface $input, OutputInterface $output): void
402
    {
403
        $io = new SymfonyStyle($input, $output);
404
405
        $io->writeln($this->getApplication()->getHelp());
406
        $io->writeln('');
407
408
        $config = $this->getConfig($input);
409
        $path = $config->getOutputPath();
410
411
        $logger = new BuildLogger($io);
412
413
        $startTime = microtime(true);
414
415
        $this->loadBootstrapFile($config, $logger);
416
        $this->removeExistingPhar($config, $logger);
417
418
        $logger->logStartBuilding($path);
419
420
        $this->createPhar($path, $config, $input, $output, $logger);
421
422
        $this->correctPermissions($path, $config, $logger);
423
424
        $logger->log(
425
            BuildLogger::STAR_PREFIX,
426
            'Done.'
427
        );
428
429
        if ($io->getVerbosity() >= OutputInterface::VERBOSITY_NORMAL) {
430
            $io->comment(
431
                sprintf(
432
                    "<info>Size: %s\nMemory usage: %.2fMB (peak: %.2fMB), time: %.2fs<info>",
433
                    formatted_filesize($path),
434
                    round(memory_get_usage() / 1024 / 1024, 2),
435
                    round(memory_get_peak_usage() / 1024 / 1024, 2),
436
                    round(microtime(true) - $startTime, 2)
437
                )
438
            );
439
        }
440
441
        if (false === file_exists($path)) {
442
            //TODO: check that one
443
            $io->warning('The archive was not generated because it did not have any contents');
444
        }
445
    }
446
447
    private function createPhar(
448
        string $path,
449
        Configuration $config,
450
        InputInterface $input,
451
        OutputInterface $output,
452
        BuildLogger $logger
453
    ): void {
454
        $box = Box::create($path);
455
456
        $box->getPhar()->startBuffering();
457
458
        $this->setReplacementValues($config, $box, $logger);
459
        $this->registerCompactors($config, $box, $logger);
460
        $this->alertAboutMappedPaths($config, $logger);
461
462
        $this->addFiles($config, $box, $logger);
463
464
        $main = $this->registerMainScript($config, $box, $logger);
465
466
        $this->registerStub($config, $box, $main, $logger);
467
        $this->configureMetadata($config, $box, $logger);
468
        $this->configureCompressionAlgorithm($config, $box, $logger);
469
470
        $box->getPhar()->stopBuffering();
471
472
        $this->signPhar($config, $box, $path, $input, $output, $logger);
473
    }
474
475
    private function loadBootstrapFile(Configuration $config, BuildLogger $logger): void
476
    {
477
        $file = $config->getBootstrapFile();
478
479
        if (null === $file) {
480
            return;
481
        }
482
483
        $logger->log(
484
            BuildLogger::QUESTION_MARK_PREFIX,
485
            sprintf(
486
                'Loading the bootstrap file "%s"',
487
                $file
488
            ),
489
            OutputInterface::VERBOSITY_VERBOSE
490
        );
491
492
        $config->loadBootstrap();
493
    }
494
495
    private function removeExistingPhar(Configuration $config, BuildLogger $logger): void
496
    {
497
        $path = $config->getOutputPath();
498
499
        if (false === file_exists($path)) {
500
            return;
501
        }
502
503
        $logger->log(
504
            BuildLogger::QUESTION_MARK_PREFIX,
505
            sprintf(
506
                'Removing the existing PHAR "%s"',
507
                $path
508
            ),
509
            OutputInterface::VERBOSITY_VERBOSE
510
        );
511
512
        (new Filesystem())->remove($path);
513
    }
514
515
    private function setReplacementValues(Configuration $config, Box $box, BuildLogger $logger): void
516
    {
517
        $values = $config->getProcessedReplacements();
518
519
        if ([] === $values) {
520
            return;
521
        }
522
523
        $logger->log(
524
            BuildLogger::QUESTION_MARK_PREFIX,
525
            'Setting replacement values',
526
            OutputInterface::VERBOSITY_VERBOSE
527
        );
528
529
        foreach ($values as $key => $value) {
530
            $logger->log(
531
                BuildLogger::PLUS_PREFIX,
532
                sprintf(
533
                    '%s: %s',
534
                    $key,
535
                    $value
536
                ),
537
                OutputInterface::VERBOSITY_VERBOSE
538
            );
539
        }
540
541
        $box->registerPlaceholders($values);
542
    }
543
544
    private function registerCompactors(Configuration $config, Box $box, BuildLogger $logger): void
545
    {
546
        $compactors = $config->getCompactors();
547
548
        if ([] === $compactors) {
549
            $logger->log(
550
                BuildLogger::QUESTION_MARK_PREFIX,
551
                'No compactor to register',
552
                OutputInterface::VERBOSITY_VERBOSE
553
            );
554
555
            return;
556
        }
557
558
        $logger->log(
559
            BuildLogger::QUESTION_MARK_PREFIX,
560
            'Registering compactors',
561
            OutputInterface::VERBOSITY_VERBOSE
562
        );
563
564
        $logCompactors = function (Compactor $compactor) use ($logger): void {
565
            $logger->log(
566
                BuildLogger::PLUS_PREFIX,
567
                get_class($compactor),
568
                OutputInterface::VERBOSITY_VERBOSE
569
            );
570
        };
571
572
        array_map($logCompactors, $compactors);
573
574
        $box->registerCompactors($compactors);
575
    }
576
577
    private function alertAboutMappedPaths(Configuration $config, BuildLogger $logger): void
578
    {
579
        $map = $config->getMap();
580
581
        if ([] === $map) {
582
            return;
583
        }
584
585
        $logger->log(
586
            BuildLogger::QUESTION_MARK_PREFIX,
587
            'Mapping paths',
588
            OutputInterface::VERBOSITY_VERBOSE
589
        );
590
591
        foreach ($map as $item) {
592
            foreach ($item as $match => $replace) {
593
                if (empty($match)) {
594
                    $match = '(all)';
595
                }
596
597
                $logger->log(
598
                    BuildLogger::MINUS_PREFIX,
599
                    sprintf(
600
                        '%s <info>></info> %s',
601
                        $match,
602
                        $replace
603
                    ),
604
                    OutputInterface::VERBOSITY_VERBOSE
605
                );
606
            }
607
        }
608
    }
609
610
    private function addFiles(Configuration $config, Box $box, BuildLogger $logger): void
611
    {
612
        if ([] !== ($iterators = $config->getFilesIterators())) {
613
            $logger->log(
614
                BuildLogger::QUESTION_MARK_PREFIX,
615
                'Adding finder files',
616
                OutputInterface::VERBOSITY_VERBOSE
617
            );
618
619
            foreach ($iterators as $iterator) {
620
                $this->addFilesToBox($config, $box, $iterator, null, false, $logger);
621
            }
622
        }
623
624
        if ([] !== ($iterators = $config->getBinaryIterators())) {
625
            $logger->log(
626
                BuildLogger::QUESTION_MARK_PREFIX,
627
                'Adding binary finder files',
628
                OutputInterface::VERBOSITY_VERBOSE
629
            );
630
631
            foreach ($iterators as $iterator) {
632
                $this->addFilesToBox($config, $box, $iterator, null, true, $logger);
633
            }
634
        }
635
636
        $this->addFilesToBox(
637
            $config,
638
            $box,
639
            $config->getDirectoriesIterator(),
640
            'Adding directories',
641
            false,
642
            $logger
643
        );
644
645
        $this->addFilesToBox(
646
            $config,
647
            $box,
648
            $config->getBinaryDirectoriesIterator(),
649
            'Adding binary directories',
650
            true,
651
            $logger
652
        );
653
654
        $this->addFilesToBox(
655
            $config,
656
            $box,
657
            $config->getFilesIterator(),
658
            'Adding files',
659
            false,
660
            $logger
661
        );
662
663
        $this->addFilesToBox(
664
            $config,
665
            $box,
666
            $config->getBinaryFilesIterator(),
667
            'Adding binary files',
668
            true,
669
            $logger
670
        );
671
    }
672
673
    private function registerMainScript(Configuration $config, Box $box, BuildLogger $logger): ?string
674
    {
675
        $main = $config->getMainScriptPath();
676
677
        if (null === $main) {
678
            return null;
679
        }
680
681
        $logger->log(
682
            BuildLogger::QUESTION_MARK_PREFIX,
683
            sprintf(
684
                'Adding main file: %s',
685
                $config->getBasePath().DIRECTORY_SEPARATOR.$main
686
            ),
687
            OutputInterface::VERBOSITY_VERBOSE
688
        );
689
690
        $mapper = $config->getMapper();
691
        $pharPath = $mapper($main);
692
693
        if (null !== $pharPath) {
694
            $logger->log(
695
                BuildLogger::CHEVRON_PREFIX,
696
                $pharPath,
697
                OutputInterface::VERBOSITY_VERBOSE
698
            );
699
700
            $main = $pharPath;
701
        }
702
703
        $box->addFromString(
704
            $main,
705
            $config->getMainScriptContent()
706
        );
707
708
        return $main;
709
    }
710
711
    private function registerStub(Configuration $config, Box $box, ?string $main, BuildLogger $logger): void
712
    {
713
        if (true === $config->isStubGenerated()) {
714
            $logger->log(
715
                BuildLogger::QUESTION_MARK_PREFIX,
716
                'Generating new stub',
717
                OutputInterface::VERBOSITY_VERBOSE
718
            );
719
720
            $stub = $this->createStub($config, $main, $logger);
721
722
            $box->getPhar()->setStub($stub->generate());
723
        } elseif (null !== ($stub = $config->getStubPath())) {
724
            $stub = $config->getBasePath().DIRECTORY_SEPARATOR.$stub;
725
726
            $logger->log(
727
                BuildLogger::QUESTION_MARK_PREFIX,
728
                sprintf(
729
                    'Using stub file: %s',
730
                    $stub
731
                ),
732
                OutputInterface::VERBOSITY_VERBOSE
733
            );
734
735
            $box->registerStub($stub);
736
        } else {
737
            $logger->log(
738
                BuildLogger::QUESTION_MARK_PREFIX,
739
                'Using default stub',
740
                OutputInterface::VERBOSITY_VERBOSE
741
            );
742
        }
743
    }
744
745
    private function configureMetadata(Configuration $config, Box $box, BuildLogger $logger): void
746
    {
747
        if (null !== ($metadata = $config->getMetadata())) {
748
            $logger->log(
749
                BuildLogger::QUESTION_MARK_PREFIX,
750
                'Setting metadata',
751
                OutputInterface::VERBOSITY_VERBOSE
752
            );
753
754
            $logger->log(
755
                BuildLogger::MINUS_PREFIX,
756
                is_string($metadata) ? $metadata : var_export($metadata, true),
757
                OutputInterface::VERBOSITY_VERBOSE
758
            );
759
760
            $box->getPhar()->setMetadata($metadata);
761
        }
762
    }
763
764
    private function configureCompressionAlgorithm(Configuration $config, Box $box, BuildLogger $logger): void
765
    {
766
        if (null !== ($algorithm = $config->getCompressionAlgorithm())) {
767
            $logger->log(
768
                BuildLogger::QUESTION_MARK_PREFIX,
769
                sprintf(
770
                    'Compressing with the algorithm "<comment>%s</comment>"',
771
                    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

771
                    /** @scrutinizer ignore-type */ array_search($algorithm, get_phar_compression_algorithms(), true)
Loading history...
772
                ),
773
                OutputInterface::VERBOSITY_VERBOSE
774
            );
775
776
            $box->getPhar()->compressFiles($algorithm);
777
        } else {
778
            $logger->log(
779
                BuildLogger::QUESTION_MARK_PREFIX,
780
                '<error>No compression</error>',
781
                OutputInterface::VERBOSITY_VERBOSE
782
            );
783
        }
784
    }
785
786
    private function signPhar(
787
        Configuration $config,
788
        Box $box,
789
        string $path,
790
        InputInterface $input,
791
        OutputInterface $output,
792
        BuildLogger $logger
793
    ): void {
794
        // sign using private key, if applicable
795
        //TODO: check that out
796
        if (file_exists($path.'.pubkey')) {
797
            unlink($path.'.pubkey');
798
        }
799
800
        $key = $config->getPrivateKeyPath();
801
802
        if (null === $key) {
803
            if (null !== ($algorithm = $config->getSigningAlgorithm())) {
0 ignored issues
show
introduced by
The condition null !== $algorithm = $c...->getSigningAlgorithm() can never be false.
Loading history...
804
                $box->getPhar()->setSignatureAlgorithm($algorithm);
805
            }
806
807
            return;
808
        }
809
810
        $logger->log(
811
            BuildLogger::QUESTION_MARK_PREFIX,
812
            'Signing using a private key',
813
            OutputInterface::VERBOSITY_VERBOSE
814
        );
815
816
        $passphrase = $config->getPrivateKeyPassphrase();
817
818
        if ($config->isPrivateKeyPrompt()) {
819
            if (false === $input->isInteractive()) {
820
                throw new RuntimeException(
821
                    sprintf(
822
                        'Accessing to the private key "%s" requires a passphrase but none provided. Either '
823
                        .'provide one or run this command in interactive mode.',
824
                        $key
825
                    )
826
                );
827
            }
828
829
            /** @var $dialog QuestionHelper */
830
            $dialog = $this->getHelper('question');
831
832
            $question = new Question('Private key passphrase:');
833
            $question->setHidden(false);
834
            $question->setHiddenFallback(false);
835
836
            $passphrase = $dialog->ask($input, $output, $question);
837
838
            $output->writeln('');
839
        }
840
841
        $box->signUsingFile($key, $passphrase);
842
    }
843
844
    private function correctPermissions(string $path, Configuration $config, BuildLogger $logger): void
845
    {
846
        if (null !== ($chmod = $config->getFileMode())) {
847
            $logger->log(
848
                BuildLogger::QUESTION_MARK_PREFIX,
849
                "Setting file permissions to <comment>$chmod</comment>",
850
                OutputInterface::VERBOSITY_VERBOSE
851
            );
852
853
            chmod($path, $chmod);
854
        }
855
    }
856
857
    /**
858
     * Adds files using an iterator.
859
     *
860
     * @param Configuration          $config
861
     * @param Box                    $box
862
     * @param iterable|SplFileInfo[] $iterator the iterator
863
     * @param string                 $message  the message to announce
864
     * @param bool                   $binary   Should the adding be binary-safe?
865
     * @param BuildLogger            $logger
866
     */
867
    private function addFilesToBox(
868
        Configuration $config,
869
        Box $box,
870
        ?iterable $iterator,
871
        ?string $message,
872
        bool $binary,
873
        BuildLogger $logger
874
    ): void {
875
        static $count = 0;
876
877
        if (null === $iterator) {
878
            return;
879
        }
880
881
        if (null !== $message) {
882
            $logger->log(BuildLogger::QUESTION_MARK_PREFIX, $message, OutputInterface::VERBOSITY_VERBOSE);
883
        }
884
885
        $box = $binary ? $box->getPhar() : $box;
886
        $mapper = $config->getMapper();
887
888
        foreach ($iterator as $file) {
889
            // @var $file SplFileInfo
890
891
            // Forces garbadge collection from time to time
892
            if (0 === (++$count % 100)) {
893
                gc_collect_cycles();
894
            }
895
896
            $relativePath = $config->retrieveRelativeBasePath($file->getPathname());
897
898
            $mapped = $mapper($relativePath);
899
900
            if (null !== $mapped) {
901
                $relativePath = $mapped;
902
            }
903
904
            if (null !== $mapped) {
905
                $logger->log(
906
                    BuildLogger::CHEVRON_PREFIX,
907
                    $relativePath,
908
                    OutputInterface::VERBOSITY_VERY_VERBOSE
909
                );
910
            } else {
911
                $logger->log(
912
                    BuildLogger::PLUS_PREFIX,
913
                    (string) $file,
914
                    OutputInterface::VERBOSITY_VERY_VERBOSE
915
                );
916
            }
917
918
            $box->addFile((string) $file, $relativePath);
919
        }
920
    }
921
922
    private function createStub(Configuration $config, ?string $main, BuildLogger $logger): StubGenerator
923
    {
924
        $stub = StubGenerator::create()
925
            ->alias($config->getAlias())
926
            ->extract($config->isExtractable())
927
            ->index($main)
928
            ->intercept($config->isInterceptFileFuncs())
929
            ->mimetypes($config->getMimetypeMapping())
930
            ->mung($config->getMungVariables())
931
            ->notFound($config->getNotFoundScriptPath())
932
            ->web($config->isWebPhar());
933
934
        if (null !== ($shebang = $config->getShebang())) {
935
            $logger->log(
936
                BuildLogger::MINUS_PREFIX,
937
                sprintf(
938
                    'Using custom shebang line: %s',
939
                    $shebang
940
                ),
941
                OutputInterface::VERBOSITY_VERY_VERBOSE
942
            );
943
944
            $stub->shebang($shebang);
945
        }
946
947
        if (null !== ($banner = $config->getStubBanner())) {
948
            $logger->log(
949
                BuildLogger::MINUS_PREFIX,
950
                sprintf(
951
                    'Using custom banner: %s',
952
                    $banner
953
                ),
954
                OutputInterface::VERBOSITY_VERY_VERBOSE
955
            );
956
957
            $stub->banner($banner);
958
        } elseif (null !== ($banner = $config->getStubBannerFromFile())) {
959
            $logger->log(
960
                BuildLogger::MINUS_PREFIX,
961
                sprintf(
962
                    'Using custom banner from file: %s',
963
                    $config->getBasePath().DIRECTORY_SEPARATOR.$config->getStubBannerPath()
964
                ),
965
                OutputInterface::VERBOSITY_VERY_VERBOSE
966
            );
967
968
            $stub->banner($banner);
969
        }
970
971
        return $stub;
972
    }
973
}
974