Failed Conditions
Pull Request — experimental/3.1 (#2525)
by Kiyotaka
54:52 queued 29:13
created

PluginService   C

Complexity

Total Complexity 73

Size/Duplication

Total Lines 427
Duplicated Lines 1.41 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 84.26%

Importance

Changes 0
Metric Value
dl 6
loc 427
ccs 198
cts 235
cp 0.8426
rs 5.5447
c 0
b 0
f 0
wmc 73
lcom 1
cbo 10

19 Methods

Rating   Name   Duplication   Size   Complexity  
B install() 0 37 3
A createTempDir() 0 11 2
A deleteDirs() 0 9 3
A unpackPluginArchive() 0 17 3
A readYml() 0 8 2
A checkSymbolName() 0 7 2
A deleteFile() 0 5 1
A checkSamePlugin() 0 7 2
A calcPluginDir() 0 4 1
A createPluginDir() 0 7 2
C registerPlugin() 0 48 7
A callPluginManagerMethod() 0 10 3
A uninstall() 0 12 1
A unregisterPlugin() 0 19 3
A disable() 0 4 1
A enable() 0 21 4
B update() 0 38 5
C updatePlugin() 0 69 14
C checkPluginArchiveContent() 6 40 14

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like PluginService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PluginService, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) 2000-2015 LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.co.jp/
9
 *
10
 * This program is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU General Public License
12
 * as published by the Free Software Foundation; either version 2
13
 * of the License, or (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program; if not, write to the Free Software
22
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23
 */
24
25
namespace Eccube\Service;
26
27
use Doctrine\ORM\EntityManager;
28
use Eccube\Annotation\Inject;
29
use Eccube\Annotation\Service;
30
use Eccube\Application;
31
use Eccube\Common\Constant;
32
use Eccube\Exception\PluginException;
33
use Eccube\Plugin\ConfigManager;
34
use Eccube\Plugin\ConfigManager as PluginConfigManager;
35
use Eccube\Repository\PluginEventHandlerRepository;
36
use Eccube\Repository\PluginRepository;
37
use Eccube\Util\Cache;
38
use Eccube\Util\Str;
39
use Symfony\Component\Filesystem\Filesystem;
40
use Symfony\Component\Yaml\Yaml;
41
42
/**
43
 * @Service
44
 */
45
class PluginService
46
{
47
    /**
48
     * @Inject(PluginEventHandlerRepository::class)
49
     * @var PluginEventHandlerRepository
50
     */
51
    protected $pluginEventHandlerRepository;
52
53
    /**
54
     * @Inject("orm.em")
55
     * @var EntityManager
56
     */
57
    protected $entityManager;
58
59
    /**
60
     * @Inject(PluginRepository::class)
61
     * @var PluginRepository
62
     */
63
    protected $pluginRepository;
64
65
    /**
66
     * @Inject("config")
67
     * @var array
68
     */
69
    protected $appConfig;
70
71
    /**
72
     * @Inject(Application::class)
73
     * @var Application
74
     */
75
    protected $app;
76
77
    const CONFIG_YML = 'config.yml';
78
    const EVENT_YML = 'event.yml';
79
80 9
    public function install($path, $source = 0)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
81
    {
82 9
        $pluginBaseDir = null;
83 9
        $tmp = null;
84
85
        try {
86 9
            PluginConfigManager::removePluginConfigCache();
87 9
            Cache::clear($this->app, false);
88 9
            $tmp = $this->createTempDir();
89
90 9
            $this->unpackPluginArchive($path, $tmp); //一旦テンポラリに展開
91 9
            $this->checkPluginArchiveContent($tmp);
92
93 7
            $config = $this->readYml($tmp.'/'.self::CONFIG_YML);
94 7
            $event = $this->readYml($tmp.'/'.self::EVENT_YML);
95 7
            $this->deleteFile($tmp); // テンポラリのファイルを削除
96
97 7
            $this->checkSamePlugin($config['code']); // 重複していないかチェック
98
99 7
            $pluginBaseDir = $this->calcPluginDir($config['code']);
100 7
            $this->createPluginDir($pluginBaseDir); // 本来の置き場所を作成
101
102 7
            $this->unpackPluginArchive($path, $pluginBaseDir); // 問題なければ本当のplugindirへ
103
104 7
            $this->registerPlugin($config, $event, $source); // dbにプラグイン登録
105 6
            ConfigManager::writePluginConfigCache();
106 4
        } catch (PluginException $e) {
107 4
            $this->deleteDirs(array($tmp, $pluginBaseDir));
108 4
            throw $e;
109
        } catch (\Exception $e) { // インストーラがどんなExceptionを上げるかわからないので
110
111
            $this->deleteDirs(array($tmp, $pluginBaseDir));
112
            throw $e;
113
        }
114
115 6
        return true;
116
    }
117
118 9
    public function createTempDir()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
119
    {
120 9
        @mkdir($this->appConfig['plugin_temp_realdir']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
121 9
        $d = ($this->appConfig['plugin_temp_realdir'].'/'.sha1(Str::random(16)));
122
123 9
        if (!mkdir($d, 0777)) {
124
            throw new PluginException($php_errormsg.$d);
125
        }
126
127 9
        return $d;
128
    }
129
130 4
    public function deleteDirs($arr)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
131
    {
132 4
        foreach ($arr as $dir) {
133 4
            if (file_exists($dir)) {
134 3
                $fs = new Filesystem();
135 4
                $fs->remove($dir);
136
            }
137
        }
138
    }
139
140 9
    public function unpackPluginArchive($archive, $dir)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
141
    {
142 9
        $extension = pathinfo($archive, PATHINFO_EXTENSION);
143
        try {
144 9
            if ($extension == 'zip') {
145
                $zip = new \ZipArchive();
146
                $zip->open($archive);
147
                $zip->extractTo($dir);
148
                $zip->close();
149
            } else {
150 9
                $phar = new \PharData($archive);
151 9
                $phar->extractTo($dir, null, true);
152
            }
153
        } catch (\Exception $e) {
154
            throw new PluginException('アップロードに失敗しました。圧縮ファイルを確認してください。');
155
        }
156
    }
157
158 1069
    public function checkPluginArchiveContent($dir, array $config_cache = array())
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
159
    {
160
        try {
161 1069
            if (!empty($config_cache)) {
162 1069
                $meta = $config_cache;
163
            } else {
164 1069
                $meta = $this->readYml($dir . '/config.yml');
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
165
            }
166
        } catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
167
            throw new PluginException($e->getMessage(), $e->getCode(), $e);
168
        }
169
170 1069
        if (!is_array($meta)) {
171 2
            throw new PluginException('config.yml not found or syntax error');
172
        }
173 1069 View Code Duplication
        if (!isset($meta['code']) || !$this->checkSymbolName($meta['code'])) {
174
            throw new PluginException('config.yml code empty or invalid_character(\W)');
175
        }
176 1069
        if (!isset($meta['name'])) {
177
            // nameは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
178 1
            throw new PluginException('config.yml name empty');
179
        }
180 1069 View Code Duplication
        if (isset($meta['event']) && !$this->checkSymbolName($meta['event'])) { // eventだけは必須ではない
181
            throw new PluginException('config.yml event empty or invalid_character(\W) ');
182
        }
183 1069
        if (!isset($meta['version'])) {
184
            // versionは直接クラス名やPATHに使われるわけではないため文字のチェックはなしし
185
            throw new PluginException('config.yml version invalid_character(\W) ');
186
        }
187 1069
        if (isset($meta['orm.path'])) {
188
            if (!is_array($meta['orm.path'])) {
189
                throw new PluginException('config.yml orm.path invalid_character(\W) ');
190
            }
191
        }
192 1069
        if (isset($meta['service'])) {
193
            if (!is_array($meta['service'])) {
194
                throw new PluginException('config.yml service invalid_character(\W) ');
195
            }
196
        }
197
    }
198
199 10
    public function readYml($yml)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
200
    {
201 10
        if (file_exists($yml)) {
202 8
            return Yaml::parse(file_get_contents($yml));
203
        }
204
205 7
        return false;
206
    }
207
208 1069
    public function checkSymbolName($string)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
209
    {
210 1069
        return strlen($string) < 256 && preg_match('/^\w+$/', $string);
211
        // plugin_nameやplugin_codeに使える文字のチェック
212
        // a-z A-Z 0-9 _
213
        // ディレクトリ名などに使われれるので厳しめ
214
    }
215
216 7
    public function deleteFile($path)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
217
    {
218 7
        $f = new Filesystem();
219 7
        $f->remove($path);
220
    }
221
222 7
    public function checkSamePlugin($code)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
223
    {
224 7
        $repo = $this->pluginRepository->findOneBy(array('code' => $code));
225 7
        if ($repo) {
226 1
            throw new PluginException('plugin already installed.');
227
        }
228
    }
229
230 7
    public function calcPluginDir($name)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
231
    {
232 7
        return $this->appConfig['plugin_realdir'].'/'.$name;
233
    }
234
235 7
    public function createPluginDir($d)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
236
    {
237 7
        $b = @mkdir($d);
238 7
        if (!$b) {
239
            throw new PluginException($php_errormsg);
240
        }
241
    }
242
243 7
    public function registerPlugin($meta, $event_yml, $source = 0)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
244
    {
245 7
        $em = $this->entityManager;
246 7
        $em->getConnection()->beginTransaction();
247
        try {
248 7
            $p = new \Eccube\Entity\Plugin();
249
            // インストール直後はプラグインは有効にしない
250 7
            $p->setName($meta['name'])
251 7
                ->setEnable(Constant::DISABLED)
252 7
                ->setClassName(isset($meta['event']) ? $meta['event'] : '')
253 7
                ->setVersion($meta['version'])
254 7
                ->setSource($source)
255 7
                ->setCode($meta['code']);
256
257 7
            $em->persist($p);
258 7
            $em->flush();
259
260 7
            if (is_array($event_yml)) {
261 2
                foreach ($event_yml as $event => $handlers) {
262 2
                    foreach ($handlers as $handler) {
263 2
                        if (!$this->checkSymbolName($handler[0])) {
264
                            throw new PluginException('Handler name format error');
265
                        }
266 2
                        $peh = new \Eccube\Entity\PluginEventHandler();
267 2
                        $peh->setPlugin($p)
268 2
                            ->setEvent($event)
269 2
                            ->setHandler($handler[0])
270 2
                            ->setHandlerType($handler[1])
271 2
                            ->setPriority($this->pluginEventHandlerRepository->calcNewPriority($event, $handler[1]));
272 2
                        $em->persist($peh);
273 2
                        $em->flush();
274
                    }
275
                }
276
            }
277
278 7
            $em->persist($p);
279
280 7
            $this->callPluginManagerMethod($meta, 'install');
281
282 6
            $em->flush();
283 6
            $em->getConnection()->commit();
284 1
        } catch (\Exception $e) {
285 1
            $em->getConnection()->rollback();
286 1
            throw new PluginException($e->getMessage());
287
        }
288
289 6
        return $p;
290
    }
291
292 7
    public function callPluginManagerMethod($meta, $method)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
293
    {
294 7
        $class = '\\Plugin'.'\\'.$meta['code'].'\\'.'PluginManager';
295 7
        if (class_exists($class)) {
296 3
            $installer = new $class(); // マネージャクラスに所定のメソッドがある場合だけ実行する
297 3
            if (method_exists($installer, $method)) {
298 3
                $installer->$method($meta, $this->app);
299
            }
300
        }
301
    }
302
303 5
    public function uninstall(\Eccube\Entity\Plugin $plugin)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
304
    {
305 5
        $pluginDir = $this->calcPluginDir($plugin->getCode());
306 5
        ConfigManager::removePluginConfigCache();
307 5
        Cache::clear($this->app, false);
308 5
        $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), 'disable');
309 5
        $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), 'uninstall');
310 5
        $this->unregisterPlugin($plugin);
311 5
        $this->deleteFile($pluginDir);
312 5
        ConfigManager::writePluginConfigCache();
313 5
        return true;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
314
    }
315
316 5
    public function unregisterPlugin(\Eccube\Entity\Plugin $p)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
317
    {
318
        try {
319 5
            $em = $this->entityManager;
320 5
            $em->getConnection()->beginTransaction();
321
322 5
            foreach ($p->getPluginEventHandlers()->toArray() as $peh) {
323 1
                $em->remove($peh);
324
            }
325 5
            $em->remove($p);
326
327 5
            $em->persist($p);
328 5
            $em->flush();
329 5
            $em->getConnection()->commit();
330
        } catch (\Exception $e) {
331
            $em->getConnection()->rollback();
332
            throw $e;
333
        }
334
    }
335
336 2
    public function disable(\Eccube\Entity\Plugin $plugin)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
337
    {
338 2
        return $this->enable($plugin, false);
339
    }
340
341 3
    public function enable(\Eccube\Entity\Plugin $plugin, $enable = true)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
342
    {
343 3
        $em = $this->entityManager;
344
        try {
345 3
            PluginConfigManager::removePluginConfigCache();
346 3
            Cache::clear($this->app, false);
347 3
            $pluginDir = $this->calcPluginDir($plugin->getCode());
348 3
            $em->getConnection()->beginTransaction();
349 3
            $plugin->setEnable($enable ? Constant::ENABLED : Constant::DISABLED);
350 3
            $em->persist($plugin);
351 3
            $this->callPluginManagerMethod(Yaml::parse(file_get_contents($pluginDir.'/'.self::CONFIG_YML)), $enable ? 'enable' : 'disable');
352 2
            $em->flush();
353 2
            $em->getConnection()->commit();
354 2
            PluginConfigManager::writePluginConfigCache();
355 1
        } catch (\Exception $e) {
356 1
            $em->getConnection()->rollback();
357 1
            throw $e;
358
        }
359
360 2
        return true;
361
    }
362
363 1
    public function update(\Eccube\Entity\Plugin $plugin, $path)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
364
    {
365 1
        $pluginBaseDir = null;
366 1
        $tmp = null;
367
        try {
368 1
            PluginConfigManager::removePluginConfigCache();
369 1
            Cache::clear($this->app, false);
370 1
            $tmp = $this->createTempDir();
371
372 1
            $this->unpackPluginArchive($path, $tmp); //一旦テンポラリに展開
373 1
            $this->checkPluginArchiveContent($tmp);
374
375 1
            $config = $this->readYml($tmp.'/'.self::CONFIG_YML);
376 1
            $event = $this->readYml($tmp.'/event.yml');
377
378 1
            if ($plugin->getCode() != $config['code']) {
379
                throw new PluginException('new/old plugin code is different.');
380
            }
381
382 1
            $pluginBaseDir = $this->calcPluginDir($config['code']);
383 1
            $this->deleteFile($tmp); // テンポラリのファイルを削除
384
385 1
            $this->unpackPluginArchive($path, $pluginBaseDir); // 問題なければ本当のplugindirへ
386
387 1
            $this->updatePlugin($plugin, $config, $event); // dbにプラグイン登録
388 1
            PluginConfigManager::writePluginConfigCache();
389
        } catch (PluginException $e) {
390
            foreach (array($tmp) as $dir) {
391
                if (file_exists($dir)) {
392
                    $fs = new Filesystem();
393
                    $fs->remove($dir);
394
                }
395
            }
396
            throw $e;
397
        }
398
399 1
        return true;
400
    }
401
402 1
    public function updatePlugin(\Eccube\Entity\Plugin $plugin, $meta, $event_yml)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
403
    {
404
        try {
405 1
            $em = $this->entityManager;
406 1
            $em->getConnection()->beginTransaction();
407 1
            $plugin->setVersion($meta['version'])
408 1
                ->setName($meta['name']);
409
410 1
            if (isset($meta['event'])) {
411 1
                $plugin->setClassName($meta['event']);
412
            }
413
414 1
            $rep = $this->pluginEventHandlerRepository;
415
416 1
            if (is_array($event_yml)) {
417 1
                foreach ($event_yml as $event => $handlers) {
418 1
                    foreach ($handlers as $handler) {
419 1
                        if (!$this->checkSymbolName($handler[0])) {
420
                            throw new PluginException('Handler name format error');
421
                        }
422
                        // updateで追加されたハンドラかどうか調べる
423 1
                        $peh = $rep->findBy(array(
424 1
                            'plugin_id' => $plugin->getId(),
425 1
                            'event' => $event,
426 1
                            'handler' => $handler[0],
427 1
                            'handler_type' => $handler[1],));
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 24 spaces, but found 28.
Loading history...
introduced by
Add a single space after each comma delimiter
Loading history...
428
429 1
                        if (!$peh) { // 新規にevent.ymlに定義されたハンドラなのでinsertする
0 ignored issues
show
Bug Best Practice introduced by
The expression $peh of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
430 1
                            $peh = new \Eccube\Entity\PluginEventHandler();
431 1
                            $peh->setPlugin($plugin)
432 1
                                ->setEvent($event)
433 1
                                ->setHandler($handler[0])
434 1
                                ->setHandlerType($handler[1])
435 1
                                ->setPriority($rep->calcNewPriority($event, $handler[1]));
436 1
                            $em->persist($peh);
437 1
                            $em->flush();
438
                        }
439
                    }
440
                }
441
442
                # アップデート後のevent.ymlで削除されたハンドラをdtb_plugin_event_handlerから探して削除
0 ignored issues
show
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
443 1
                foreach ($rep->findBy(array('plugin_id' => $plugin->getId())) as $peh) {
444 1
                    if (!isset($event_yml[$peh->getEvent()])) {
445
                        $em->remove($peh);
446
                        $em->flush();
447
                    } else {
448 1
                        $match = false;
449 1
                        foreach ($event_yml[$peh->getEvent()] as $handler) {
450 1
                            if ($peh->getHandler() == $handler[0] && $peh->getHandlerType() == $handler[1]) {
451 1
                                $match = true;
452
                            }
453
                        }
454 1
                        if (!$match) {
455 1
                            $em->remove($peh);
456 1
                            $em->flush();
457
                        }
458
                    }
459
                }
460
            }
461
462 1
            $em->persist($plugin);
463 1
            $this->callPluginManagerMethod($meta, 'update');
464 1
            $em->flush();
465 1
            $em->getConnection()->commit();
466
        } catch (\Exception $e) {
467
            $em->getConnection()->rollback();
468
            throw $e;
469
        }
470
    }
471
}
472