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 Installer 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 Installer, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 53 | class Installer { |
||
| 54 | |||
| 55 | /** |
||
| 56 | * |
||
| 57 | * This function installs an app. All information needed are passed in the |
||
| 58 | * associative array $data. |
||
| 59 | * The following keys are required: |
||
| 60 | * - source: string, can be "path" or "http" |
||
| 61 | * |
||
| 62 | * One of the following keys is required: |
||
| 63 | * - path: path to the file containing the app |
||
| 64 | * - href: link to the downloadable file containing the app |
||
| 65 | * |
||
| 66 | * The following keys are optional: |
||
| 67 | * - pretend: boolean, if set true the system won't do anything |
||
| 68 | * - noinstall: boolean, if true appinfo/install.php won't be loaded |
||
| 69 | * - inactive: boolean, if set true the appconfig/app.sample.php won't be |
||
| 70 | * renamed |
||
| 71 | * |
||
| 72 | * This function works as follows |
||
| 73 | * -# fetching the file |
||
| 74 | * -# unzipping it |
||
| 75 | * -# check the code |
||
| 76 | * -# installing the database at appinfo/database.xml |
||
| 77 | * -# including appinfo/install.php |
||
| 78 | * -# setting the installed version |
||
| 79 | * |
||
| 80 | * It is the task of oc_app_install to create the tables and do whatever is |
||
| 81 | * needed to get the app working. |
||
| 82 | * |
||
| 83 | * Installs an app |
||
| 84 | * @param array $data with all information |
||
| 85 | * @throws \Exception |
||
| 86 | * @return integer |
||
| 87 | */ |
||
| 88 | public static function installApp( $data = array()) { |
||
| 89 | $l = \OC::$server->getL10N('lib'); |
||
| 90 | |||
| 91 | list($extractDir, $path) = self::downloadApp($data); |
||
| 92 | |||
| 93 | $info = self::checkAppsIntegrity($data, $extractDir, $path); |
||
| 94 | $appId = OC_App::cleanAppId($info['id']); |
||
| 95 | $basedir = OC_App::getInstallPath().'/'.$appId; |
||
| 96 | //check if the destination directory already exists |
||
| 97 | if(is_dir($basedir)) { |
||
| 98 | OC_Helper::rmdirr($extractDir); |
||
| 99 | if($data['source']=='http') { |
||
| 100 | unlink($path); |
||
| 101 | } |
||
| 102 | throw new \Exception($l->t("App directory already exists")); |
||
| 103 | } |
||
| 104 | |||
| 105 | if(!empty($data['pretent'])) { |
||
| 106 | return false; |
||
|
|
|||
| 107 | } |
||
| 108 | |||
| 109 | //copy the app to the correct place |
||
| 110 | if(@!mkdir($basedir)) { |
||
| 111 | OC_Helper::rmdirr($extractDir); |
||
| 112 | if($data['source']=='http') { |
||
| 113 | unlink($path); |
||
| 114 | } |
||
| 115 | throw new \Exception($l->t("Can't create app folder. Please fix permissions. %s", array($basedir))); |
||
| 116 | } |
||
| 117 | |||
| 118 | $extractDir .= '/' . $info['id']; |
||
| 119 | View Code Duplication | if(!file_exists($extractDir)) { |
|
| 120 | OC_Helper::rmdirr($basedir); |
||
| 121 | throw new \Exception($l->t("Archive does not contain a directory named %s", $info['id'])); |
||
| 122 | } |
||
| 123 | OC_Helper::copyr($extractDir, $basedir); |
||
| 124 | |||
| 125 | //remove temporary files |
||
| 126 | OC_Helper::rmdirr($extractDir); |
||
| 127 | |||
| 128 | //install the database |
||
| 129 | if(is_file($basedir.'/appinfo/database.xml')) { |
||
| 130 | if (\OC::$server->getAppConfig()->getValue($info['id'], 'installed_version') === null) { |
||
| 131 | OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml'); |
||
| 132 | } else { |
||
| 133 | OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml'); |
||
| 134 | } |
||
| 135 | } |
||
| 136 | |||
| 137 | \OC_App::setupBackgroundJobs($info['background-jobs']); |
||
| 138 | |||
| 139 | //run appinfo/install.php |
||
| 140 | if((!isset($data['noinstall']) or $data['noinstall']==false)) { |
||
| 141 | self::includeAppScript($basedir . '/appinfo/install.php'); |
||
| 142 | } |
||
| 143 | |||
| 144 | $appData = OC_App::getAppInfo($appId); |
||
| 145 | OC_App::executeRepairSteps($appId, $appData['repair-steps']['install']); |
||
| 146 | |||
| 147 | //set the installed version |
||
| 148 | \OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'])); |
||
| 149 | \OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no'); |
||
| 150 | |||
| 151 | //set remote/public handlers |
||
| 152 | foreach($info['remote'] as $name=>$path) { |
||
| 153 | \OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path); |
||
| 154 | } |
||
| 155 | View Code Duplication | foreach($info['public'] as $name=>$path) { |
|
| 156 | \OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path); |
||
| 157 | } |
||
| 158 | |||
| 159 | OC_App::setAppTypes($info['id']); |
||
| 160 | |||
| 161 | return $info['id']; |
||
| 162 | } |
||
| 163 | |||
| 164 | /** |
||
| 165 | * @brief checks whether or not an app is installed |
||
| 166 | * @param string $app app |
||
| 167 | * @returns bool |
||
| 168 | * |
||
| 169 | * Checks whether or not an app is installed, i.e. registered in apps table. |
||
| 170 | */ |
||
| 171 | public static function isInstalled( $app ) { |
||
| 174 | |||
| 175 | /** |
||
| 176 | * @brief Update an application |
||
| 177 | * @param array $info |
||
| 178 | * @param bool $isShipped |
||
| 179 | * @throws \Exception |
||
| 180 | * @return bool |
||
| 181 | * |
||
| 182 | * This function could work like described below, but currently it disables and then |
||
| 183 | * enables the app again. This does result in an updated app. |
||
| 184 | * |
||
| 185 | * |
||
| 186 | * This function installs an app. All information needed are passed in the |
||
| 187 | * associative array $info. |
||
| 188 | * The following keys are required: |
||
| 189 | * - source: string, can be "path" or "http" |
||
| 190 | * |
||
| 191 | * One of the following keys is required: |
||
| 192 | * - path: path to the file containing the app |
||
| 193 | * - href: link to the downloadable file containing the app |
||
| 194 | * |
||
| 195 | * The following keys are optional: |
||
| 196 | * - pretend: boolean, if set true the system won't do anything |
||
| 197 | * - noupgrade: boolean, if true appinfo/upgrade.php won't be loaded |
||
| 198 | * |
||
| 199 | * This function works as follows |
||
| 200 | * -# fetching the file |
||
| 201 | * -# removing the old files |
||
| 202 | * -# unzipping new file |
||
| 203 | * -# including appinfo/upgrade.php |
||
| 204 | * -# setting the installed version |
||
| 205 | * |
||
| 206 | * upgrade.php can determine the current installed version of the app using |
||
| 207 | * "\OC::$server->getAppConfig()->getValue($appid, 'installed_version')" |
||
| 208 | */ |
||
| 209 | public static function updateApp($info=array(), $isShipped=false) { |
||
| 236 | |||
| 237 | /** |
||
| 238 | * update an app by it's id |
||
| 239 | * |
||
| 240 | * @param integer $ocsId |
||
| 241 | * @return bool |
||
| 242 | * @throws \Exception |
||
| 243 | */ |
||
| 244 | public static function updateAppByOCSId($ocsId) { |
||
| 266 | |||
| 267 | /** |
||
| 268 | * @param array $data |
||
| 269 | * @return array |
||
| 270 | * @throws \Exception |
||
| 271 | */ |
||
| 272 | public static function downloadApp($data = array()) { |
||
| 321 | |||
| 322 | /** |
||
| 323 | * check an app's integrity |
||
| 324 | * @param array $data |
||
| 325 | * @param string $extractDir |
||
| 326 | * @param string $path |
||
| 327 | * @param bool $isShipped |
||
| 328 | * @return array |
||
| 329 | * @throws \Exception |
||
| 330 | */ |
||
| 331 | public static function checkAppsIntegrity($data, $extractDir, $path, $isShipped = false) { |
||
| 415 | |||
| 416 | /** |
||
| 417 | * Check if an update for the app is available |
||
| 418 | * @param string $app |
||
| 419 | * @return string|false false or the version number of the update |
||
| 420 | * |
||
| 421 | * The function will check if an update for a version is available |
||
| 422 | */ |
||
| 423 | public static function isUpdateAvailable( $app ) { |
||
| 461 | |||
| 462 | /** |
||
| 463 | * Check if app is already downloaded |
||
| 464 | * @param string $name name of the application to remove |
||
| 465 | * @return boolean |
||
| 466 | * |
||
| 467 | * The function will check if the app is already downloaded in the apps repository |
||
| 468 | */ |
||
| 469 | public static function isDownloaded( $name ) { |
||
| 483 | |||
| 484 | /** |
||
| 485 | * Removes an app |
||
| 486 | * @param string $name name of the application to remove |
||
| 487 | * @return boolean |
||
| 488 | * |
||
| 489 | * |
||
| 490 | * This function works as follows |
||
| 491 | * -# call uninstall repair steps |
||
| 492 | * -# removing the files |
||
| 493 | * |
||
| 494 | * The function will not delete preferences, tables and the configuration, |
||
| 495 | * this has to be done by the function oc_app_uninstall(). |
||
| 496 | */ |
||
| 497 | public static function removeApp($appId) { |
||
| 511 | |||
| 512 | /** |
||
| 513 | * Installs shipped apps |
||
| 514 | * |
||
| 515 | * This function installs all apps found in the 'apps' directory that should be enabled by default; |
||
| 516 | * @param bool $softErrors When updating we ignore errors and simply log them, better to have a |
||
| 517 | * working ownCloud at the end instead of an aborted update. |
||
| 518 | * @return array Array of error messages (appid => Exception) |
||
| 519 | */ |
||
| 520 | public static function installShippedApps($softErrors = false) { |
||
| 557 | |||
| 558 | /** |
||
| 559 | * install an app already placed in the app folder |
||
| 560 | * @param string $app id of the app to install |
||
| 561 | * @return integer |
||
| 562 | */ |
||
| 563 | public static function installShippedApp($app) { |
||
| 609 | |||
| 610 | /** |
||
| 611 | * check the code of an app with some static code checks |
||
| 612 | * @param string $folder the folder of the app to check |
||
| 613 | * @return boolean true for app is o.k. and false for app is not o.k. |
||
| 614 | */ |
||
| 615 | public static function checkCode($folder) { |
||
| 626 | |||
| 627 | /** |
||
| 628 | * @param $basedir |
||
| 629 | */ |
||
| 630 | private static function includeAppScript($script) { |
||
| 635 | } |
||
| 636 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.