Completed
Push — master ( 1183f0...23d94b )
by Anton
01:16
created

src/Support/Composer.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
     * @return void
54
     */
55
    public function __construct(Filesystem $files, string $workingPath, string $loggingPath)
56
    {
57
        $this->files = $files;
58
        $this->workingPath = $workingPath;
59
        $this->loggingPath = $loggingPath;
60
    }
61
62
    /**
63
     * Regenerate the Composer autoloader files.
64
     *
65
     * @param array $extra
66
     * @return void
67
     */
68
    public function dumpAutoload(array $extra = []): void
69
    {
70
        $command = array_merge($this->getComposerExecutable(), ['dump-autoload'], $extra);
71
72
        $this->getProcess($command)->run();
73
    }
74
75
    /**
76
     * Regenerate the optimized Composer autoloader files.
77
     *
78
     * @return void
79
     */
80
    public function dumpOptimized(): void
81
    {
82
        $this->dumpAutoload(['--optimize']);
83
    }
84
85
    /**
86
     * Install Composer requirement.
87
     *
88
     * @param \Cog\Contracts\Paket\Requirement\Entities\Requirement $requirement
89
     * @param \Cog\Contracts\Paket\Job\Entities\Job $job
90
     * @return void
91
     *
92
     * @throws \Cog\Contracts\Paket\Job\Exceptions\JobFailed
93
     */
94 View Code Duplication
    public function install(RequirementContract $requirement, JobContract $job): void
95
    {
96
        $flags = [
97
            '--no-interaction',
98
        ];
99
100
        if ($requirement->isDevelopment()) {
101
            $flags[] = '--dev';
102
        }
103
104
        $command = sprintf(
105
            '%s require %s %s',
106
            '/usr/bin/composer',
107
            $requirement,
108
            implode(' ', $flags)
109
        );
110
111
        $this->executeCommand($job, $command);
112
    }
113
114
    /**
115
     * Uninstall Composer requirement.
116
     *
117
     * @param \Cog\Contracts\Paket\Requirement\Entities\Requirement $requirement
118
     * @param \Cog\Contracts\Paket\Job\Entities\Job $job
119
     * @return void
120
     *
121
     * @throws \Cog\Contracts\Paket\Job\Exceptions\JobFailed
122
     */
123 View Code Duplication
    public function uninstall(RequirementContract $requirement, JobContract $job): void
124
    {
125
        $flags = [
126
            '--no-interaction',
127
        ];
128
129
        if ($requirement->isDevelopment()) {
130
            $flags[] = '--dev';
131
        }
132
133
        $command = sprintf(
134
            '%s remove %s %s',
135
            '/usr/bin/composer',
136
            $requirement->getName(),
137
            implode(' ', $flags)
138
        );
139
140
        $this->executeCommand($job, $command);
141
    }
142
143
    /**
144
     * Builds full command, executes it and logs process output.
145
     *
146
     * @param \Cog\Contracts\Paket\Job\Entities\Job $job
147
     * @param string $command
148
     *
149
     * @throws \Cog\Contracts\Paket\Job\Exceptions\JobFailed
150
     */
151
    private function executeCommand(JobContract $job, string $command): void
152
    {
153
        $jobLogFile = sprintf('%s/%s.log', $this->loggingPath, $job->getId());
154
        $this->addLineToLogFile($jobLogFile, "$ {$command}\n");
155
156
        $commands = [
157
            sprintf('export COMPOSER_HOME=%s', '~/.composer'),
158
            sprintf('cd %s', $this->workingPath),
159
            $command,
160
        ];
161
162
        $fullCommand = implode(' && ', $commands);
163
        $isPtyMode = true;
164
        $timeout = 5 * 60;
165
166
        $process = Process::fromShellCommandline($fullCommand);
167
        $process->setPty($isPtyMode);
168
        $process->setTimeout($timeout);
169
        $process->start();
170
171
        foreach ($process as $type => $data) {
172
            $this->addLineToLogFile($jobLogFile, trim($data));
173
        }
174
175
        $this->addLineToLogFile($jobLogFile, "\n\nDone. Job exited with {$process->getExitCode()}.");
176
177
        if ($process->getExitCode() !== 0) {
178
            throw JobFailed::withExitCode($job, $process->getExitCode());
179
        }
180
    }
181
182
    private function getComposerExecutable(): array
183
    {
184
        if ($this->files->exists($this->workingPath . '/composer.phar')) {
185
            return [$this->getPhpBinary(), 'composer.phar'];
186
        }
187
188
        return ['composer'];
189
    }
190
191
    private function getPhpBinary(): string
192
    {
193
        return ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false));
0 ignored issues
show
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...
194
    }
195
196
    private function getProcess(array $command): Process
197
    {
198
        return (new Process($command, $this->workingPath))->setTimeout(null);
199
    }
200
201
    private function addLineToLogFile(string $jobLogFile, string $line): void
202
    {
203
        $this->files->append($jobLogFile, $line . "\n");
204
    }
205
}
206