Completed
Pull Request — master (#34)
by Théo
02:20
created

Build::createPhar()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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

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