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 SqlUserRepository 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 SqlUserRepository, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
30 | final class SqlUserRepository implements UserRepository |
||
31 | { |
||
32 | const DATE_FORMAT = 'Y-m-d H:i:s'; |
||
33 | |||
34 | /** |
||
35 | * The pdo instance. |
||
36 | * |
||
37 | * @var \PDO |
||
38 | */ |
||
39 | private $pdo; |
||
40 | |||
41 | /** |
||
42 | * The user event bus, it can be null. |
||
43 | * |
||
44 | * @var UserEventBus|null |
||
45 | */ |
||
46 | private $eventBus; |
||
47 | |||
48 | /** |
||
49 | * Constructor. |
||
50 | * |
||
51 | * @param \PDO $aPdo The pdo instance |
||
52 | * @param UserEventBus|null $anEventBus The user event bus, it can be null |
||
53 | */ |
||
54 | public function __construct(\PDO $aPdo, UserEventBus $anEventBus = null) |
||
59 | |||
60 | /** |
||
61 | * {@inheritdoc} |
||
62 | */ |
||
63 | View Code Duplication | public function userOfId(UserId $anId) |
|
70 | |||
71 | /** |
||
72 | * {@inheritdoc} |
||
73 | */ |
||
74 | View Code Duplication | public function userOfEmail(UserEmail $anEmail) |
|
81 | |||
82 | /** |
||
83 | * {@inheritdoc} |
||
84 | */ |
||
85 | View Code Duplication | public function userOfConfirmationToken(UserToken $aConfirmationToken) |
|
94 | |||
95 | /** |
||
96 | * {@inheritdoc} |
||
97 | */ |
||
98 | View Code Duplication | public function userOfInvitationToken(UserToken $anInvitationToken) |
|
107 | |||
108 | /** |
||
109 | * {@inheritdoc} |
||
110 | */ |
||
111 | View Code Duplication | public function userOfRememberPasswordToken(UserToken $aRememberPasswordToken) |
|
120 | |||
121 | /** |
||
122 | * {@inheritdoc} |
||
123 | */ |
||
124 | public function persist(User $aUser) |
||
132 | |||
133 | /** |
||
134 | * {@inheritdoc} |
||
135 | */ |
||
136 | public function remove(User $aUser) |
||
144 | |||
145 | /** |
||
146 | * {@inheritdoc} |
||
147 | */ |
||
148 | public function size() |
||
152 | |||
153 | /** |
||
154 | * Loads the user schema into database create the table |
||
155 | * with user attribute properties as columns. |
||
156 | */ |
||
157 | public function initSchema() |
||
179 | |||
180 | /** |
||
181 | * Checks if the user given exists in the database. |
||
182 | * |
||
183 | * @param User $aUser The user |
||
184 | * |
||
185 | * @return bool |
||
186 | */ |
||
187 | private function exist(User $aUser) |
||
195 | |||
196 | /** |
||
197 | * Prepares the insert SQL with the user given. |
||
198 | * |
||
199 | * @param User $aUser The user |
||
200 | */ |
||
201 | private function insert(User $aUser) |
||
251 | |||
252 | /** |
||
253 | * Prepares the update SQL with the user given. |
||
254 | * |
||
255 | * @param User $aUser The user |
||
256 | */ |
||
257 | private function update(User $aUser) |
||
285 | |||
286 | /** |
||
287 | * Wrapper that encapsulates the same |
||
288 | * logic about execute the query in PDO. |
||
289 | * |
||
290 | * @param string $aSql The SQL |
||
291 | * @param array $parameters Array which contains the parameters of SQL |
||
292 | * |
||
293 | * @return \PDOStatement |
||
294 | */ |
||
295 | private function execute($aSql, array $parameters) |
||
302 | |||
303 | /** |
||
304 | * Builds the user with the given sql row attributes. |
||
305 | * |
||
306 | * @param array $row Array which contains attributes of user |
||
307 | * |
||
308 | * @return User |
||
309 | */ |
||
310 | private function buildUser($row) |
||
311 | { |
||
312 | $createdOn = new \DateTimeImmutable($row['created_on']); |
||
313 | $updatedOn = new \DateTimeImmutable($row['updated_on']); |
||
314 | $lastLogin = null === $row['last_login'] |
||
315 | ? null |
||
316 | : new \DateTimeImmutable($row['last_login']); |
||
317 | |||
318 | $confirmationToken = null; |
||
319 | View Code Duplication | if (null !== $row['confirmation_token_token']) { |
|
320 | $confirmationToken = new UserToken($row['confirmation_token_token']); |
||
321 | $this->set($confirmationToken, 'createdOn', new \DateTimeImmutable($row['confirmation_token_created_on'])); |
||
322 | } |
||
323 | $invitationToken = null; |
||
324 | View Code Duplication | if (null !== $row['invitation_token_token']) { |
|
325 | $invitationToken = new UserToken($row['invitation_token_token']); |
||
326 | $this->set($invitationToken, 'createdOn', new \DateTimeImmutable($row['invitation_token_created_on'])); |
||
327 | } |
||
328 | $rememberPasswordToken = null; |
||
329 | View Code Duplication | if (null !== $row['remember_password_token_token']) { |
|
330 | $rememberPasswordToken = new UserToken($row['remember_password_token_token']); |
||
331 | $this->set($rememberPasswordToken, 'createdOn', new \DateTimeImmutable($row['remember_password_token_created_on'])); |
||
332 | } |
||
333 | |||
334 | $user = User::signUp( |
||
335 | new UserId($row['id']), |
||
336 | new UserEmail($row['email']), |
||
337 | UserPassword::fromEncoded($row['password'], $row['salt']), |
||
338 | $this->rolesToArray($row['roles']) |
||
339 | ); |
||
340 | |||
341 | $user = $this->set($user, 'createdOn', $createdOn); |
||
342 | $user = $this->set($user, 'updatedOn', $updatedOn); |
||
343 | $user = $this->set($user, 'lastLogin', $lastLogin); |
||
344 | $user = $this->set($user, 'confirmationToken', $confirmationToken); |
||
345 | $user = $this->set($user, 'invitationToken', $invitationToken); |
||
346 | $user = $this->set($user, 'rememberPasswordToken', $rememberPasswordToken); |
||
347 | |||
348 | return $user; |
||
349 | } |
||
350 | |||
351 | /** |
||
352 | * Transforms given user roles into encoded plain json array. |
||
353 | * |
||
354 | * @param array $userRoles Array which contains the user roles |
||
355 | * |
||
356 | * @return string |
||
357 | */ |
||
358 | private function rolesToString(array $userRoles) |
||
366 | |||
367 | /** |
||
368 | * Transforms given user roles encoded array into user roles collection. |
||
369 | * |
||
370 | * @param array $userRoles Encoded json array |
||
371 | * |
||
372 | * @return UserRole[] |
||
373 | */ |
||
374 | private function rolesToArray($userRoles) |
||
380 | |||
381 | /** |
||
382 | * Populates by Reflection the domain object with the given SQL plain values. |
||
383 | * |
||
384 | * @param object $object The domain object |
||
385 | * @param string $propertyName The property name |
||
386 | * @param mixed $propertyValue The property value |
||
387 | * |
||
388 | * @return object |
||
389 | */ |
||
390 | private function set($object, $propertyName, $propertyValue) |
||
400 | |||
401 | /** |
||
402 | * Handles the given events with event bus. |
||
403 | * |
||
404 | * @param array $events A collection of user domain events |
||
405 | */ |
||
406 | private function handle($events) |
||
412 | } |
||
413 |
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.