Completed
Pull Request — 4.0 (#4664)
by Hideki
04:42
created

ComposerApiService::dropTableToExtra()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 12
1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.ec-cube.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\Entity\BaseInfo;
19
use Eccube\Exception\PluginException;
20
use Eccube\Repository\BaseInfoRepository;
21
use Eccube\Service\PluginContext;
22
use Eccube\Service\SchemaService;
23
use Symfony\Component\Console\Input\ArrayInput;
24
use Symfony\Component\Console\Output\BufferedOutput;
25
use Symfony\Component\Console\Output\OutputInterface;
26
27
/**
28
 * Class ComposerApiService
29
 */
30
class ComposerApiService implements ComposerServiceInterface
31
{
32
    /**
33
     * @var EccubeConfig
34
     */
35
    protected $eccubeConfig;
36
37
    /**
38
     * @var Application
39
     */
40
    private $consoleApplication;
41
42
    private $workingDir;
43
    /**
44
     * @var BaseInfoRepository
45
     */
46
    private $baseInfoRepository;
47
48
    /** @var SchemaService */
49
    private $schemaService;
50
51
    /**
52
     * @var PluginContext
53
     */
54
    private $pluginContext;
55
56
    public function __construct(
57
        EccubeConfig $eccubeConfig,
58
        BaseInfoRepository $baseInfoRepository,
59
        SchemaService $schemaService,
60
        PluginContext $pluginContext
61
    ) {
62
        $this->eccubeConfig = $eccubeConfig;
63
        $this->schemaService = $schemaService;
64
        $this->baseInfoRepository = $baseInfoRepository;
65
        $this->pluginContext = $pluginContext;
66
    }
67
68
    /**
69
     * Run get info command
70
     *
71
     * @param string $pluginName format foo/bar or foo/bar:1.0.0 or "foo/bar 1.0.0"
72
     * @param string|null $version
73
     *
74
     * @return array
75
     *
76
     * @throws PluginException
77
     * @throws \Doctrine\ORM\NoResultException
78
     * @throws \Doctrine\ORM\NonUniqueResultException
79
     */
80
    public function execInfo($pluginName, $version)
81
    {
82
        $output = $this->runCommand([
83
            'command' => 'info',
84
            'package' => $pluginName,
85
            'version' => $version,
86
            '--available' => true,
87
        ]);
88
89
        return OutputParser::parseInfo($output);
90
    }
91
92
    /**
93
     * Run execute command
94
     *
95
     * @param string $packageName format "foo/bar foo/bar:1.0.0"
96
     * @param OutputInterface|null $output
97
     *
98
     * @return string
99
     *
100
     * @throws PluginException
101
     * @throws \Doctrine\ORM\NoResultException
102
     * @throws \Doctrine\ORM\NonUniqueResultException
103
     */
104 View Code Duplication
    public function execRequire($packageName, $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...
105
    {
106
        $packageName = explode(' ', trim($packageName));
107
108
        return $this->runCommand([
109
            'command' => 'require',
110
            'packages' => $packageName,
111
            '--no-interaction' => true,
112
            '--profile' => true,
113
            '--prefer-dist' => true,
114
            '--update-with-dependencies' => true,
115
            '--no-scripts' => true,
116
        ], $output);
117
    }
118
119
    /**
120
     * Run remove command
121
     *
122
     * @param string $packageName format "foo/bar foo/bar:1.0.0"
123
     * @param OutputInterface|null $output
124
     *
125
     * @return string
126
     *
127
     * @throws PluginException
128
     * @throws \Doctrine\ORM\NoResultException
129
     * @throws \Doctrine\ORM\NonUniqueResultException
130
     */
131 View Code Duplication
    public function execRemove($packageName, $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...
132
    {
133
        $this->dropTableToExtra($packageName);
134
135
        $packageName = explode(' ', trim($packageName));
136
137
        return $this->runCommand([
138
            'command' => 'remove',
139
            'packages' => $packageName,
140
            '--ignore-platform-reqs' => true,
141
            '--no-interaction' => true,
142
            '--profile' => true,
143
            '--no-scripts' => true,
144
        ], $output);
145
    }
146
147
    /**
148
     * Run update command
149
     *
150
     * @param boolean $dryRun
151
     * @param OutputInterface|null $output
152
     *
153
     * @throws PluginException
154
     * @throws \Doctrine\ORM\NoResultException
155
     * @throws \Doctrine\ORM\NonUniqueResultException
156
     */
157 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...
158
    {
159
        $this->runCommand([
160
            'command' => 'update',
161
            '--no-interaction' => true,
162
            '--profile' => true,
163
            '--no-scripts' => true,
164
            '--dry-run' => (bool) $dryRun,
165
        ], $output);
166
    }
167
168
    /**
169
     * Run install command
170
     *
171
     * @param boolean $dryRun
172
     * @param OutputInterface|null $output
173
     *
174
     * @throws PluginException
175
     * @throws \Doctrine\ORM\NoResultException
176
     * @throws \Doctrine\ORM\NonUniqueResultException
177
     */
178 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...
179
    {
180
        $this->runCommand([
181
            'command' => 'install',
182
            '--no-interaction' => true,
183
            '--profile' => true,
184
            '--no-scripts' => true,
185
            '--dry-run' => (bool) $dryRun,
186
        ], $output);
187
    }
188
189
    /**
190
     * Get require
191
     *
192
     * @param string $packageName
193
     * @param string|null $version
194
     * @param string $callback
195
     * @param null $typeFilter
196
     * @param int $level
197
     *
198
     * @throws PluginException
199
     * @throws \Doctrine\ORM\NoResultException
200
     * @throws \Doctrine\ORM\NonUniqueResultException
201
     */
202
    public function foreachRequires($packageName, $version, $callback, $typeFilter = null, $level = 0)
203
    {
204
        if (strpos($packageName, '/') === false) {
205
            return;
206
        }
207
        $info = $this->execInfo($packageName, $version);
208
        if (isset($info['requires'])) {
209
            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...
210
                if (isset($info['type']) && $info['type'] === $typeFilter) {
211
                    $this->foreachRequires($name, $version, $callback, $typeFilter, $level + 1);
212
                    if (isset($info['descrip.'])) {
213
                        $info['description'] = $info['descrip.'];
214
                    }
215
                    if ($level) {
216
                        $callback($info);
217
                    }
218
                }
219
            }
220
        }
221
    }
222
223
    /**
224
     * Run get config information
225
     *
226
     * @param string $key
227
     * @param null $value
228
     *
229
     * @return array|mixed
230
     *
231
     * @throws PluginException
232
     * @throws \Doctrine\ORM\NoResultException
233
     * @throws \Doctrine\ORM\NonUniqueResultException
234
     */
235
    public function execConfig($key, $value = null)
236
    {
237
        $commands = [
238
            'command' => 'config',
239
            'setting-key' => $key,
240
            'setting-value' => $value,
241
        ];
242
        if ($value) {
243
            $commands['setting-value'] = $value;
244
        }
245
        $output = $this->runCommand($commands, null, false);
246
247
        return OutputParser::parseConfig($output);
248
    }
249
250
    /**
251
     * Get config list
252
     *
253
     * @return array
254
     *
255
     * @throws PluginException
256
     * @throws \Doctrine\ORM\NoResultException
257
     * @throws \Doctrine\ORM\NonUniqueResultException
258
     */
259
    public function getConfig()
260
    {
261
        $output = $this->runCommand([
262
            'command' => 'config',
263
            '--list' => true,
264
        ], null, false);
265
266
        return OutputParser::parseList($output);
267
    }
268
269
    /**
270
     * Set work dir
271
     *
272
     * @param string $workingDir
273
     */
274
    public function setWorkingDir($workingDir)
275
    {
276
        $this->workingDir = $workingDir;
277
    }
278
279
    /**
280
     * Run composer command
281
     *
282
     * @param array $commands
283
     * @param OutputInterface|null $output
284
     * @param bool $init
285
     *
286
     * @return string
287
     *
288
     * @throws PluginException
289
     * @throws \Doctrine\ORM\NoResultException
290
     * @throws \Doctrine\ORM\NonUniqueResultException
291
     * @throws \Exception
292
     */
293
    public function runCommand($commands, $output = null, $init = true)
294
    {
295
        if ($init) {
296
            $this->init();
297
        }
298
        $commands['--working-dir'] = $this->workingDir;
299
        $commands['--no-ansi'] = true;
300
        $input = new ArrayInput($commands);
301
        $useBufferedOutput = $output === null;
302
303
        if ($useBufferedOutput) {
304
            $output = new BufferedOutput();
305
            ob_start(function ($buffer) use ($output) {
306
                $output->write($buffer);
307
308
                return null;
309
            });
310
        }
311
312
        $exitCode = $this->consoleApplication->run($input, $output);
313
314
        if ($useBufferedOutput) {
315
            ob_end_clean();
316
            $log = $output->fetch();
317
            if ($exitCode) {
318
                log_error($log);
319
                throw new PluginException($log);
320
            }
321
            log_info($log, $commands);
322
323
            return $log;
324
        } elseif ($exitCode) {
325
            throw new PluginException();
326
        }
327
328
        return null;
329
    }
330
331
    /**
332
     * Init composer console application
333
     *
334
     * @param BaseInfo|null $BaseInfo
335
     *
336
     * @throws PluginException
337
     * @throws \Doctrine\ORM\NoResultException
338
     * @throws \Doctrine\ORM\NonUniqueResultException
339
     */
340
    private function init($BaseInfo = null)
341
    {
342
        $BaseInfo = $BaseInfo ?: $this->baseInfoRepository->get();
343
344
        set_time_limit(0);
345
346
        $composerMemory = $this->eccubeConfig['eccube_composer_memory_limit'];
347
        ini_set('memory_limit', $composerMemory);
348
349
        // Config for some environment
350
        putenv('COMPOSER_HOME='.$this->eccubeConfig['plugin_realdir'].'/.composer');
351
        $this->initConsole();
352
        $this->workingDir = $this->workingDir ? $this->workingDir : $this->eccubeConfig['kernel.project_dir'];
353
        $url = $this->eccubeConfig['eccube_package_api_url'];
354
        $json = json_encode([
355
            'type' => 'composer',
356
            'url' => $url,
357
            'options' => [
358
                'http' => [
359
                    'header' => ['X-ECCUBE-KEY: '.$BaseInfo->getAuthenticationKey()],
360
                ],
361
            ],
362
        ]);
363
        $this->execConfig('platform.php', [PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION.'.'.PHP_RELEASE_VERSION]);
364
        $this->execConfig('repositories.eccube', [$json]);
365
        if (strpos($url, 'http://') === 0) {
366
            $this->execConfig('secure-http', ['false']);
367
        }
368
        $this->initConsole();
369
    }
370
371
    private function initConsole()
372
    {
373
        $consoleApplication = new Application();
374
        $consoleApplication->resetComposer();
375
        $consoleApplication->setAutoExit(false);
376
        $this->consoleApplication = $consoleApplication;
377
    }
378
379
    /**
380
     * @throws PluginException
381
     * @throws \Doctrine\ORM\NoResultException
382
     * @throws \Doctrine\ORM\NonUniqueResultException
383
     */
384
    public function configureRepository(BaseInfo $BaseInfo)
385
    {
386
        $this->init($BaseInfo);
387
    }
388
389
    private function dropTableToExtra($packageNames)
390
    {
391
        foreach (explode(' ', trim($packageNames)) as $packageName) {
392
            $pluginCode = basename($packageName);
393
            $this->pluginContext->setCode($pluginCode);
394
            $this->pluginContext->setUninstall();
395
396
            foreach ($this->pluginContext->getExtraEntityNamespaces() as $namespace) {
397
                $this->schemaService->dropTable($namespace);
398
            }
399
        }
400
    }
401
}
402