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 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 |
||
48 | class PluginService |
||
49 | { |
||
50 | /** |
||
51 | * @Inject(PluginEventHandlerRepository::class) |
||
52 | * @var PluginEventHandlerRepository |
||
53 | */ |
||
54 | protected $pluginEventHandlerRepository; |
||
55 | |||
56 | /** |
||
57 | * @Inject("orm.em") |
||
58 | * @var EntityManager |
||
59 | */ |
||
60 | protected $entityManager; |
||
61 | |||
62 | /** |
||
63 | * @Inject(PluginRepository::class) |
||
64 | * @var PluginRepository |
||
65 | */ |
||
66 | protected $pluginRepository; |
||
67 | |||
68 | /** |
||
69 | * @Inject("config") |
||
70 | * @var array |
||
71 | */ |
||
72 | protected $appConfig; |
||
73 | |||
74 | /** |
||
75 | * @Inject(Application::class) |
||
76 | * @var Application |
||
77 | */ |
||
78 | protected $app; |
||
79 | |||
80 | /** |
||
81 | * @var EntityProxyService |
||
82 | * @Inject(EntityProxyService::class) |
||
83 | */ |
||
84 | protected $entityProxyService; |
||
85 | |||
86 | /** |
||
87 | * @Inject(SchemaService::class) |
||
88 | * @var SchemaService |
||
89 | */ |
||
90 | protected $schemaService; |
||
91 | |||
92 | /** |
||
93 | 15 | * @Inject("eccube.service.composer") |
|
94 | * @var ComposerServiceInterface |
||
95 | 15 | */ |
|
96 | 15 | protected $composerService; |
|
97 | |||
98 | const CONFIG_YML = 'config.yml'; |
||
99 | const EVENT_YML = 'event.yml'; |
||
100 | 15 | const VENDOR_NAME = 'ec-cube'; |
|
101 | 15 | ||
102 | /** |
||
103 | * Plugin type/library of ec-cube |
||
104 | 15 | */ |
|
105 | 15 | const ECCUBE_LIBRARY = 1; |
|
106 | 15 | ||
107 | /** |
||
108 | 15 | * Plugin type/library of other (except ec-cube) |
|
109 | 15 | */ |
|
110 | const OTHER_LIBRARY = 2; |
||
111 | 13 | ||
112 | 13 | /** |
|
113 | 13 | * ファイル指定してのプラグインインストール |
|
114 | * |
||
115 | 13 | * @param string $path path to tar.gz/zip plugin file |
|
116 | * @param int $source |
||
117 | 13 | * @return mixed |
|
118 | 13 | * @throws PluginException |
|
119 | * @throws \Exception |
||
120 | 13 | */ |
|
121 | public function install($path, $source = 0) |
||
122 | 13 | { |
|
123 | $pluginBaseDir = null; |
||
124 | $tmp = null; |
||
125 | 12 | try { |
|
126 | 12 | // プラグイン配置前に実施する処理 |
|
127 | $this->preInstall(); |
||
128 | 12 | $tmp = $this->createTempDir(); |
|
129 | 4 | ||
130 | 4 | // 一旦テンポラリに展開 |
|
131 | 4 | $this->unpackPluginArchive($path, $tmp); |
|
132 | $this->checkPluginArchiveContent($tmp); |
||
133 | |||
134 | $config = $this->readYml($tmp.'/'.self::CONFIG_YML); |
||
135 | $event = $this->readYml($tmp.'/'.self::EVENT_YML); |
||
136 | 12 | // テンポラリのファイルを削除 |
|
137 | 15 | $this->deleteFile($tmp); |
|
138 | 12 | ||
139 | // 重複していないかチェック |
||
140 | 15 | $this->checkSamePlugin($config['code']); |
|
141 | |||
142 | $pluginBaseDir = $this->calcPluginDir($config['code']); |
||
143 | // 本来の置き場所を作成 |
||
144 | $this->createPluginDir($pluginBaseDir); |
||
145 | |||
146 | // 問題なければ本当のplugindirへ |
||
147 | $this->unpackPluginArchive($path, $pluginBaseDir); |
||
148 | 15 | ||
149 | 15 | // Check dependent plugin |
|
150 | // Don't install ec-cube library |
||
151 | 15 | $dependents = $this->getDependentByCode($config['code'], self::OTHER_LIBRARY); |
|
152 | if (!empty($dependents)) { |
||
153 | $package = $this->parseToComposerCommand($dependents); |
||
154 | $this->composerService->execRequire($package); |
||
155 | 15 | } |
|
156 | |||
157 | // プラグイン配置後に実施する処理 |
||
158 | $this->postInstall($config, $event, $source); |
||
159 | } catch (PluginException $e) { |
||
160 | 4 | $this->deleteDirs(array($tmp, $pluginBaseDir)); |
|
161 | 4 | throw $e; |
|
162 | 3 | } catch (\Exception $e) { |
|
163 | 4 | // インストーラがどんなExceptionを上げるかわからないので |
|
164 | $this->deleteDirs(array($tmp, $pluginBaseDir)); |
||
165 | throw $e; |
||
166 | } |
||
167 | |||
168 | return true; |
||
169 | } |
||
170 | 15 | ||
171 | // インストール事前処理 |
||
172 | 15 | public function preInstall() |
|
178 | 15 | ||
179 | 15 | // インストール事後処理 |
|
180 | public function postInstall($config, $event, $source) |
||
203 | |||
204 | 1067 | public function createTempDir() |
|
215 | 1067 | ||
216 | public function deleteDirs($arr) |
||
225 | |||
226 | public function unpackPluginArchive($archive, $dir) |
||
243 | |||
244 | public function checkPluginArchiveContent($dir, array $config_cache = array()) |
||
284 | |||
285 | 13 | public function readYml($yml) |
|
293 | |||
294 | 2 | public function checkSymbolName($string) |
|
301 | 2 | ||
302 | public function deleteFile($path) |
||
307 | |||
308 | 13 | public function checkSamePlugin($code) |
|
315 | |||
316 | public function calcPluginDir($name) |
||
320 | |||
321 | public function createPluginDir($d) |
||
328 | |||
329 | public function registerPlugin($meta, $event_yml, $source = 0) |
||
377 | 12 | ||
378 | 10 | public function callPluginManagerMethod($meta, $method) |
|
388 | 12 | ||
389 | 12 | public function uninstall(\Eccube\Entity\Plugin $plugin) |
|
406 | |||
407 | public function unregisterPlugin(\Eccube\Entity\Plugin $p) |
||
420 | 11 | ||
421 | public function disable(\Eccube\Entity\Plugin $plugin) |
||
425 | 10 | ||
426 | 10 | /** |
|
427 | 10 | * Proxyを再生成します. |
|
428 | 1 | * @param Plugin $plugin プラグイン |
|
429 | 1 | * @param boolean $temporary プラグインが無効状態でも一時的に生成するかどうか |
|
430 | 1 | * @param string|null $outputDir 出力先 |
|
431 | * @return array 生成されたファイルのパス |
||
432 | */ |
||
433 | 10 | private function regenerateProxy(Plugin $plugin, $temporary, $outputDir = null) |
|
466 | |||
467 | public function enable(\Eccube\Entity\Plugin $plugin, $enable = true) |
||
493 | |||
494 | /** |
||
495 | * Update plugin |
||
496 | 1 | * |
|
497 | 1 | * @param Plugin $plugin |
|
498 | 1 | * @param string $path |
|
499 | 1 | * @return bool |
|
500 | 1 | * @throws PluginException |
|
501 | * @throws \Exception |
||
502 | 1 | */ |
|
503 | 1 | public function update(\Eccube\Entity\Plugin $plugin, $path) |
|
504 | 1 | { |
|
505 | 1 | $pluginBaseDir = null; |
|
506 | 1 | $tmp = null; |
|
507 | 1 | try { |
|
508 | 1 | PluginConfigManager::removePluginConfigCache(); |
|
509 | 1 | CacheUtil::clear($this->app, false); |
|
510 | 1 | $tmp = $this->createTempDir(); |
|
511 | |||
512 | $this->unpackPluginArchive($path, $tmp); //一旦テンポラリに展開 |
||
513 | $this->checkPluginArchiveContent($tmp); |
||
514 | |||
515 | $config = $this->readYml($tmp.'/'.self::CONFIG_YML); |
||
516 | 1 | $event = $this->readYml($tmp.'/event.yml'); |
|
517 | 1 | ||
518 | if ($plugin->getCode() != $config['code']) { |
||
519 | throw new PluginException('new/old plugin code is different.'); |
||
520 | } |
||
521 | 1 | ||
522 | 1 | $pluginBaseDir = $this->calcPluginDir($config['code']); |
|
523 | 1 | $this->deleteFile($tmp); // テンポラリのファイルを削除 |
|
524 | 1 | ||
525 | $this->unpackPluginArchive($path, $pluginBaseDir); // 問題なければ本当のplugindirへ |
||
526 | |||
527 | 1 | // Check dependent plugin |
|
528 | 1 | // Don't install ec-cube library |
|
529 | 1 | $dependents = $this->getDependentByCode($config['code'], self::OTHER_LIBRARY); |
|
530 | if (!empty($dependents)) { |
||
531 | $package = $this->parseToComposerCommand($dependents); |
||
532 | $this->composerService->execRequire($package); |
||
533 | } |
||
534 | |||
535 | 1 | $this->updatePlugin($plugin, $config, $event); // dbにプラグイン登録 |
|
536 | 1 | PluginConfigManager::writePluginConfigCache(); |
|
537 | 1 | } catch (PluginException $e) { |
|
538 | 1 | $this->deleteDirs([$tmp]); |
|
539 | throw $e; |
||
540 | } catch (\Exception $e) { |
||
541 | // catch exception of composer |
||
542 | $this->deleteDirs([$tmp]); |
||
543 | throw $e; |
||
544 | } |
||
545 | |||
546 | return true; |
||
547 | } |
||
548 | |||
549 | public function updatePlugin(\Eccube\Entity\Plugin $plugin, $meta, $event_yml) |
||
618 | |||
619 | /** |
||
620 | * Do check dependency plugin |
||
621 | * |
||
622 | * @param array $plugins get from api |
||
623 | * @param array $plugin format as plugin from api |
||
624 | * @param array $dependents template output |
||
625 | * @return array|mixed |
||
626 | */ |
||
627 | public function getDependency($plugins, $plugin, $dependents = array()) |
||
659 | |||
660 | /** |
||
661 | * Get plugin information |
||
662 | * |
||
663 | * @param array $plugins get from api |
||
664 | * @param string $pluginCode |
||
665 | * @return array|null |
||
666 | */ |
||
667 | public function buildInfo($plugins, $pluginCode) |
||
688 | |||
689 | /** |
||
690 | * Get dependency name and version only |
||
691 | * |
||
692 | * @param array $plugins get from api |
||
693 | * @param array $plugin target plugin from api |
||
694 | * @return mixed format [0 => ['name' => pluginName1, 'version' => pluginVersion1], 1 => ['name' => pluginName2, 'version' => pluginVersion2]] |
||
695 | */ |
||
696 | public function getRequirePluginName($plugins, $plugin) |
||
714 | |||
715 | /** |
||
716 | * Check require plugin in enable |
||
717 | * |
||
718 | * @param string $pluginCode |
||
719 | * @return array plugin code |
||
720 | */ |
||
721 | public function findRequirePluginNeedEnable($pluginCode) |
||
753 | /** |
||
754 | * Find the dependent plugins that need to be disabled |
||
755 | * |
||
756 | * @param string $pluginCode |
||
757 | * @return array plugin code |
||
758 | */ |
||
759 | public function findDependentPluginNeedDisable($pluginCode) |
||
763 | |||
764 | /** |
||
765 | * Find the other plugin that has requires on it. |
||
766 | * Check in both dtb_plugin table and <PluginCode>/composer.json |
||
767 | * |
||
768 | * @param string $pluginCode |
||
769 | * @param bool $enableOnly |
||
770 | * @return array plugin code |
||
771 | */ |
||
772 | public function findDependentPlugin($pluginCode, $enableOnly = false) |
||
804 | |||
805 | /** |
||
806 | * Get dependent plugin by code |
||
807 | * It's base on composer.json |
||
808 | * Return the plugin code and version in the format of the composer |
||
809 | * |
||
810 | * @param string $pluginCode |
||
811 | * @param int|null $libraryType |
||
812 | * self::ECCUBE_LIBRARY only return library/plugin of eccube |
||
813 | * self::OTHER_LIBRARY only return library/plugin of 3rd part ex: symfony, composer, ... |
||
814 | * default : return all library/plugin |
||
815 | * @return array format [packageName1 => version1, packageName2 => version2] |
||
816 | */ |
||
817 | public function getDependentByCode($pluginCode, $libraryType = null) |
||
846 | |||
847 | /** |
||
848 | * Format array dependent plugin to string |
||
849 | * It is used for commands. |
||
850 | * |
||
851 | * @param array $packages format [packageName1 => version1, packageName2 => version2] |
||
852 | * @param bool $getVersion |
||
853 | * @return string format if version=true: "packageName1:version1 packageName2:version2", if version=false: "packageName1 packageName2" |
||
854 | */ |
||
855 | public function parseToComposerCommand(array $packages, $getVersion = true) |
||
866 | |||
867 | /** |
||
868 | * @param string $pluginVersion |
||
869 | * @param string $remoteVersion |
||
870 | * @return mixed |
||
871 | */ |
||
872 | public function isUpdate($pluginVersion, $remoteVersion) |
||
876 | |||
877 | /** |
||
878 | * @param array $plugins get from api |
||
879 | * @param string $pluginCode |
||
880 | * @return false|int|string |
||
881 | */ |
||
882 | private function checkPluginExist($plugins, $pluginCode) |
||
892 | |||
893 | /** |
||
894 | * @param string $code |
||
895 | * @return bool |
||
896 | */ |
||
897 | private function isEnable($code) |
||
909 | } |
||
910 |