| Total Complexity | 40 |
| Total Lines | 393 |
| Duplicated Lines | 14.5 % |
| Changes | 0 | ||
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 UserRepository 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.
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 UserRepository, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 17 | class UserRepository extends Repository |
||
| 18 | { |
||
| 19 | |||
| 20 | /** |
||
| 21 | * Convenience method to get a new User object. |
||
| 22 | * @param string $username The username. |
||
| 23 | * @param Container $container The DI container. |
||
| 24 | * @return User |
||
| 25 | */ |
||
| 26 | public static function getUser($username, Container $container) |
||
| 27 | { |
||
| 28 | $user = new User($username); |
||
| 29 | $userRepo = new UserRepository(); |
||
| 30 | $userRepo->setContainer($container); |
||
| 31 | $user->setRepository($userRepo); |
||
| 32 | return $user; |
||
| 33 | } |
||
| 34 | |||
| 35 | /** |
||
| 36 | * Get the user's ID. |
||
| 37 | * @param string $databaseName The database to query. |
||
| 38 | * @param string $username The username to find. |
||
| 39 | * @return int |
||
| 40 | */ |
||
| 41 | View Code Duplication | public function getId($databaseName, $username) |
|
|
|
|||
| 42 | { |
||
| 43 | $cacheKey = $this->getCacheKey(func_get_args(), 'user_id'); |
||
| 44 | if ($this->cache->hasItem($cacheKey)) { |
||
| 45 | return $this->cache->getItem($cacheKey)->get(); |
||
| 46 | } |
||
| 47 | |||
| 48 | $userTable = $this->getTableName($databaseName, 'user'); |
||
| 49 | $sql = "SELECT user_id FROM $userTable WHERE user_name = :username LIMIT 1"; |
||
| 50 | $resultQuery = $this->getProjectsConnection()->prepare($sql); |
||
| 51 | $resultQuery->bindParam('username', $username); |
||
| 52 | $resultQuery->execute(); |
||
| 53 | $userId = (int)$resultQuery->fetchColumn(); |
||
| 54 | |||
| 55 | // Cache for 10 minutes and return. |
||
| 56 | $this->setCache($cacheKey, $userId); |
||
| 57 | return $userId; |
||
| 58 | } |
||
| 59 | |||
| 60 | /** |
||
| 61 | * Get the user's registration date. |
||
| 62 | * @param string $databaseName The database to query. |
||
| 63 | * @param string $username The username to find. |
||
| 64 | * @return string|null As returned by the database. |
||
| 65 | */ |
||
| 66 | View Code Duplication | public function getRegistrationDate($databaseName, $username) |
|
| 67 | { |
||
| 68 | $cacheKey = $this->getCacheKey(func_get_args(), 'user_registration'); |
||
| 69 | if ($this->cache->hasItem($cacheKey)) { |
||
| 70 | return $this->cache->getItem($cacheKey)->get(); |
||
| 71 | } |
||
| 72 | |||
| 73 | $userTable = $this->getTableName($databaseName, 'user'); |
||
| 74 | $sql = "SELECT user_registration FROM $userTable WHERE user_name = :username LIMIT 1"; |
||
| 75 | $resultQuery = $this->getProjectsConnection()->prepare($sql); |
||
| 76 | $resultQuery->bindParam('username', $username); |
||
| 77 | $resultQuery->execute(); |
||
| 78 | $registrationDate = $resultQuery->fetchColumn(); |
||
| 79 | |||
| 80 | // Cache and return. |
||
| 81 | $this->setCache($cacheKey, $registrationDate); |
||
| 82 | return $registrationDate; |
||
| 83 | } |
||
| 84 | |||
| 85 | /** |
||
| 86 | * Get the user's (system) edit count. |
||
| 87 | * @param string $databaseName The database to query. |
||
| 88 | * @param string $username The username to find. |
||
| 89 | * @return int|null As returned by the database. |
||
| 90 | */ |
||
| 91 | View Code Duplication | public function getEditCount($databaseName, $username) |
|
| 92 | { |
||
| 93 | $userTable = $this->getTableName($databaseName, 'user'); |
||
| 94 | $sql = "SELECT user_editcount FROM $userTable WHERE user_name = :username LIMIT 1"; |
||
| 95 | $resultQuery = $this->getProjectsConnection()->prepare($sql); |
||
| 96 | $resultQuery->bindParam('username', $username); |
||
| 97 | $resultQuery->execute(); |
||
| 98 | return $resultQuery->fetchColumn(); |
||
| 99 | } |
||
| 100 | |||
| 101 | /** |
||
| 102 | * Get group names of the given user. |
||
| 103 | * @param Project $project The project. |
||
| 104 | * @param string $username The username. |
||
| 105 | * @return string[] |
||
| 106 | */ |
||
| 107 | public function getGroups(Project $project, $username) |
||
| 108 | { |
||
| 109 | // Use md5 to ensure the key does not contain reserved characters. |
||
| 110 | $cacheKey = $this->getCacheKey(func_get_args(), 'user_groups'); |
||
| 111 | if ($this->cache->hasItem($cacheKey)) { |
||
| 112 | return $this->cache->getItem($cacheKey)->get(); |
||
| 113 | } |
||
| 114 | |||
| 115 | $this->stopwatch->start($cacheKey, 'XTools'); |
||
| 116 | $api = $this->getMediawikiApi($project); |
||
| 117 | $params = [ |
||
| 118 | 'list' => 'users', |
||
| 119 | 'ususers' => $username, |
||
| 120 | 'usprop' => 'groups' |
||
| 121 | ]; |
||
| 122 | $query = new SimpleRequest('query', $params); |
||
| 123 | $result = []; |
||
| 124 | $res = $api->getRequest($query); |
||
| 125 | if (isset($res['batchcomplete']) && isset($res['query']['users'][0]['groups'])) { |
||
| 126 | $result = $res['query']['users'][0]['groups']; |
||
| 127 | } |
||
| 128 | |||
| 129 | // Cache for 10 minutes, and return. |
||
| 130 | $this->setCache($cacheKey, $result); |
||
| 131 | $this->stopwatch->stop($cacheKey); |
||
| 132 | |||
| 133 | return $result; |
||
| 134 | } |
||
| 135 | |||
| 136 | /** |
||
| 137 | * Get a user's global group membership (starting at XTools' default project if none is |
||
| 138 | * provided). This requires the CentralAuth extension to be installed. |
||
| 139 | * @link https://www.mediawiki.org/wiki/Extension:CentralAuth |
||
| 140 | * @param string $username The username. |
||
| 141 | * @param Project $project The project to query. |
||
| 142 | * @return string[] |
||
| 143 | */ |
||
| 144 | public function getGlobalGroups($username, Project $project = null) |
||
| 145 | { |
||
| 146 | // Get the default project if not provided. |
||
| 147 | if (!$project instanceof Project) { |
||
| 148 | $project = ProjectRepository::getDefaultProject($this->container); |
||
| 149 | } |
||
| 150 | |||
| 151 | // Create the API query. |
||
| 152 | $api = $this->getMediawikiApi($project); |
||
| 153 | $params = [ |
||
| 154 | 'meta' => 'globaluserinfo', |
||
| 155 | 'guiuser' => $username, |
||
| 156 | 'guiprop' => 'groups' |
||
| 157 | ]; |
||
| 158 | $query = new SimpleRequest('query', $params); |
||
| 159 | |||
| 160 | // Get the result. |
||
| 161 | $res = $api->getRequest($query); |
||
| 162 | $result = []; |
||
| 163 | if (isset($res['batchcomplete']) && isset($res['query']['globaluserinfo']['groups'])) { |
||
| 164 | $result = $res['query']['globaluserinfo']['groups']; |
||
| 165 | } |
||
| 166 | return $result; |
||
| 167 | } |
||
| 168 | |||
| 169 | /** |
||
| 170 | * Search the ipblocks table to see if the user is currently blocked |
||
| 171 | * and return the expiry if they are. |
||
| 172 | * @param $databaseName The database to query. |
||
| 173 | * @param $userid The ID of the user to search for. |
||
| 174 | * @return bool|string Expiry of active block or false |
||
| 175 | */ |
||
| 176 | View Code Duplication | public function getBlockExpiry($databaseName, $userid) |
|
| 177 | { |
||
| 178 | $ipblocksTable = $this->getTableName($databaseName, 'ipblocks'); |
||
| 179 | $sql = "SELECT ipb_expiry |
||
| 180 | FROM $ipblocksTable |
||
| 181 | WHERE ipb_user = :userid |
||
| 182 | LIMIT 1"; |
||
| 183 | $resultQuery = $this->getProjectsConnection()->prepare($sql); |
||
| 184 | $resultQuery->bindParam('userid', $userid); |
||
| 185 | $resultQuery->execute(); |
||
| 186 | return $resultQuery->fetchColumn(); |
||
| 187 | } |
||
| 188 | |||
| 189 | /** |
||
| 190 | * Get pages created by a user |
||
| 191 | * @param Project $project |
||
| 192 | * @param User $user |
||
| 193 | * @param string|int $namespace Namespace ID or 'all' |
||
| 194 | * @param string $redirects One of 'noredirects', 'onlyredirects' or blank for both |
||
| 195 | * @return string[] Result of query, see below. Includes live and deleted pages. |
||
| 196 | */ |
||
| 197 | public function getPagesCreated(Project $project, User $user, $namespace, $redirects) |
||
| 281 | } |
||
| 282 | |||
| 283 | /** |
||
| 284 | * Get edit count within given timeframe and namespace. |
||
| 285 | * @param Project $project |
||
| 286 | * @param User $user |
||
| 287 | * @param int|string $namespace Namespace ID or 'all' for all namespaces |
||
| 288 | * @param string $start Start date in a format accepted by strtotime() |
||
| 289 | * @param string $end End date in a format accepted by strtotime() |
||
| 290 | */ |
||
| 291 | public function countEdits(Project $project, User $user, $namespace = 'all', $start = '', $end = '') |
||
| 317 | } |
||
| 318 | |||
| 319 | /** |
||
| 320 | * Get information about the currently-logged in user. |
||
| 321 | * @return array |
||
| 322 | */ |
||
| 323 | public function getXtoolsUserInfo() |
||
| 324 | { |
||
| 325 | /** @var Session $session */ |
||
| 326 | $session = $this->container->get('session'); |
||
| 327 | return $session->get('logged_in_user'); |
||
| 328 | } |
||
| 329 | |||
| 330 | /** |
||
| 331 | * Maximum number of edits to process, based on configuration. |
||
| 332 | * @return int |
||
| 333 | */ |
||
| 334 | public function maxEdits() |
||
| 335 | { |
||
| 336 | return $this->container->getParameter('app.max_user_edits'); |
||
| 337 | } |
||
| 338 | |||
| 339 | /** |
||
| 340 | * Get SQL clauses for joining on `page` and restricting to a namespace. |
||
| 341 | * @param Project $project |
||
| 342 | * @param int|string $namespace Namespace ID or 'all' for all namespaces. |
||
| 343 | * @return array [page join clause, page namespace clause] |
||
| 344 | */ |
||
| 345 | protected function getPageAndNamespaceSql(Project $project, $namespace) |
||
| 346 | { |
||
| 347 | if ($namespace === 'all') { |
||
| 348 | return [null, null]; |
||
| 349 | } |
||
| 350 | |||
| 351 | $pageTable = $project->getTableName('page'); |
||
| 352 | $pageJoin = $namespace !== 'all' ? "LEFT JOIN $pageTable ON rev_page = page_id" : null; |
||
| 353 | $condNamespace = 'AND page_namespace = :namespace'; |
||
| 354 | |||
| 355 | return [$pageJoin, $condNamespace]; |
||
| 356 | } |
||
| 357 | |||
| 358 | /** |
||
| 359 | * Get SQL clauses for rev_timestamp, based on whether values for |
||
| 360 | * the given start and end parameters exist. |
||
| 361 | * @param string $start |
||
| 362 | * @param string $end |
||
| 363 | * @return string[] Clauses for start and end timestamps. |
||
| 364 | */ |
||
| 365 | protected function getRevTimestampConditions($start, $end) |
||
| 378 | } |
||
| 379 | |||
| 380 | /** |
||
| 381 | * Prepare the given SQL, bind the given parameters, and execute the Doctrine Statement. |
||
| 382 | * @param string $sql |
||
| 383 | * @param User $user |
||
| 384 | * @param string $namespace |
||
| 385 | * @param string $start |
||
| 386 | * @param string $end |
||
| 387 | * @return Doctrine\DBAL\Statement |
||
| 388 | */ |
||
| 389 | protected function executeQuery($sql, User $user, $namespace = 'all', $start = '', $end = '') |
||
| 410 | } |
||
| 411 | } |
||
| 412 |
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.