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 AppManager 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 AppManager, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 47 | class AppManager implements IAppManager { |
||
| 48 | |||
| 49 | /** |
||
| 50 | * Apps with these types can not be enabled for certain groups only |
||
| 51 | * @var string[] |
||
| 52 | */ |
||
| 53 | protected $protectedAppTypes = [ |
||
| 54 | 'filesystem', |
||
| 55 | 'prelogin', |
||
| 56 | 'authentication', |
||
| 57 | 'logging', |
||
| 58 | 'prevent_group_restriction', |
||
| 59 | ]; |
||
| 60 | |||
| 61 | /** @var \OCP\IUserSession */ |
||
| 62 | private $userSession; |
||
| 63 | /** @var \OCP\IAppConfig */ |
||
| 64 | private $appConfig; |
||
| 65 | /** @var \OCP\IGroupManager */ |
||
| 66 | private $groupManager; |
||
| 67 | /** @var \OCP\ICacheFactory */ |
||
| 68 | private $memCacheFactory; |
||
| 69 | /** @var string[] $appId => $enabled */ |
||
| 70 | private $installedAppsCache; |
||
| 71 | /** @var string[] */ |
||
| 72 | private $shippedApps; |
||
| 73 | /** @var string[] */ |
||
| 74 | private $alwaysEnabled; |
||
| 75 | /** @var EventDispatcherInterface */ |
||
| 76 | private $dispatcher; |
||
| 77 | /** @var IConfig */ |
||
| 78 | private $config; |
||
| 79 | |||
| 80 | /** |
||
| 81 | * Apps as 'appId' => [ |
||
| 82 | * 'path' => '/app/path' |
||
| 83 | * 'url' => '/app/url' |
||
| 84 | * ] |
||
| 85 | * @var string[][] |
||
| 86 | */ |
||
| 87 | private $appDirs = []; |
||
| 88 | |||
| 89 | /** |
||
| 90 | * @param IUserSession $userSession |
||
| 91 | * @param IAppConfig $appConfig |
||
| 92 | * @param IGroupManager $groupManager |
||
| 93 | * @param ICacheFactory $memCacheFactory |
||
| 94 | * @param EventDispatcherInterface $dispatcher |
||
| 95 | * @param IConfig $config |
||
| 96 | */ |
||
| 97 | public function __construct(IUserSession $userSession = null, |
||
| 110 | |||
| 111 | /** |
||
| 112 | * @return string[] $appId => $enabled |
||
| 113 | */ |
||
| 114 | private function getInstalledAppsValues() { |
||
| 130 | |||
| 131 | /** |
||
| 132 | * List all installed apps |
||
| 133 | * |
||
| 134 | * @return string[] |
||
| 135 | */ |
||
| 136 | public function getInstalledApps() { |
||
| 139 | |||
| 140 | /** |
||
| 141 | * List all apps enabled for a user |
||
| 142 | * |
||
| 143 | * @param \OCP\IUser|null $user |
||
| 144 | * @return string[] |
||
| 145 | */ |
||
| 146 | public function getEnabledAppsForUser(IUser $user = null) { |
||
| 153 | |||
| 154 | /** |
||
| 155 | * Check if an app is enabled for user |
||
| 156 | * |
||
| 157 | * @param string $appId |
||
| 158 | * @param \OCP\IUser $user (optional) if not defined, the currently logged in user will be used |
||
| 159 | * @return bool |
||
| 160 | */ |
||
| 161 | public function isEnabledForUser($appId, $user = null) { |
||
| 175 | |||
| 176 | /** |
||
| 177 | * @param string $enabled |
||
| 178 | * @param IUser $user |
||
| 179 | * @return bool |
||
| 180 | */ |
||
| 181 | private function checkAppForUser($enabled, $user) { |
||
| 208 | |||
| 209 | /** |
||
| 210 | * Check if an app is installed in the instance |
||
| 211 | * |
||
| 212 | * @param string $appId |
||
| 213 | * @return bool |
||
| 214 | */ |
||
| 215 | public function isInstalled($appId) { |
||
| 219 | |||
| 220 | /** |
||
| 221 | * Enable an app for every user |
||
| 222 | * |
||
| 223 | * @param string $appId |
||
| 224 | * @throws \Exception |
||
| 225 | */ |
||
| 226 | View Code Duplication | public function enableApp($appId) { |
|
| 227 | if($this->getAppPath($appId) === false) { |
||
| 228 | throw new \Exception("$appId can't be enabled since it is not installed."); |
||
| 229 | } |
||
| 230 | $this->canEnableTheme($appId); |
||
| 231 | |||
| 232 | $this->installedAppsCache[$appId] = 'yes'; |
||
| 233 | $this->appConfig->setValue($appId, 'enabled', 'yes'); |
||
| 234 | $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent( |
||
| 235 | ManagerEvent::EVENT_APP_ENABLE, $appId |
||
| 236 | )); |
||
| 237 | $this->clearAppsCache(); |
||
| 238 | } |
||
| 239 | |||
| 240 | /** |
||
| 241 | * Do not allow more than one active app-theme |
||
| 242 | * |
||
| 243 | * @param $appId |
||
| 244 | * @throws AppManagerException |
||
| 245 | */ |
||
| 246 | protected function canEnableTheme($appId) { |
||
| 261 | |||
| 262 | /** |
||
| 263 | * Wrapper for OC_App for easy mocking |
||
| 264 | * |
||
| 265 | * @param string $appId |
||
| 266 | * @return bool |
||
| 267 | */ |
||
| 268 | protected function isTheme($appId) { |
||
| 271 | |||
| 272 | /** |
||
| 273 | * Enable an app only for specific groups |
||
| 274 | * |
||
| 275 | * @param string $appId |
||
| 276 | * @param \OCP\IGroup[] $groups |
||
| 277 | * @throws \Exception if app can't be enabled for groups |
||
| 278 | */ |
||
| 279 | public function enableAppForGroups($appId, $groups) { |
||
| 299 | |||
| 300 | /** |
||
| 301 | * Disable an app for every user |
||
| 302 | * |
||
| 303 | * @param string $appId |
||
| 304 | * @throws \Exception if app can't be disabled |
||
| 305 | */ |
||
| 306 | View Code Duplication | public function disableApp($appId) { |
|
| 307 | if ($this->isAlwaysEnabled($appId)) { |
||
| 308 | throw new \Exception("$appId can't be disabled."); |
||
| 309 | } |
||
| 310 | unset($this->installedAppsCache[$appId]); |
||
| 311 | $this->appConfig->setValue($appId, 'enabled', 'no'); |
||
| 312 | $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent( |
||
| 313 | ManagerEvent::EVENT_APP_DISABLE, $appId |
||
| 314 | )); |
||
| 315 | $this->clearAppsCache(); |
||
| 316 | } |
||
| 317 | |||
| 318 | /** |
||
| 319 | * Clear the cached list of apps when enabling/disabling an app |
||
| 320 | */ |
||
| 321 | public function clearAppsCache() { |
||
| 325 | |||
| 326 | /** |
||
| 327 | * Returns a list of apps that need upgrade |
||
| 328 | * |
||
| 329 | * @param array $ocVersion ownCloud version as array of version components |
||
| 330 | * @return array list of app info from apps that need an upgrade |
||
| 331 | * |
||
| 332 | * @internal |
||
| 333 | */ |
||
| 334 | public function getAppsNeedingUpgrade($ocVersion) { |
||
| 351 | |||
| 352 | /** |
||
| 353 | * Returns the app information from "appinfo/info.xml". |
||
| 354 | * |
||
| 355 | * @param string $appId app id |
||
| 356 | * |
||
| 357 | * @return array app info |
||
| 358 | * |
||
| 359 | * @internal |
||
| 360 | */ |
||
| 361 | View Code Duplication | public function getAppInfo($appId) { |
|
| 372 | |||
| 373 | /** |
||
| 374 | * Returns a list of apps incompatible with the given version |
||
| 375 | * |
||
| 376 | * @param array $version ownCloud version as array of version components |
||
| 377 | * |
||
| 378 | * @return array list of app info from incompatible apps |
||
| 379 | * |
||
| 380 | * @internal |
||
| 381 | */ |
||
| 382 | public function getIncompatibleApps($version) { |
||
| 393 | |||
| 394 | /** |
||
| 395 | * @inheritdoc |
||
| 396 | */ |
||
| 397 | public function isShipped($appId) { |
||
| 401 | |||
| 402 | private function isAlwaysEnabled($appId) { |
||
| 406 | |||
| 407 | private function loadShippedJson() { |
||
| 418 | |||
| 419 | /** |
||
| 420 | * @inheritdoc |
||
| 421 | */ |
||
| 422 | public function getAlwaysEnabledApps() { |
||
| 426 | |||
| 427 | /** |
||
| 428 | * @param string $package package path |
||
| 429 | * @param bool $skipMigrations whether to skip migrations, which would only install the code |
||
| 430 | * @return string|false app id or false in case of error |
||
| 431 | * @since 10.0 |
||
| 432 | */ |
||
| 433 | public function installApp($package, $skipMigrations = false) { |
||
| 440 | |||
| 441 | /** |
||
| 442 | * @param string $package |
||
| 443 | * @return mixed |
||
| 444 | * @since 10.0 |
||
| 445 | */ |
||
| 446 | public function updateApp($package) { |
||
| 452 | |||
| 453 | /** |
||
| 454 | * Returns the list of all apps, enabled and disabled |
||
| 455 | * |
||
| 456 | * @return string[] |
||
| 457 | * @since 10.0 |
||
| 458 | */ |
||
| 459 | public function getAllApps() { |
||
| 462 | |||
| 463 | /** |
||
| 464 | * @param string $path |
||
| 465 | * @return string[] app info |
||
| 466 | */ |
||
| 467 | public function readAppPackage($path) { |
||
| 477 | |||
| 478 | /** |
||
| 479 | * Indicates if app installation is supported. Usually it is but in certain |
||
| 480 | * environments it is disallowed because of hardening. In a clustered setup |
||
| 481 | * apps need to be installed on each cluster node which is out of scope of |
||
| 482 | * ownCloud itself. |
||
| 483 | * |
||
| 484 | * @return bool |
||
| 485 | * @since 10.0.3 |
||
| 486 | */ |
||
| 487 | public function canInstall() { |
||
| 495 | |||
| 496 | /** |
||
| 497 | * Get the absolute path to the directory for the given app. |
||
| 498 | * If the app exists in multiple directories, the most recent version is taken. |
||
| 499 | * Returns false if not found |
||
| 500 | * |
||
| 501 | * @param string $appId |
||
| 502 | * @return string|false |
||
| 503 | * @since 10.0.5 |
||
| 504 | */ |
||
| 505 | public function getAppPath($appId) { |
||
| 514 | |||
| 515 | /** |
||
| 516 | * Get the HTTP Web path to the app directory for the given app, relative to the ownCloud webroot. |
||
| 517 | * If the app exists in multiple directories, web path to the most recent version is taken. |
||
| 518 | * Returns false if not found |
||
| 519 | * |
||
| 520 | * @param string $appId |
||
| 521 | * @return string|false |
||
| 522 | * @since 10.0.5 |
||
| 523 | */ |
||
| 524 | public function getAppWebPath($appId) { |
||
| 537 | |||
| 538 | /** |
||
| 539 | * Search for an app in all app directories |
||
| 540 | * Returns an app directory as an array with keys |
||
| 541 | * 'path' - a path to the app with no trailing slash |
||
| 542 | * 'url' - a web path to the app with no trailing slash |
||
| 543 | * both are relative to OC root directory and webroot |
||
| 544 | * |
||
| 545 | * @param string $appId |
||
| 546 | * @return false|string[] |
||
| 547 | */ |
||
| 548 | protected function findAppInDirectories($appId) { |
||
| 579 | |||
| 580 | /** |
||
| 581 | * Save app path and webPath to internal cache |
||
| 582 | * @param string $appId |
||
| 583 | * @param string[] $appData |
||
| 584 | */ |
||
| 585 | protected function saveAppPath($appId, $appData) { |
||
| 588 | |||
| 589 | /** |
||
| 590 | * Get OC web root |
||
| 591 | * Wrapper for easy mocking |
||
| 592 | * @return string |
||
| 593 | */ |
||
| 594 | protected function getOcWebRoot() { |
||
| 597 | |||
| 598 | /** |
||
| 599 | * Get apps roots as an array of path and url |
||
| 600 | * Wrapper for easy mocking |
||
| 601 | * @return string[][] |
||
| 602 | */ |
||
| 603 | protected function getAppRoots(){ |
||
| 606 | |||
| 607 | /** |
||
| 608 | * Get app's version based on it's path |
||
| 609 | * Wrapper for easy mocking |
||
| 610 | * |
||
| 611 | * @param string $path |
||
| 612 | * @return string |
||
| 613 | */ |
||
| 614 | protected function getAppVersionByPath($path) { |
||
| 617 | } |
||
| 618 |
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.