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 |
||
20 | class User extends BaseUser |
||
21 | { |
||
22 | /** |
||
23 | * Set the value of [password] column. |
||
24 | * |
||
25 | * @param string $v new value |
||
26 | * |
||
27 | * @return $this|\User The current object (for fluent API support) |
||
28 | */ |
||
29 | public function setPassword($v) |
||
46 | |||
47 | // setPassword() |
||
48 | |||
49 | /** |
||
50 | * Check a plain text password against the value of [password] column. |
||
51 | * |
||
52 | * @param string $v plain text password |
||
53 | * |
||
54 | * @return $this|\User The current object (for fluent API support) |
||
55 | */ |
||
56 | public function checkPassword($v) |
||
66 | |||
67 | // checkPassword() |
||
68 | |||
69 | public function isAdmin() |
||
73 | |||
74 | /** |
||
75 | * Get the [firstname] and [lastname] column value concatenated with a space. |
||
76 | * |
||
77 | * @return string |
||
78 | */ |
||
79 | public function getName() |
||
83 | |||
84 | /** |
||
85 | * Get the URL for the user's profile image. |
||
86 | * |
||
87 | * @param string $size either 'small' or 'large' |
||
88 | * |
||
89 | * @return string |
||
90 | */ |
||
91 | public function getProfileImage($size) |
||
149 | |||
150 | /** |
||
151 | * Get array of roles currently assigned to the user. |
||
152 | * |
||
153 | * @return array of Role() objects |
||
154 | */ |
||
155 | public function getCurrentRoles() |
||
166 | |||
167 | /** |
||
168 | * Get object of upcoming events currently assigned to the user. |
||
169 | * |
||
170 | * @return array of Event() objects |
||
171 | */ |
||
172 | public function getUpcomingEvents() |
||
176 | |||
177 | /** |
||
178 | * Get object of upcoming events currently assigned to the user. |
||
179 | * |
||
180 | * @return array of Event() objects |
||
181 | */ |
||
182 | public function getRolesInEvent(Event $event) |
||
186 | |||
187 | public function getCurrentNotifications() |
||
191 | |||
192 | public function getUnreadNotifications() |
||
196 | |||
197 | /** |
||
198 | * Determine if the user is marked as available for an event. |
||
199 | * |
||
200 | * @return bool if user is available |
||
201 | */ |
||
202 | public function isAvailableForEvent(Event $event) |
||
215 | |||
216 | /** |
||
217 | * Determine if the user is marked as available for an event. |
||
218 | * |
||
219 | * @return \TechWilk\Rota\Availability |
||
220 | */ |
||
221 | public function getAvailabilityForEvent(Event $event) |
||
228 | |||
229 | public function getActiveCalendarTokens() |
||
236 | |||
237 | View Code Duplication | public function upcomingEventsAvailable() |
|
249 | |||
250 | View Code Duplication | public function upcomingEventsUnavailable() |
|
262 | |||
263 | public function upcomingEventsAwaitingResponse() |
||
299 | |||
300 | public function getInitials() |
||
311 | |||
312 | public function authoriser() |
||
316 | } |
||
317 |
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.