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 |