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 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, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
45 | class User extends ActiveRecord implements yii\web\IdentityInterface |
||
46 | { |
||
47 | use CommonTrait; |
||
48 | |||
49 | /** Male gender */ |
||
50 | const MALE = 1; |
||
51 | /** Female gender */ |
||
52 | const FEMALE = 2; |
||
53 | |||
54 | /** Active user status */ |
||
55 | const STATUS_ACTIVE = 1; |
||
56 | /** Blocked user status */ |
||
57 | const STATUS_BLOCKED = 2; |
||
58 | /** User await email confirmation status */ |
||
59 | const STATUS_CONFIRM = 3; |
||
60 | /** User await access restore status */ |
||
61 | const STATUS_RESTORE = 4; |
||
62 | |||
63 | // TODO where store password between data enter and confirmation email? Password must be send with congratulation email |
||
64 | /** @var string password field on registration on restore */ |
||
65 | public $password; |
||
66 | |||
67 | /** |
||
68 | * @inheritdoc |
||
69 | */ |
||
70 | 16 | public static function tableName() |
|
74 | |||
75 | /** |
||
76 | * @inheritdoc |
||
77 | * @return UserQuery the active query used by this AR class. |
||
78 | */ |
||
79 | 15 | public static function find() |
|
84 | |||
85 | /** |
||
86 | * Finds an identity by the given ID. |
||
87 | * @param string|integer $id the ID to be looked for |
||
88 | * @return IdentityInterface the identity object that matches the given ID. |
||
89 | * Null should be returned if such an identity cannot be found |
||
90 | * or the identity is not in an active state (disabled, deleted, etc.) |
||
91 | */ |
||
92 | 2 | public static function findIdentity($id) |
|
96 | |||
97 | /** |
||
98 | * Find user by token |
||
99 | * @param string $token for search |
||
100 | * @return null|static |
||
101 | */ |
||
102 | 3 | public static function findByToken($token) |
|
106 | |||
107 | /** |
||
108 | * Finds an identity by the given token. |
||
109 | * @param mixed $token the token to be looked for |
||
110 | * @param mixed $type the type of the token. The value of this parameter depends on the implementation. |
||
111 | * For example, [[\yii\filters\auth\HttpBearerAuth]] will set this parameter to be `yii\filters\auth\HttpBearerAuth`. |
||
112 | * @return IdentityInterface the identity object that matches the given token. |
||
113 | * Null should be returned if such an identity cannot be found |
||
114 | * or the identity is not in an active state (disabled, deleted, etc.) |
||
115 | */ |
||
116 | 1 | public static function findIdentityByAccessToken($token, $type = null) |
|
123 | |||
124 | /** |
||
125 | * Confirm user registration |
||
126 | * @return bool return false if cannot confirm. |
||
127 | * If in errors list has key `error`, that user already confirmed |
||
128 | * If in errors list has key `token`, that confirm token was expired |
||
129 | */ |
||
130 | 3 | public function confirm() |
|
150 | |||
151 | /** |
||
152 | * Check that user was confirmed |
||
153 | * @return bool |
||
154 | */ |
||
155 | 6 | public function isConfirmed() |
|
159 | |||
160 | /** |
||
161 | * Checks that the token was expired |
||
162 | * @param int $timeToExpire time |
||
163 | * @return bool |
||
164 | */ |
||
165 | 3 | public function isTokenExpired($timeToExpire) |
|
169 | |||
170 | /** |
||
171 | * Checks that the confirmation token was expired |
||
172 | * @return bool |
||
173 | */ |
||
174 | 3 | public function isConfirmTokenExpired() |
|
178 | |||
179 | /** |
||
180 | * Checks that the restore token was expired |
||
181 | * @return bool |
||
182 | */ |
||
183 | 1 | public function isRestoreTokenExpired() |
|
187 | |||
188 | /** |
||
189 | * @inheritdoc |
||
190 | */ |
||
191 | 11 | public function rules() |
|
233 | |||
234 | /** |
||
235 | * @inheritdoc |
||
236 | */ |
||
237 | 1 | public function attributeLabels() |
|
260 | |||
261 | /** |
||
262 | * Get user profile |
||
263 | * @return yii\db\ActiveQuery |
||
264 | */ |
||
265 | 1 | public function getProfile() |
|
269 | |||
270 | /** |
||
271 | * @inheritdoc |
||
272 | */ |
||
273 | 11 | public function beforeSave($insert) |
|
283 | |||
284 | /** |
||
285 | * @inheritdoc |
||
286 | */ |
||
287 | 10 | public function afterSave($insert, $changedAttributes) |
|
298 | |||
299 | /** |
||
300 | * @inheritdoc |
||
301 | */ |
||
302 | 11 | public function transactions() |
|
308 | |||
309 | /** |
||
310 | * Returns an ID that can uniquely identify a user identity. |
||
311 | * @return string|integer an ID that uniquely identifies a user identity. |
||
312 | */ |
||
313 | 2 | public function getId() |
|
317 | |||
318 | /** |
||
319 | * Validates the given auth key. |
||
320 | * |
||
321 | * This is required if [[User::enableAutoLogin]] is enabled. |
||
322 | * @param string $authKey the given auth key |
||
323 | * @return boolean whether the given auth key is valid. |
||
324 | * @see getAuthKey() |
||
325 | */ |
||
326 | 1 | public function validateAuthKey($authKey) |
|
331 | |||
332 | /** |
||
333 | * Returns a key that can be used to check the validity of a given identity ID. |
||
334 | * |
||
335 | * The key should be unique for each individual user, and should be persistent |
||
336 | * so that it can be used to check the validity of the user identity. |
||
337 | * |
||
338 | * The space of such keys should be big enough to defeat potential identity attacks. |
||
339 | * |
||
340 | * This is required if [[User::enableAutoLogin]] is enabled. |
||
341 | * @return string a key that is used to check the validity of a given identity ID. |
||
342 | * @see validateAuthKey() |
||
343 | */ |
||
344 | 1 | public function getAuthKey() |
|
348 | |||
349 | /** |
||
350 | * Check active user |
||
351 | * @return bool |
||
352 | */ |
||
353 | 2 | public function isActive() |
|
357 | |||
358 | /** |
||
359 | * User creation |
||
360 | * For create the user you always must set attributes `email`, `password` and `name` |
||
361 | * @param bool $sendEmail whether to send email about registration |
||
362 | * @return bool |
||
363 | */ |
||
364 | 1 | public function create($sendEmail = false) |
|
394 | |||
395 | /** |
||
396 | * User registration |
||
397 | * @return bool |
||
398 | */ |
||
399 | 8 | public function register() |
|
429 | |||
430 | /** |
||
431 | * Password generator |
||
432 | * @return mixed |
||
433 | */ |
||
434 | 7 | public function generatePassword() |
|
438 | |||
439 | /** |
||
440 | * Generate special hash |
||
441 | * @return string |
||
442 | */ |
||
443 | 8 | public function generateToken() |
|
451 | |||
452 | /** |
||
453 | * Start password restore procedure |
||
454 | * @return bool |
||
455 | */ |
||
456 | 1 | public function restore() |
|
475 | |||
476 | /** |
||
477 | * Check blocked user |
||
478 | * @return bool |
||
479 | */ |
||
480 | 4 | public function isBlocked() |
|
484 | |||
485 | /** |
||
486 | * Block user |
||
487 | * @param bool $sendMail whether to send confirmation email about blocking. |
||
488 | * If null, use global setting Module::$enableBlockingEmail |
||
489 | * @return bool return false if user already blocked or not confirmed |
||
490 | */ |
||
491 | 1 | View Code Duplication | public function block($sendMail = null) |
509 | |||
510 | /** |
||
511 | * Unblock user |
||
512 | * @param bool $sendMail whether to send confirmation email about unblocking. |
||
513 | * If null, use global setting Module::$enableUnblockingEmail |
||
514 | * @return bool return false if user not blocked |
||
515 | */ |
||
516 | 1 | View Code Duplication | public function unblock($sendMail = null) |
534 | |||
535 | /** |
||
536 | * Change the user password |
||
537 | * @return bool |
||
538 | */ |
||
539 | 1 | public function changePassword() |
|
557 | |||
558 | /** |
||
559 | * Set new password |
||
560 | * @param bool $sendEmail whether to send email about password change |
||
561 | * @throws yii\base\Exception |
||
562 | * @throws yii\base\InvalidConfigException |
||
563 | */ |
||
564 | 1 | public function newPassword($sendEmail = null) |
|
584 | |||
585 | /** |
||
586 | * Check that user request restore |
||
587 | * @return bool |
||
588 | */ |
||
589 | 2 | public function isRestore() |
|
593 | |||
594 | /** |
||
595 | * Get user status as text |
||
596 | * @param string $status status value. If not set, get from model |
||
597 | * @return null|string |
||
598 | */ |
||
599 | public function getStatusText($status = null) |
||
607 | |||
608 | /** |
||
609 | * Get status list as array |
||
610 | * @return array |
||
611 | */ |
||
612 | static public function statusesList() |
||
621 | |||
622 | /** |
||
623 | * Get user gender as text |
||
624 | * @param int $gender gender value. If not set, get from model |
||
625 | * @return null|string |
||
626 | */ |
||
627 | public function getGenderText($gender = null) |
||
635 | |||
636 | /** |
||
637 | * Get gender list as array |
||
638 | * @return array |
||
639 | */ |
||
640 | static public function gendersList() |
||
648 | |||
649 | /** |
||
650 | * Resend confirmation message |
||
651 | */ |
||
652 | public function resend() |
||
661 | |||
662 | /** |
||
663 | * @inheritdoc |
||
664 | */ |
||
665 | 15 | public function behaviors() |
|
676 | } |
||
677 |
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_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.