Completed
Push — master ( e67e18...3776e9 )
by Anton
01:37
created

Composer::writeLineToLogFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
/*
4
 * This file is part of Laravel Paket.
5
 *
6
 * (c) Anton Komarev <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Cog\Laravel\Paket\Support;
15
16
use Cog\Contracts\Paket\Job\Exceptions\JobFailed;
17
use Cog\Contracts\Paket\Job\Entities\Job as JobContract;
18
use Cog\Contracts\Paket\Requirement\Entities\Requirement as RequirementContract;
19
use Illuminate\Filesystem\Filesystem;
20
use Illuminate\Support\ProcessUtils;
21
use Symfony\Component\Process\Process;
22
use Symfony\Component\Process\PhpExecutableFinder;
23
24
final class Composer
25
{
26
    /**
27
     * The filesystem instance.
28
     *
29
     * @var \Illuminate\Filesystem\Filesystem
30
     */
31
    private $files;
32
33
    /**
34
     * The working path to execute Composer from.
35
     *
36
     * @var string
37
     */
38
    private $workingPath;
39
40
    /**
41
     * The logging path to store Composer logs.
42
     *
43
     * @var string
44
     */
45
    private $loggingPath;
46
47
    /**
48
     * Create a new Composer manager instance.
49
     *
50
     * @param \Illuminate\Filesystem\Filesystem $files
51
     * @param string $workingPath
52
     * @param string $loggingPath
53
     */
54
    public function __construct(Filesystem $files, string $workingPath, string $loggingPath)
55
    {
56
        $this->files = $files;
57
        $this->workingPath = $workingPath;
58
        $this->loggingPath = $loggingPath;
59
    }
60
61
    /**
62
     * Regenerate the Composer autoloader files.
63
     *
64
     * @param string[] $extra
65
     * @return void
66
     */
67
    public function dumpAutoload(array $extra = []): void
68
    {
69
        $command = array_merge($this->getComposerExecutable(), ['dump-autoload'], $extra);
70
71
        $this->getProcess($command)->run();
72
    }
73
74
    /**
75
     * Regenerate the optimized Composer autoloader files.
76
     *
77
     * @return void
78
     */
79
    public function dumpOptimized(): void
80
    {
81
        $this->dumpAutoload(['--optimize']);
82
    }
83
84
    /**
85
     * Install Composer requirement.
86
     *
87
     * @param \Cog\Contracts\Paket\Requirement\Entities\Requirement $requirement
88
     * @param \Cog\Contracts\Paket\Job\Entities\Job $job
89
     * @return void
90
     *
91
     * @throws \Cog\Contracts\Paket\Job\Exceptions\JobFailed
92
     */
93
    public function install(RequirementContract $requirement, JobContract $job): void
94
    {
95
        $flags = [
96
            '--no-interaction',
97
        ];
98
99
        if ($requirement->isDevelopment()) {
100
            $flags[] = '--dev';
101
        }
102
103
        $command = sprintf(
104
            '%s require %s %s',
105
            '/usr/bin/composer',
106
            $requirement,
107
            implode(' ', $flags)
108
        );
109
110
        $this->executeCommand($job, $command);
111
    }
112
113
    /**
114
     * Uninstall Composer requirement.
115
     *
116
     * @param \Cog\Contracts\Paket\Requirement\Entities\Requirement $requirement
117
     * @param \Cog\Contracts\Paket\Job\Entities\Job $job
118
     * @return void
119
     *
120
     * @throws \Cog\Contracts\Paket\Job\Exceptions\JobFailed
121
     */
122
    public function uninstall(RequirementContract $requirement, JobContract $job): void
123
    {
124
        $flags = [
125
            '--no-interaction',
126
        ];
127
128
        if ($requirement->isDevelopment()) {
129
            $flags[] = '--dev';
130
        }
131
132
        $command = sprintf(
133
            '%s remove %s %s',
134
            '/usr/bin/composer',
135
            $requirement->getName(),
136
            implode(' ', $flags)
137
        );
138
139
        $this->executeCommand($job, $command);
140
    }
141
142
    /**
143
     * Builds full command, executes it and logs process output.
144
     *
145
     * @param \Cog\Contracts\Paket\Job\Entities\Job $job
146
     * @param string $command
147
     *
148
     * @throws \Cog\Contracts\Paket\Job\Exceptions\JobFailed
149
     */
150
    private function executeCommand(JobContract $job, string $command): void
151
    {
152
        $jobLogFile = sprintf('%s/%s.log', $this->loggingPath, $job->getId());
153
        $this->writeLineToLogFile($jobLogFile, "$ {$command}\n");
154
155
        $commands = [
156
            sprintf('export COMPOSER_HOME=%s', '~/.composer'),
157
            sprintf('cd %s', $this->workingPath),
158
            $command,
159
        ];
160
161
        $fullCommand = implode(' && ', $commands);
162
        $isPtyMode = true;
163
        $timeout = 5 * 60;
164
165
        $process = Process::fromShellCommandline($fullCommand);
166
        $process->setPty($isPtyMode);
167
        $process->setTimeout($timeout);
168
        $process->start();
169
170
        foreach ($process as $type => $data) {
171
            $this->writeLineToLogFile($jobLogFile, trim($data));
172
        }
173
174
        $this->writeLineToLogFile($jobLogFile, "\n\nDone. Job exited with {$process->getExitCode()}.");
175
176
        if ($process->getExitCode() !== 0) {
177
            throw JobFailed::withExitCode($job, $process->getExitCode());
178
        }
179
    }
180
181
    private function getComposerExecutable(): array
182
    {
183
        if ($this->files->exists($this->workingPath . '/composer.phar')) {
184
            return [$this->getPhpBinary(), 'composer.phar'];
185
        }
186
187
        return ['composer'];
188
    }
189
190
    private function getPhpBinary(): string
191
    {
192
        return ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false));
0 ignored issues
show
Bug introduced by antonkomarev
It seems like (new \Symfony\Component\...eFinder())->find(false) targeting Symfony\Component\Proces...xecutableFinder::find() can also be of type false or null; however, Illuminate\Support\ProcessUtils::escapeArgument() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
193
    }
194
195
    private function getProcess(array $command): Process
196
    {
197
        return (new Process($command, $this->workingPath))->setTimeout(null);
198
    }
199
200
    private function writeLineToLogFile(string $jobLogFile, string $line): void
201
    {
202
        $this->files->append($jobLogFile, $line . "\n");
203
    }
204
}
205