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 AbstractUser 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 AbstractUser, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
23 | abstract class AbstractUser extends Content implements |
||
24 | UserInterface, |
||
25 | ConfigurableInterface |
||
26 | { |
||
27 | use ConfigurableTrait; |
||
28 | |||
29 | /** |
||
30 | * @var UserInterface $authenticatedUser |
||
31 | */ |
||
32 | protected static $authenticatedUser; |
||
33 | |||
34 | /** |
||
35 | * The username should be unique and mandatory. |
||
36 | * |
||
37 | * It is also used as the login name and main identifier (key). |
||
38 | * |
||
39 | * @var string |
||
40 | */ |
||
41 | private $username = ''; |
||
42 | |||
43 | /** |
||
44 | * The password is stored encrypted in the (database) storage. |
||
45 | * |
||
46 | * @var string|null |
||
47 | */ |
||
48 | private $password; |
||
49 | |||
50 | /** |
||
51 | * The email address associated with the account. |
||
52 | * |
||
53 | * @var string |
||
54 | */ |
||
55 | private $email; |
||
56 | |||
57 | /** |
||
58 | * Roles define a set of tasks a user is allowed or denied from performing. |
||
59 | * |
||
60 | * @var string[] |
||
61 | */ |
||
62 | private $roles = []; |
||
63 | |||
64 | /** |
||
65 | * The timestamp of the latest (successful) login. |
||
66 | * |
||
67 | * @var DateTimeInterface|null |
||
68 | */ |
||
69 | private $lastLoginDate; |
||
70 | |||
71 | /** |
||
72 | * The IP address during the latest (successful) login. |
||
73 | * |
||
74 | * @var string|null |
||
75 | */ |
||
76 | private $lastLoginIp; |
||
77 | |||
78 | /** |
||
79 | * The timestamp of the latest password change. |
||
80 | * |
||
81 | * @var DateTimeInterface|null |
||
82 | */ |
||
83 | private $lastPasswordDate; |
||
84 | |||
85 | /** |
||
86 | * The IP address during the latest password change. |
||
87 | * |
||
88 | * @var string|null |
||
89 | */ |
||
90 | private $lastPasswordIp; |
||
91 | |||
92 | /** |
||
93 | * Tracks the password reset token. |
||
94 | * |
||
95 | * If the token is set (not empty), then the user should be prompted |
||
96 | * to reset his password after login / enter the token to continue. |
||
97 | * |
||
98 | * @var string|null |
||
99 | */ |
||
100 | private $loginToken = ''; |
||
101 | |||
102 | /** |
||
103 | * @see \Charcoal\Source\StorableTrait::key() |
||
104 | * @return string |
||
105 | */ |
||
106 | public function key() |
||
110 | |||
111 | /** |
||
112 | * Force a lowercase username |
||
113 | * |
||
114 | * @param string $username The username (also the login name). |
||
115 | * @throws InvalidArgumentException If the username is not a string. |
||
116 | * @return UserInterface Chainable |
||
117 | */ |
||
118 | View Code Duplication | public function setUsername($username) |
|
130 | |||
131 | /** |
||
132 | * @return string |
||
133 | */ |
||
134 | public function username() |
||
138 | |||
139 | /** |
||
140 | * @param string $email The user email. |
||
141 | * @throws InvalidArgumentException If the email is not a string. |
||
142 | * @return UserInterface Chainable |
||
143 | */ |
||
144 | public function setEmail($email) |
||
156 | |||
157 | /** |
||
158 | * @return string |
||
159 | */ |
||
160 | public function email() |
||
164 | |||
165 | /** |
||
166 | * @param string|null $password The user password. Encrypted in storage. |
||
167 | * @throws InvalidArgumentException If the password is not a string (or null, to reset). |
||
168 | * @return UserInterface Chainable |
||
169 | */ |
||
170 | public function setPassword($password) |
||
184 | |||
185 | /** |
||
186 | * @return string|null |
||
187 | */ |
||
188 | public function password() |
||
192 | |||
193 | /** |
||
194 | * @param string|string[]|null $roles The ACL roles this user belongs to. |
||
195 | * @throws InvalidArgumentException If the roles argument is invalid. |
||
196 | * @return UserInterface Chainable |
||
197 | */ |
||
198 | public function setRoles($roles) |
||
219 | |||
220 | /** |
||
221 | * @return string[] |
||
222 | */ |
||
223 | public function roles() |
||
227 | |||
228 | /** |
||
229 | * @param string|DateTimeInterface|null $lastLoginDate The last login date. |
||
230 | * @throws InvalidArgumentException If the ts is not a valid date/time. |
||
231 | * @return UserInterface Chainable |
||
232 | */ |
||
233 | View Code Duplication | public function setLastLoginDate($lastLoginDate) |
|
261 | |||
262 | /** |
||
263 | * @return DateTimeInterface|null |
||
264 | */ |
||
265 | public function lastLoginDate() |
||
269 | |||
270 | /** |
||
271 | * @param string|integer|null $ip The last login IP address. |
||
272 | * @throws InvalidArgumentException If the IP is not an IP string, an integer, or null. |
||
273 | * @return UserInterface Chainable |
||
274 | */ |
||
275 | View Code Duplication | public function setLastLoginIp($ip) |
|
296 | |||
297 | /** |
||
298 | * Get the last login IP in x.x.x.x format |
||
299 | * |
||
300 | * @return string|null |
||
301 | */ |
||
302 | public function lastLoginIp() |
||
306 | |||
307 | /** |
||
308 | * @param string|DateTimeInterface|null $lastPasswordDate The last password date. |
||
309 | * @throws InvalidArgumentException If the passsword date is not a valid DateTime. |
||
310 | * @return UserInterface Chainable |
||
311 | */ |
||
312 | View Code Duplication | public function setLastPasswordDate($lastPasswordDate) |
|
340 | |||
341 | /** |
||
342 | * @return DateTimeInterface|null |
||
343 | */ |
||
344 | public function lastPasswordDate() |
||
348 | |||
349 | /** |
||
350 | * @param integer|string|null $ip The last password IP. |
||
351 | * @throws InvalidArgumentException If the IP is not null, an integer or an IP string. |
||
352 | * @return UserInterface Chainable |
||
353 | */ |
||
354 | View Code Duplication | public function setLastPasswordIp($ip) |
|
375 | |||
376 | /** |
||
377 | * Get the last password change IP in x.x.x.x format |
||
378 | * |
||
379 | * @return string|null |
||
380 | */ |
||
381 | public function lastPasswordIp() |
||
385 | |||
386 | /** |
||
387 | * @param string|null $token The login token. |
||
388 | * @throws InvalidArgumentException If the token is not a string. |
||
389 | * @return UserInterface Chainable |
||
390 | */ |
||
391 | public function setLoginToken($token) |
||
408 | |||
409 | /** |
||
410 | * @return string|null |
||
411 | */ |
||
412 | public function loginToken() |
||
416 | |||
417 | /** |
||
418 | * @throws Exception If trying to save a user to session without a ID. |
||
419 | * @return UserInterface Chainable |
||
420 | */ |
||
421 | public function saveToSession() |
||
433 | |||
434 | /** |
||
435 | * Log in the user (in session) |
||
436 | * |
||
437 | * Called when the authentication is successful. |
||
438 | * |
||
439 | * @return boolean Success / Failure |
||
440 | */ |
||
441 | public function login() |
||
459 | |||
460 | /** |
||
461 | * Empties the session var associated to the session key. |
||
462 | * |
||
463 | * @return boolean Logged out or not. |
||
464 | */ |
||
465 | public function logout() |
||
479 | |||
480 | /** |
||
481 | * Reset the password. |
||
482 | * |
||
483 | * Encrypt the password and re-save the object in the database. |
||
484 | * Also updates the last password date & ip. |
||
485 | * |
||
486 | * @param string $plainPassword The plain (non-encrypted) password to reset to. |
||
487 | * @throws InvalidArgumentException If the plain password is not a string. |
||
488 | * @return UserInterface Chainable |
||
489 | */ |
||
490 | public function resetPassword($plainPassword) |
||
513 | |||
514 | /** |
||
515 | * Get the currently authenticated user (from session) |
||
516 | * |
||
517 | * Return null if there is no current user in logged into |
||
518 | * |
||
519 | * @param FactoryInterface $factory The factory to create the user object with. |
||
520 | * @throws Exception If the user from session is invalid. |
||
521 | * @return UserInterface|null |
||
522 | */ |
||
523 | public static function getAuthenticated(FactoryInterface $factory) |
||
553 | } |
||
554 |
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.