Completed
Push — sf/last-boss ( 58156b...d8ef98 )
by Kiyotaka
05:59
created

ComposerApiService::runCommand()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 16
nop 3
dl 0
loc 35
rs 8.7377
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.co.jp/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Eccube\Service\Composer;
15
16
use Composer\Console\Application;
17
use Eccube\Common\EccubeConfig;
18
use Eccube\Exception\PluginException;
19
use Eccube\Repository\BaseInfoRepository;
20
use Symfony\Component\Console\Input\ArrayInput;
21
use Symfony\Component\Console\Output\BufferedOutput;
22
use Symfony\Component\Console\Output\OutputInterface;
23
24
/**
25
 * Class ComposerApiService
26
 */
27
class ComposerApiService implements ComposerServiceInterface
28
{
29
    /**
30
     * @var EccubeConfig
31
     */
32
    protected $eccubeConfig;
33
34
    /**
35
     * @var Application
36
     */
37
    private $consoleApplication;
38
39
    private $workingDir;
40
41
    private $baseInfo;
42
43
    public function __construct(EccubeConfig $eccubeConfig, BaseInfoRepository $baseInfoRepository)
44
    {
45
        $this->eccubeConfig = $eccubeConfig;
46
        $this->baseInfo = $baseInfoRepository->get();
47
    }
48
49
    /**
50
     * Run get info command
51
     *
52
     * @param string $pluginName format foo/bar or foo/bar:1.0.0 or "foo/bar 1.0.0"
53
     * @param string|null $version
54
     *
55
     * @return array
56
     *
57
     * @throws PluginException
58
     */
59
    public function execInfo($pluginName, $version)
60
    {
61
        $output = $this->runCommand([
62
            'command' => 'info',
63
            'package' => $pluginName,
64
            'version' => $version,
65
            '--available' => true,
66
        ]);
67
68
        return OutputParser::parseInfo($output);
69
    }
70
71
    /**
72
     * Run execute command
73
     *
74
     * @param string $packageName format "foo/bar foo/bar:1.0.0"
75
     * @param null|OutputInterface $output
76
     *
77
     * @return string
78
     *
79
     * @throws PluginException
80
     */
81
    public function execRequire($packageName, $output = null)
82
    {
83
        $packageName = explode(' ', trim($packageName));
84
85
        return $this->runCommand([
86
            'command' => 'require',
87
            'packages' => $packageName,
88
            '--no-interaction' => true,
89
            '--profile' => true,
90
            '--prefer-dist' => true,
91
            '--ignore-platform-reqs' => true,
92
            '--update-with-dependencies' => true,
93
            '--no-scripts' => true,
94
        ], $output);
95
    }
96
97
    /**
98
     * Run remove command
99
     *
100
     * @param string $packageName format "foo/bar foo/bar:1.0.0"
101
     * @param null|OutputInterface $output
102
     *
103
     * @return string
104
     *
105
     * @throws PluginException
106
     */
107
    public function execRemove($packageName, $output = null)
108
    {
109
        $packageName = explode(' ', trim($packageName));
110
111
        return $this->runCommand([
112
            'command' => 'remove',
113
            'packages' => $packageName,
114
            '--ignore-platform-reqs' => true,
115
            '--no-interaction' => true,
116
            '--profile' => true,
117
            '--no-scripts' => true,
118
        ], $output);
119
    }
120
121
    /**
122
     * Run update command
123
     *
124
     * @param boolean $dryRun
125
     * @param null|OutputInterface $output
126
     *
127
     * @throws PluginException
128
     */
129 View Code Duplication
    public function execUpdate($dryRun, $output = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
130
    {
131
        $this->runCommand([
132
            'command' => 'update',
133
            '--no-interaction' => true,
134
            '--profile' => true,
135
            '--no-scripts' => true,
136
            '--dry-run' => (bool) $dryRun,
137
        ], $output);
138
    }
139
140
    /**
141
     * Run install command
142
     *
143
     * @param boolean $dryRun
144
     * @param null|OutputInterface $output
145
     *
146
     * @throws PluginException
147
     */
148 View Code Duplication
    public function execInstall($dryRun, $output = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
149
    {
150
        $this->runCommand([
151
            'command' => 'install',
152
            '--no-interaction' => true,
153
            '--profile' => true,
154
            '--no-scripts' => true,
155
            '--dry-run' => (bool) $dryRun,
156
        ], $output);
157
    }
158
159
    /**
160
     * Get require
161
     *
162
     * @param string $packageName
163
     * @param string|null $version
164
     * @param string $callback
165
     * @param null $typeFilter
166
     * @param int $level
167
     *
168
     * @throws PluginException
169
     */
170
    public function foreachRequires($packageName, $version, $callback, $typeFilter = null, $level = 0)
171
    {
172
        if (strpos($packageName, '/') === false) {
173
            return;
174
        }
175
        $info = $this->execInfo($packageName, $version);
176
        if (isset($info['requires'])) {
177
            foreach ($info['requires'] as $name => $version) {
0 ignored issues
show
Bug introduced by
The expression $info['requires'] of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
178
                if (isset($info['type']) && $info['type'] === $typeFilter) {
179
                    $this->foreachRequires($name, $version, $callback, $typeFilter, $level + 1);
180
                    if (isset($info['descrip.'])) {
181
                        $info['description'] = $info['descrip.'];
182
                    }
183
                    if ($level) {
184
                        $callback($info);
185
                    }
186
                }
187
            }
188
        }
189
    }
190
191
    /**
192
     * Run get config information
193
     *
194
     * @param string $key
195
     * @param null $value
196
     *
197
     * @return array|mixed
198
     *
199
     * @throws PluginException
200
     */
201
    public function execConfig($key, $value = null)
202
    {
203
        $commands = [
204
            'command' => 'config',
205
            'setting-key' => $key,
206
            'setting-value' => $value,
207
        ];
208
        if ($value) {
209
            $commands['setting-value'] = $value;
210
        }
211
        $output = $this->runCommand($commands, null, false);
212
213
        return OutputParser::parseConfig($output);
214
    }
215
216
    /**
217
     * Get config list
218
     *
219
     * @return array
220
     *
221
     * @throws PluginException
222
     */
223
    public function getConfig()
224
    {
225
        $output = $this->runCommand([
226
            'command' => 'config',
227
            '--list' => true,
228
        ], null, false);
229
230
        return OutputParser::parseList($output);
231
    }
232
233
    /**
234
     * Set work dir
235
     *
236
     * @param string $workingDir
237
     */
238
    public function setWorkingDir($workingDir)
239
    {
240
        $this->workingDir = $workingDir;
241
    }
242
243
    /**
244
     * Run composer command
245
     *
246
     * @param array $commands
247
     * @param null|OutputInterface $output
248
     *
249
     * @return string
250
     *
251
     * @throws PluginException
252
     */
253
    public function runCommand($commands, $output = null, $init = true)
254
    {
255
        if ($init) {
256
            $this->init();
257
        }
258
        $commands['--working-dir'] = $this->workingDir;
259
        $commands['--no-ansi'] = true;
260
        $input = new ArrayInput($commands);
261
        $useBufferedOutput = $output === null;
262
263
        if ($useBufferedOutput) {
264
            $output = new BufferedOutput();
265
            ob_start(function ($buffer) use ($output) {
266
                $output->write($buffer);
267
268
                return null;
269
            });
270
        }
271
272
        $exitCode = $this->consoleApplication->run($input, $output);
273
274
        if ($useBufferedOutput) {
275
            ob_end_clean();
276
            $log = $output->fetch();
277
            if ($exitCode) {
278
                log_error($log);
279
                throw new PluginException($log);
280
            }
281
            log_info($log, $commands);
282
283
            return $log;
284
        } elseif ($exitCode) {
285
            throw new PluginException();
286
        }
287
    }
288
289
    /**
290
     * Init composer console application
291
     */
292
    private function init()
293
    {
294
        set_time_limit(0);
295
        ini_set('memory_limit', '-1');
296
        // Config for some environment
297
        putenv('COMPOSER_HOME='.$this->eccubeConfig['plugin_realdir'].'/.composer');
298
        $this->initConsole();
299
        $this->workingDir = $this->workingDir ? $this->workingDir : $this->eccubeConfig['kernel.project_dir'];
300
        $config = $this->getConfig();
301
        if (!isset($config['repositories']['eccube'])) {
302
            $url = $this->eccubeConfig['eccube_package_repo_url'];
303
            $json = json_encode([
304
                'type' => 'composer',
305
                'url' => $url,
306
                'options' => [
307
                    'http' => [
308
                        'header' => ['X-ECCUBE-KEY: '.$this->baseInfo->getAuthenticationKey()]
309
                    ]
310
                ]
311
            ]);
312
            $this->execConfig('repositories.eccube', [$json]);
313
            if (strpos($url, 'http://') == 0) {
314
                $this->execConfig('secure-http', ['false']);
315
            }
316
            $this->initConsole();
317
        }
318
    }
319
320
    private function initConsole()
321
    {
322
        $consoleApplication = new Application();
323
        $consoleApplication->resetComposer();
324
        $consoleApplication->setAutoExit(false);
325
        $this->consoleApplication = $consoleApplication;
326
    }
327
328
    /**
329
     * Get version of composer
330
     *
331
     * @return null|string
332
     *
333
     * @throws PluginException
334
     */
335
    public function composerVersion()
336
    {
337
        $this->init();
338
        $output = $this->runCommand([
339
            '--version' => true,
340
        ]);
341
342
        return OutputParser::parseComposerVersion($output);
343
    }
344
345
    /**
346
     * Get mode
347
     *
348
     * @return string
349
     */
350
    public function getMode()
351
    {
352
        return 'API';
353
    }
354
}
355