Completed
Push — master ( 20d2a7...83241b )
by Anton
01:20
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
     * @param \Cog\Contracts\Paket\Job\Entities\Job $job
145
     * @param string $command
146
     *
147
     * @throws \Cog\Contracts\Paket\Job\Exceptions\JobFailed
148
     */
149
    private function executeCommand(JobContract $job, string $command): void
150
    {
151
        $jobLogFile = sprintf('%s/%s.log', $this->loggingPath, $job->getId());
152
        $this->addLineToLogFile($jobLogFile, "$ {$command}\n");
153
154
        $commands = [
155
            sprintf('export COMPOSER_HOME=%s', '~/.composer'),
156
            sprintf('cd %s', $this->workingPath),
157
            $command,
158
        ];
159
160
        $fullCommand = implode(' && ', $commands);
161
        $isPtyMode = true;
162
        $timeout = 5 * 60;
163
164
        $process = Process::fromShellCommandline($fullCommand);
165
        $process->setPty($isPtyMode);
166
        $process->setTimeout($timeout);
167
        $process->start();
168
169
        foreach ($process as $type => $data) {
170
            $this->addLineToLogFile($jobLogFile, trim($data));
171
        }
172
173
        $this->addLineToLogFile($jobLogFile, "\n\nDone. Job exited with {$process->getExitCode()}.");
174
175
        if ($process->getExitCode() !== 0) {
176
            throw JobFailed::withExitCode($job, $process->getExitCode());
177
        }
178
    }
179
180
    private function getComposerExecutable(): array
181
    {
182
        if ($this->files->exists($this->workingPath . '/composer.phar')) {
183
            return [$this->getPhpBinary(), 'composer.phar'];
184
        }
185
186
        return ['composer'];
187
    }
188
189
    private function getPhpBinary(): string
190
    {
191
        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...
192
    }
193
194
    private function getProcess(array $command): Process
195
    {
196
        return (new Process($command, $this->workingPath))->setTimeout(null);
197
    }
198
199
    private function addLineToLogFile(string $jobLogFile, string $line): void
200
    {
201
        $this->files->append($jobLogFile, $line . "\n");
202
    }
203
}
204