Completed
Push — remove-extra-tables ( 85f800 )
by Kiyotaka
05:51
created

ComposerApiService::dropTableToExtra()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
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
    public function execRequire($packageName, $output = null)
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
            '--ignore-platform-reqs' => true,
115
            '--update-with-dependencies' => true,
116
            '--no-scripts' => true,
117
        ], $output);
118
    }
119
120
    /**
121
     * Run remove command
122
     *
123
     * @param string $packageName format "foo/bar foo/bar:1.0.0"
124
     * @param OutputInterface|null $output
125
     *
126
     * @return string
127
     *
128
     * @throws PluginException
129
     * @throws \Doctrine\ORM\NoResultException
130
     * @throws \Doctrine\ORM\NonUniqueResultException
131
     */
132
    public function execRemove($packageName, $output = null)
133
    {
134
        $this->dropTableToExtra($packageName);
135
136
        $packageName = explode(' ', trim($packageName));
137
138
        return $this->runCommand([
139
            'command' => 'remove',
140
            'packages' => $packageName,
141
            '--ignore-platform-reqs' => true,
142
            '--no-interaction' => true,
143
            '--profile' => true,
144
            '--no-scripts' => true,
145
        ], $output);
146
    }
147
148
    /**
149
     * Run update command
150
     *
151
     * @param boolean $dryRun
152
     * @param OutputInterface|null $output
153
     *
154
     * @throws PluginException
155
     * @throws \Doctrine\ORM\NoResultException
156
     * @throws \Doctrine\ORM\NonUniqueResultException
157
     */
158 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...
159
    {
160
        $this->runCommand([
161
            'command' => 'update',
162
            '--no-interaction' => true,
163
            '--profile' => true,
164
            '--no-scripts' => true,
165
            '--dry-run' => (bool) $dryRun,
166
        ], $output);
167
    }
168
169
    /**
170
     * Run install command
171
     *
172
     * @param boolean $dryRun
173
     * @param OutputInterface|null $output
174
     *
175
     * @throws PluginException
176
     * @throws \Doctrine\ORM\NoResultException
177
     * @throws \Doctrine\ORM\NonUniqueResultException
178
     */
179 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...
180
    {
181
        $this->runCommand([
182
            'command' => 'install',
183
            '--no-interaction' => true,
184
            '--profile' => true,
185
            '--no-scripts' => true,
186
            '--dry-run' => (bool) $dryRun,
187
        ], $output);
188
    }
189
190
    /**
191
     * Get require
192
     *
193
     * @param string $packageName
194
     * @param string|null $version
195
     * @param string $callback
196
     * @param null $typeFilter
197
     * @param int $level
198
     *
199
     * @throws PluginException
200
     * @throws \Doctrine\ORM\NoResultException
201
     * @throws \Doctrine\ORM\NonUniqueResultException
202
     */
203
    public function foreachRequires($packageName, $version, $callback, $typeFilter = null, $level = 0)
204
    {
205
        if (strpos($packageName, '/') === false) {
206
            return;
207
        }
208
        $info = $this->execInfo($packageName, $version);
209
        if (isset($info['requires'])) {
210
            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...
211
                if (isset($info['type']) && $info['type'] === $typeFilter) {
212
                    $this->foreachRequires($name, $version, $callback, $typeFilter, $level + 1);
213
                    if (isset($info['descrip.'])) {
214
                        $info['description'] = $info['descrip.'];
215
                    }
216
                    if ($level) {
217
                        $callback($info);
218
                    }
219
                }
220
            }
221
        }
222
    }
223
224
    /**
225
     * Run get config information
226
     *
227
     * @param string $key
228
     * @param null $value
229
     *
230
     * @return array|mixed
231
     *
232
     * @throws PluginException
233
     * @throws \Doctrine\ORM\NoResultException
234
     * @throws \Doctrine\ORM\NonUniqueResultException
235
     */
236
    public function execConfig($key, $value = null)
237
    {
238
        $commands = [
239
            'command' => 'config',
240
            'setting-key' => $key,
241
            'setting-value' => $value,
242
        ];
243
        if ($value) {
244
            $commands['setting-value'] = $value;
245
        }
246
        $output = $this->runCommand($commands, null, false);
247
248
        return OutputParser::parseConfig($output);
249
    }
250
251
    /**
252
     * Get config list
253
     *
254
     * @return array
255
     *
256
     * @throws PluginException
257
     * @throws \Doctrine\ORM\NoResultException
258
     * @throws \Doctrine\ORM\NonUniqueResultException
259
     */
260
    public function getConfig()
261
    {
262
        $output = $this->runCommand([
263
            'command' => 'config',
264
            '--list' => true,
265
        ], null, false);
266
267
        return OutputParser::parseList($output);
268
    }
269
270
    /**
271
     * Set work dir
272
     *
273
     * @param string $workingDir
274
     */
275
    public function setWorkingDir($workingDir)
276
    {
277
        $this->workingDir = $workingDir;
278
    }
279
280
    /**
281
     * Run composer command
282
     *
283
     * @param array $commands
284
     * @param OutputInterface|null $output
285
     * @param bool $init
286
     *
287
     * @return string
288
     *
289
     * @throws PluginException
290
     * @throws \Doctrine\ORM\NoResultException
291
     * @throws \Doctrine\ORM\NonUniqueResultException
292
     * @throws \Exception
293
     */
294
    public function runCommand($commands, $output = null, $init = true)
295
    {
296
        if ($init) {
297
            $this->init();
298
        }
299
        $commands['--working-dir'] = $this->workingDir;
300
        $commands['--no-ansi'] = true;
301
        $input = new ArrayInput($commands);
302
        $useBufferedOutput = $output === null;
303
304
        if ($useBufferedOutput) {
305
            $output = new BufferedOutput();
306
            ob_start(function ($buffer) use ($output) {
307
                $output->write($buffer);
308
309
                return null;
310
            });
311
        }
312
313
        $exitCode = $this->consoleApplication->run($input, $output);
314
315
        if ($useBufferedOutput) {
316
            ob_end_clean();
317
            $log = $output->fetch();
318
            if ($exitCode) {
319
                log_error($log);
320
                throw new PluginException($log);
321
            }
322
            log_info($log, $commands);
323
324
            return $log;
325
        } elseif ($exitCode) {
326
            throw new PluginException();
327
        }
328
329
        return null;
330
    }
331
332
    /**
333
     * Init composer console application
334
     *
335
     * @param BaseInfo|null $BaseInfo
336
     *
337
     * @throws PluginException
338
     * @throws \Doctrine\ORM\NoResultException
339
     * @throws \Doctrine\ORM\NonUniqueResultException
340
     */
341
    private function init($BaseInfo = null)
342
    {
343
        $BaseInfo = $BaseInfo ?: $this->baseInfoRepository->get();
344
345
        set_time_limit(0);
346
347
        $composerMemory = $this->eccubeConfig['eccube_composer_memory_limit'];
348
        ini_set('memory_limit', $composerMemory);
349
350
        // Config for some environment
351
        putenv('COMPOSER_HOME='.$this->eccubeConfig['plugin_realdir'].'/.composer');
352
        $this->initConsole();
353
        $this->workingDir = $this->workingDir ? $this->workingDir : $this->eccubeConfig['kernel.project_dir'];
354
        $url = $this->eccubeConfig['eccube_package_api_url'];
355
        $json = json_encode([
356
            'type' => 'composer',
357
            'url' => $url,
358
            'options' => [
359
                'http' => [
360
                    'header' => ['X-ECCUBE-KEY: '.$BaseInfo->getAuthenticationKey()],
361
                ],
362
            ],
363
        ]);
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