Passed
Push — master ( 4165df...b83769 )
by Théo
01:49
created

Build::createStub()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 50
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

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