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 Project 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 Project, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 14 | class Project extends Model |
||
| 15 | { |
||
| 16 | |||
| 17 | /** @var string The project name as supplied by the user. */ |
||
| 18 | protected $nameUnnormalized; |
||
| 19 | |||
| 20 | /** @var string[] Basic metadata about the project */ |
||
| 21 | protected $metadata; |
||
| 22 | |||
| 23 | /** @var string[] Project's 'dbName', 'url' and 'lang'. */ |
||
| 24 | protected $basicInfo; |
||
| 25 | |||
| 26 | /** |
||
| 27 | * Whether the user being queried for in this session |
||
| 28 | * has opted in to restricted statistics |
||
| 29 | * @var bool |
||
| 30 | */ |
||
| 31 | protected $userOptedIn; |
||
| 32 | |||
| 33 | /** |
||
| 34 | * Create a new Project. |
||
| 35 | * @param string $nameOrUrl The project's database name or URL. |
||
| 36 | */ |
||
| 37 | 85 | public function __construct($nameOrUrl) |
|
| 41 | |||
| 42 | /** |
||
| 43 | * Unique identifier this Project, to be used in cache keys. |
||
| 44 | * @see Repository::getCacheKey() |
||
| 45 | * @return string |
||
| 46 | */ |
||
| 47 | public function getCacheKey() |
||
| 51 | |||
| 52 | /** |
||
| 53 | * Get 'dbName', 'url' and 'lang' of the project, the relevant basic info |
||
| 54 | * we can get from the meta database. This is all you need to make |
||
| 55 | * database queries. More comprehensive metadata can be fetched with |
||
| 56 | * getMetadata() at the expense of an API call, which may be cached. |
||
| 57 | * @return string[] |
||
| 58 | */ |
||
| 59 | 39 | protected function getBasicInfo() |
|
| 66 | |||
| 67 | /** |
||
| 68 | * Get full metadata about the project. See ProjectRepository::getMetadata |
||
| 69 | * for more information. |
||
| 70 | * @return string[] |
||
| 71 | */ |
||
| 72 | 15 | protected function getMetadata() |
|
| 80 | |||
| 81 | /** |
||
| 82 | * Does this project exist? |
||
| 83 | * @return bool |
||
| 84 | */ |
||
| 85 | 8 | public function exists() |
|
| 89 | |||
| 90 | /** |
||
| 91 | * The unique domain name of this project, without protocol or path components. |
||
| 92 | * This should be used as the canonical project identifier. |
||
| 93 | * |
||
| 94 | * @return string |
||
| 95 | */ |
||
| 96 | 19 | public function getDomain() |
|
| 101 | |||
| 102 | /** |
||
| 103 | * The name of the database for this project. |
||
| 104 | * |
||
| 105 | * @return string |
||
| 106 | */ |
||
| 107 | 13 | public function getDatabaseName() |
|
| 111 | |||
| 112 | /** |
||
| 113 | * The language for this project. |
||
| 114 | * |
||
| 115 | * @return string |
||
| 116 | */ |
||
| 117 | 2 | public function getLang() |
|
| 121 | |||
| 122 | /** |
||
| 123 | * The project URL is the fully-qualified domain name, with protocol and trailing slash. |
||
| 124 | * |
||
| 125 | * @param bool $withTrailingSlash Whether to append a slash. |
||
| 126 | * @return string |
||
| 127 | */ |
||
| 128 | 13 | public function getUrl($withTrailingSlash = true) |
|
| 132 | |||
| 133 | /** |
||
| 134 | * Get a MediawikiApi object for this Project. |
||
| 135 | * |
||
| 136 | * @return MediawikiApi |
||
| 137 | */ |
||
| 138 | public function getApi() |
||
| 142 | |||
| 143 | /** |
||
| 144 | * The base URL path of this project (that page titles are appended to). |
||
| 145 | * For some wikis the title (apparently) may not be at the end. |
||
| 146 | * Replace $1 with the article name. |
||
| 147 | * |
||
| 148 | * @link https://www.mediawiki.org/wiki/Manual:$wgArticlePath |
||
| 149 | * |
||
| 150 | * @return string |
||
| 151 | */ |
||
| 152 | 4 | View Code Duplication | public function getArticlePath() |
| 159 | |||
| 160 | /** |
||
| 161 | * The URL path of the directory that contains index.php, with no trailing slash. |
||
| 162 | * Defaults to '/w' which is the same as the normal WMF set-up. |
||
| 163 | * |
||
| 164 | * @link https://www.mediawiki.org/wiki/Manual:$wgScriptPath |
||
| 165 | * |
||
| 166 | * @return string |
||
| 167 | */ |
||
| 168 | 2 | View Code Duplication | public function getScriptPath() |
| 175 | |||
| 176 | /** |
||
| 177 | * The URL path to index.php |
||
| 178 | * Defaults to '/w/index.php' which is the same as the normal WMF set-up. |
||
| 179 | * |
||
| 180 | * @return string |
||
| 181 | */ |
||
| 182 | 1 | public function getScript() |
|
| 189 | |||
| 190 | /** |
||
| 191 | * The full URL to api.php |
||
| 192 | * |
||
| 193 | * @return string |
||
| 194 | */ |
||
| 195 | 6 | public function getApiUrl() |
|
| 199 | |||
| 200 | /** |
||
| 201 | * Get this project's title, the human-language full title of the wiki (e.g. "English |
||
| 202 | * Wikipedia" or |
||
| 203 | */ |
||
| 204 | 1 | public function getTitle() |
|
| 209 | |||
| 210 | /** |
||
| 211 | * Get an array of this project's namespaces and their IDs. |
||
| 212 | * |
||
| 213 | * @return string[] Keys are IDs, values are names. |
||
| 214 | */ |
||
| 215 | 8 | public function getNamespaces() |
|
| 220 | |||
| 221 | /** |
||
| 222 | * Get the title of the Main Page. |
||
| 223 | * @return string |
||
| 224 | */ |
||
| 225 | 1 | View Code Duplication | public function getMainPage() |
| 226 | { |
||
| 227 | 1 | $metadata = $this->getMetadata(); |
|
| 228 | 1 | return isset($metadata['general']['mainpage']) |
|
| 229 | 1 | ? $metadata['general']['mainpage'] |
|
| 230 | 1 | : ''; |
|
| 231 | } |
||
| 232 | |||
| 233 | /** |
||
| 234 | * Get a list of users who are in one of the given user groups. |
||
| 235 | * @param string[] User groups to search for. |
||
| 236 | * @return string[] User groups keyed by user name. |
||
| 237 | */ |
||
| 238 | 1 | public function getUsersInGroups($groups) |
|
| 252 | |||
| 253 | /** |
||
| 254 | * Get the name of the page on this project that the user must create in order to opt in for |
||
| 255 | * restricted statistics display. |
||
| 256 | * @param User $user |
||
| 257 | * @return string |
||
| 258 | */ |
||
| 259 | 2 | public function userOptInPage(User $user) |
|
| 264 | |||
| 265 | /** |
||
| 266 | * Has a user opted in to having their restricted statistics displayed to anyone? |
||
| 267 | * @param User $user |
||
| 268 | * @return bool |
||
| 269 | */ |
||
| 270 | 3 | public function userHasOptedIn(User $user) |
|
| 308 | |||
| 309 | /** |
||
| 310 | * Does this project support page assessments? |
||
| 311 | * @return bool |
||
| 312 | */ |
||
| 313 | 2 | public function hasPageAssessments() |
|
| 317 | |||
| 318 | /** |
||
| 319 | * Get the image URL of the badge for the given page assessment |
||
| 320 | * @param string $class Valid classification for project, such as 'Start', 'GA', etc. |
||
| 321 | * @return string URL to image |
||
| 322 | */ |
||
| 323 | 2 | public function getAssessmentBadgeURL($class) |
|
| 335 | |||
| 336 | /** |
||
| 337 | * Normalize and quote a table name for use in SQL. |
||
| 338 | * @param string $tableName |
||
| 339 | * @param string|null $tableExtension Optional table extension, which will only get used if we're on Labs. |
||
| 340 | * @return string Fully-qualified and quoted table name. |
||
| 341 | */ |
||
| 342 | 1 | public function getTableName($tableName, $tableExtension = null) |
|
| 346 | } |
||
| 347 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.