Completed
Pull Request — master (#36)
by Théo
02:06
created

Build::correctPermissions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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