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 User_LDAP 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 User_LDAP, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 49 | class User_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserInterface, IUserLDAP { | 
            ||
| 50 | /** @var \OCP\IConfig */  | 
            ||
| 51 | protected $ocConfig;  | 
            ||
| 52 | |||
| 53 | /** @var INotificationManager */  | 
            ||
| 54 | protected $notificationManager;  | 
            ||
| 55 | |||
| 56 | /** @var string */  | 
            ||
| 57 | protected $currentUserInDeletionProcess;  | 
            ||
| 58 | |||
| 59 | /**  | 
            ||
| 60 | * @param Access $access  | 
            ||
| 61 | * @param \OCP\IConfig $ocConfig  | 
            ||
| 62 | * @param \OCP\Notification\IManager $notificationManager  | 
            ||
| 63 | * @param IUserSession $userSession  | 
            ||
| 64 | */  | 
            ||
| 65 | 	public function __construct(Access $access, IConfig $ocConfig, INotificationManager $notificationManager, IUserSession $userSession) { | 
            ||
| 71 | |||
| 72 | 	protected function registerHooks(IUserSession $userSession) { | 
            ||
| 76 | |||
| 77 | 	public function preDeleteUser(IUser $user) { | 
            ||
| 80 | |||
| 81 | 	public function postDeleteUser() { | 
            ||
| 84 | |||
| 85 | /**  | 
            ||
| 86 | * checks whether the user is allowed to change his avatar in Nextcloud  | 
            ||
| 87 | * @param string $uid the Nextcloud user name  | 
            ||
| 88 | * @return boolean either the user can or cannot  | 
            ||
| 89 | */  | 
            ||
| 90 | 	public function canChangeAvatar($uid) { | 
            ||
| 101 | |||
| 102 | /**  | 
            ||
| 103 | * returns the username for the given login name, if available  | 
            ||
| 104 | *  | 
            ||
| 105 | * @param string $loginName  | 
            ||
| 106 | * @return string|false  | 
            ||
| 107 | */  | 
            ||
| 108 | 	public function loginName2UserName($loginName) { | 
            ||
| 132 | |||
| 133 | /**  | 
            ||
| 134 | * returns the username for the given LDAP DN, if available  | 
            ||
| 135 | *  | 
            ||
| 136 | * @param string $dn  | 
            ||
| 137 | * @return string|false with the username  | 
            ||
| 138 | */  | 
            ||
| 139 | 	public function dn2UserName($dn) { | 
            ||
| 142 | |||
| 143 | /**  | 
            ||
| 144 | * returns an LDAP record based on a given login name  | 
            ||
| 145 | *  | 
            ||
| 146 | * @param string $loginName  | 
            ||
| 147 | * @return array  | 
            ||
| 148 | * @throws NotOnLDAP  | 
            ||
| 149 | */  | 
            ||
| 150 | 	public function getLDAPUserByLoginName($loginName) { | 
            ||
| 160 | |||
| 161 | /**  | 
            ||
| 162 | * Check if the password is correct without logging in the user  | 
            ||
| 163 | *  | 
            ||
| 164 | * @param string $uid The username  | 
            ||
| 165 | * @param string $password The password  | 
            ||
| 166 | * @return false|string  | 
            ||
| 167 | */  | 
            ||
| 168 | 	public function checkPassword($uid, $password) { | 
            ||
| 202 | |||
| 203 | /**  | 
            ||
| 204 | * Set password  | 
            ||
| 205 | * @param string $uid The username  | 
            ||
| 206 | * @param string $password The new password  | 
            ||
| 207 | * @return bool  | 
            ||
| 208 | */  | 
            ||
| 209 | 	public function setPassword($uid, $password) { | 
            ||
| 233 | |||
| 234 | /**  | 
            ||
| 235 | * Get a list of all users  | 
            ||
| 236 | *  | 
            ||
| 237 | * @param string $search  | 
            ||
| 238 | * @param integer $limit  | 
            ||
| 239 | * @param integer $offset  | 
            ||
| 240 | * @return string[] an array of all uids  | 
            ||
| 241 | */  | 
            ||
| 242 | 	public function getUsers($search = '', $limit = 10, $offset = 0) { | 
            ||
| 277 | |||
| 278 | /**  | 
            ||
| 279 | * checks whether a user is still available on LDAP  | 
            ||
| 280 | *  | 
            ||
| 281 | * @param string|\OCA\User_LDAP\User\User $user either the Nextcloud user  | 
            ||
| 282 | * name or an instance of that user  | 
            ||
| 283 | * @return bool  | 
            ||
| 284 | * @throws \Exception  | 
            ||
| 285 | * @throws \OC\ServerNotAvailableException  | 
            ||
| 286 | */  | 
            ||
| 287 | 	public function userExistsOnLDAP($user) { | 
            ||
| 326 | |||
| 327 | /**  | 
            ||
| 328 | * check if a user exists  | 
            ||
| 329 | * @param string $uid the username  | 
            ||
| 330 | * @return boolean  | 
            ||
| 331 | * @throws \Exception when connection could not be established  | 
            ||
| 332 | */  | 
            ||
| 333 | 	public function userExists($uid) { | 
            ||
| 359 | |||
| 360 | /**  | 
            ||
| 361 | * returns whether a user was deleted in LDAP  | 
            ||
| 362 | *  | 
            ||
| 363 | * @param string $uid The username of the user to delete  | 
            ||
| 364 | * @return bool  | 
            ||
| 365 | */  | 
            ||
| 366 | 	public function deleteUser($uid) { | 
            ||
| 381 | |||
| 382 | /**  | 
            ||
| 383 | * get the user's home directory  | 
            ||
| 384 | *  | 
            ||
| 385 | * @param string $uid the username  | 
            ||
| 386 | * @return bool|string  | 
            ||
| 387 | * @throws NoUserException  | 
            ||
| 388 | * @throws \Exception  | 
            ||
| 389 | */  | 
            ||
| 390 | 	public function getHome($uid) { | 
            ||
| 391 | // user Exists check required as it is not done in user proxy!  | 
            ||
| 392 | 		if(!$this->userExists($uid)) { | 
            ||
| 393 | return false;  | 
            ||
| 394 | }  | 
            ||
| 395 | |||
| 396 | $cacheKey = 'getHome'.$uid;  | 
            ||
| 397 | $path = $this->access->connection->getFromCache($cacheKey);  | 
            ||
| 398 | 		if(!is_null($path)) { | 
            ||
| 399 | return $path;  | 
            ||
| 400 | }  | 
            ||
| 401 | |||
| 402 | // early return path if it is a deleted user  | 
            ||
| 403 | $user = $this->access->userManager->get($uid);  | 
            ||
| 404 | 		if($user instanceof OfflineUser) { | 
            ||
| 405 | if($this->currentUserInDeletionProcess !== null  | 
            ||
| 406 | && $this->currentUserInDeletionProcess === $user->getOCName()  | 
            ||
| 407 | 			) { | 
            ||
| 408 | return $user->getHomePath();  | 
            ||
| 409 | 			} else { | 
            ||
| 410 | throw new NoUserException($uid . ' is not a valid user anymore');  | 
            ||
| 411 | }  | 
            ||
| 412 | 		} else if ($user === null) { | 
            ||
| 413 | throw new NoUserException($uid . ' is not a valid user anymore');  | 
            ||
| 414 | }  | 
            ||
| 415 | |||
| 416 | $path = $user->getHomePath();  | 
            ||
| 417 | $this->access->cacheUserHome($uid, $path);  | 
            ||
| 418 | |||
| 419 | return $path;  | 
            ||
| 420 | }  | 
            ||
| 421 | |||
| 422 | /**  | 
            ||
| 423 | * get display name of the user  | 
            ||
| 424 | * @param string $uid user ID of the user  | 
            ||
| 425 | * @return string|false display name  | 
            ||
| 426 | */  | 
            ||
| 427 | 	public function getDisplayName($uid) { | 
            ||
| 471 | |||
| 472 | /**  | 
            ||
| 473 | * Get a list of all display names  | 
            ||
| 474 | *  | 
            ||
| 475 | * @param string $search  | 
            ||
| 476 | * @param string|null $limit  | 
            ||
| 477 | * @param string|null $offset  | 
            ||
| 478 | * @return array an array of all displayNames (value) and the corresponding uids (key)  | 
            ||
| 479 | */  | 
            ||
| 480 | 	public function getDisplayNames($search = '', $limit = null, $offset = null) { | 
            ||
| 494 | |||
| 495 | /**  | 
            ||
| 496 | * Check if backend implements actions  | 
            ||
| 497 | * @param int $actions bitwise-or'ed actions  | 
            ||
| 498 | * @return boolean  | 
            ||
| 499 | *  | 
            ||
| 500 | * Returns the supported actions as int to be  | 
            ||
| 501 | * compared with \OC\User\Backend::CREATE_USER etc.  | 
            ||
| 502 | */  | 
            ||
| 503 | 	public function implementsActions($actions) { | 
            ||
| 512 | |||
| 513 | /**  | 
            ||
| 514 | * @return bool  | 
            ||
| 515 | */  | 
            ||
| 516 | 	public function hasUserListings() { | 
            ||
| 519 | |||
| 520 | /**  | 
            ||
| 521 | * counts the users in LDAP  | 
            ||
| 522 | *  | 
            ||
| 523 | * @return int|bool  | 
            ||
| 524 | */  | 
            ||
| 525 | 	public function countUsers() { | 
            ||
| 535 | |||
| 536 | /**  | 
            ||
| 537 | * Backend name to be shown in user management  | 
            ||
| 538 | * @return string the name of the backend to be shown  | 
            ||
| 539 | */  | 
            ||
| 540 | 	public function getBackendName(){ | 
            ||
| 543 | |||
| 544 | /**  | 
            ||
| 545 | * Return access for LDAP interaction.  | 
            ||
| 546 | * @param string $uid  | 
            ||
| 547 | * @return Access instance of Access for LDAP interaction  | 
            ||
| 548 | */  | 
            ||
| 549 | 	public function getLDAPAccess($uid) { | 
            ||
| 552 | |||
| 553 | /**  | 
            ||
| 554 | * Return LDAP connection resource from a cloned connection.  | 
            ||
| 555 | * The cloned connection needs to be closed manually.  | 
            ||
| 556 | * of the current access.  | 
            ||
| 557 | * @param string $uid  | 
            ||
| 558 | * @return resource of the LDAP connection  | 
            ||
| 559 | */  | 
            ||
| 560 | 	public function getNewLDAPConnection($uid) { | 
            ||
| 564 | }  | 
            ||
| 565 | 
Let’s take a look at an example:
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.
Available Fixes
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the interface: