Passed
Pull Request — master (#397)
by Théo
02:14
created

ComposerOrchestrator   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 160
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 20
eloc 75
dl 0
loc 160
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A generateAutoloadStatements() 0 32 2
A __construct() 0 2 1
A dumpAutoload() 0 29 3
A retrieveComposerExecutable() 0 6 1
B dumpAutoloader() 0 40 7
A retrieveAutoloadFile() 0 23 3
A retrieveSubProcessVerbosity() 0 11 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\Composer;
16
17
use Humbug\PhpScoper\Autoload\ScoperAutoloadGenerator;
18
use Humbug\PhpScoper\Whitelist;
19
use KevinGH\Box\Console\Logger\CompileLogger;
20
use function KevinGH\Box\FileSystem\dump_file;
21
use function KevinGH\Box\FileSystem\file_contents;
22
use const PHP_EOL;
23
use function preg_replace;
24
use RuntimeException;
25
use function str_replace;
26
use Symfony\Component\Console\Input\StringInput;
27
use Symfony\Component\Console\Output\NullOutput;
28
use Symfony\Component\Console\Output\OutputInterface;
29
use Symfony\Component\Console\Style\SymfonyStyle;
30
use Symfony\Component\Process\Exception\ProcessFailedException;
31
use Symfony\Component\Process\ExecutableFinder;
32
use Symfony\Component\Process\Process;
33
use function trim;
34
35
/**
36
 * @private
37
 */
38
final class ComposerOrchestrator
39
{
40
    private function __construct()
41
    {
42
    }
43
44
    public static function dumpAutoload(
45
        Whitelist $whitelist,
46
        string $prefix,
47
        bool $excludeDevFiles,
48
        SymfonyStyle $io = null
49
    ): void {
50
        if (null === $io) {
51
            $io = new SymfonyStyle(
52
                new StringInput(''),
53
                new NullOutput()
54
            );
55
        }
56
57
        $logger = new CompileLogger($io);
58
59
        $composerExecutable = self::retrieveComposerExecutable();
60
61
        self::dumpAutoloader($composerExecutable, true === $excludeDevFiles, $logger);
62
63
        if ('' !== $prefix) {
64
            $autoloadFile = self::retrieveAutoloadFile($composerExecutable, $logger);
65
66
            $autoloadContents = self::generateAutoloadStatements(
67
                $whitelist,
68
                $prefix,
69
                file_contents($autoloadFile)
70
            );
71
72
            dump_file($autoloadFile, $autoloadContents);
73
        }
74
    }
75
76
    private static function generateAutoloadStatements(Whitelist $whitelist, string $prefix, string $autoload): string
77
    {
78
        if ([] === $whitelist->toArray()) {
79
            return $autoload;
80
        }
81
82
        $autoload = str_replace('<?php', '', $autoload);
83
84
        $autoload = preg_replace(
85
            '/return (ComposerAutoloaderInit.+::getLoader\(\));/',
86
            '\$loader = $1;',
87
            $autoload
88
        );
89
90
        $whitelistStatements = (new ScoperAutoloadGenerator($whitelist))->dump($prefix);
91
92
        $whitelistStatements = preg_replace(
93
            '/scoper\-autoload\.php \@generated by PhpScoper/',
94
            '@generated by Humbug Box',
95
            $whitelistStatements
96
        );
97
98
        $whitelistStatements = preg_replace(
99
            '/(\s*\\$loader \= .*)/',
100
            $autoload,
101
            $whitelistStatements
102
        );
103
104
        return preg_replace(
105
            '/\n{2,}/m',
106
            PHP_EOL.PHP_EOL,
107
            $whitelistStatements
108
        );
109
    }
110
111
    private static function retrieveComposerExecutable(): string
112
    {
113
        $executableFinder = new ExecutableFinder();
114
        $executableFinder->addSuffix('.phar');
115
116
        return $executableFinder->find('composer');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $executableFinder->find('composer') could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
117
    }
118
119
    private static function dumpAutoloader(string $composerExecutable, bool $noDev, CompileLogger $logger): void
120
    {
121
        $composerCommand = [$composerExecutable, 'dump-autoload', '--classmap-authoritative'];
122
123
        if (true === $noDev) {
124
            $composerCommand[] = '--no-dev';
125
        }
126
127
        if (null !== $verbosity = self::retrieveSubProcessVerbosity($logger->getIO())) {
128
            $composerCommand[] = $verbosity;
129
        }
130
131
        if ($logger->getIO()->isDecorated()) {
132
            $composerCommand[] = '--ansi';
133
        }
134
135
        $dumpAutoloadProcess = new Process($composerCommand);
136
137
        $logger->log(
138
            CompileLogger::CHEVRON_PREFIX,
139
            $dumpAutoloadProcess->getCommandLine(),
140
            OutputInterface::VERBOSITY_VERBOSE
141
        );
142
143
        $dumpAutoloadProcess->run();
144
145
        if (false === $dumpAutoloadProcess->isSuccessful()) {
146
            throw new RuntimeException(
147
                'Could not dump the autoloader.',
148
                0,
149
                new ProcessFailedException($dumpAutoloadProcess)
150
            );
151
        }
152
153
        if ('' !== $output = $dumpAutoloadProcess->getOutput()) {
154
            $logger->getIO()->writeln($output, OutputInterface::VERBOSITY_VERBOSE);
155
        }
156
157
        if ('' !== $output = $dumpAutoloadProcess->getErrorOutput()) {
158
            $logger->getIO()->writeln($output, OutputInterface::VERBOSITY_VERBOSE);
159
        }
160
    }
161
162
    private static function retrieveAutoloadFile(string $composerExecutable, CompileLogger $logger): string
163
    {
164
        $command = [$composerExecutable, 'config', 'vendor-dir'];
165
166
        if ($logger->getIO()->isDecorated()) {
167
            $composerCommand[] = '--ansi';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$composerCommand was never initialized. Although not strictly required by PHP, it is generally a good practice to add $composerCommand = array(); before regardless.
Loading history...
168
        }
169
170
        $vendorDirProcess = new Process($command);
171
172
        $logger->log(
173
            CompileLogger::CHEVRON_PREFIX,
174
            $vendorDirProcess->getCommandLine(),
175
            OutputInterface::VERBOSITY_VERBOSE
176
        );
177
178
        $vendorDirProcess->run();
179
180
        if (false === $vendorDirProcess->isSuccessful()) {
181
            new ProcessFailedException($vendorDirProcess);
182
        }
183
184
        return trim($vendorDirProcess->getOutput()).'/autoload.php';
185
    }
186
187
    private static function retrieveSubProcessVerbosity(SymfonyStyle $io): ?string
188
    {
189
        if ($io->isDebug()) {
190
            return '-vvv';
191
        }
192
193
        if ($io->isVeryVerbose()) {
194
            return '-v';
195
        }
196
197
        return null;
198
    }
199
}
200