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 Api 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 Api, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
31 | class Api |
||
32 | { |
||
33 | /** |
||
34 | * The authentication client. |
||
35 | * |
||
36 | * @var Authentication |
||
37 | */ |
||
38 | private $auth; |
||
39 | |||
40 | /** |
||
41 | * The constructor. |
||
42 | * |
||
43 | * @param Authentication $auth The authentication client. |
||
44 | */ |
||
45 | 36 | public function __construct(Authentication $auth) |
|
49 | |||
50 | /** |
||
51 | * Processes a response. |
||
52 | * |
||
53 | * @param Response $response The response object. |
||
54 | * @param callable $processor The response processor. |
||
55 | * |
||
56 | * @return Response The response |
||
57 | */ |
||
58 | 20 | private function processResponse(Response $response, $processor) |
|
67 | |||
68 | /** |
||
69 | * Execute the given http request. |
||
70 | * |
||
71 | * @param Request $request |
||
72 | * @param callable|null $processor |
||
73 | * |
||
74 | * @throws RateLimitedReached |
||
75 | * |
||
76 | * @return Response The response |
||
77 | */ |
||
78 | 22 | public function execute(Request $request, $processor = null) |
|
79 | 1 | { |
|
80 | 22 | $response = $this->auth->execute($request); |
|
81 | |||
82 | 22 | if ($response->rateLimited()) { |
|
83 | throw new RateLimitedReached($response); |
||
84 | } |
||
85 | |||
86 | 22 | if (is_callable($processor)) { |
|
87 | 20 | $response = $this->processResponse($response, $processor); |
|
88 | 10 | } |
|
89 | |||
90 | 22 | return $response; |
|
91 | } |
||
92 | |||
93 | /** |
||
94 | * Fetch a single user and processes the response. |
||
95 | * |
||
96 | * @param Request $request |
||
97 | * |
||
98 | * @return Response The response |
||
99 | */ |
||
100 | 4 | View Code Duplication | private function fetchUser(Request $request) |
110 | |||
111 | /** |
||
112 | * Fetch a single board and processes the response. |
||
113 | * |
||
114 | * @param Request $request |
||
115 | * |
||
116 | * @throws RateLimitedReached |
||
117 | * |
||
118 | * @return Response The response |
||
119 | */ |
||
120 | 4 | View Code Duplication | private function fetchBoard(Request $request) |
130 | |||
131 | /** |
||
132 | * Fetch a single pin and processes the response. |
||
133 | * |
||
134 | * @param Request $request |
||
135 | * |
||
136 | * @throws RateLimitedReached |
||
137 | * |
||
138 | * @return Response The response |
||
139 | */ |
||
140 | View Code Duplication | private function fetchPin(Request $request) |
|
141 | { |
||
142 | $request->setFields(Pin::fields()); |
||
143 | |||
144 | return $this->execute($request, function (Response $response) { |
||
145 | $mapper = new Mapper(new Pin()); |
||
146 | |||
147 | return $mapper->toSingle($response); |
||
148 | }); |
||
149 | } |
||
150 | |||
151 | /** |
||
152 | * Fetch multiple boards and processes the response. |
||
153 | * |
||
154 | * @param Request $request |
||
155 | * @param string[] $fields |
||
156 | * |
||
157 | * @throws RateLimitedReached |
||
158 | * |
||
159 | * @return Response The response |
||
160 | */ |
||
161 | 6 | View Code Duplication | private function fetchMultipleBoards(Request $request, array $fields = null) |
172 | |||
173 | /** |
||
174 | * Fetch multiple users and processes the response. |
||
175 | * |
||
176 | * @param Request $request |
||
177 | * |
||
178 | * @throws RateLimitedReached |
||
179 | * |
||
180 | * @return Response The response |
||
181 | */ |
||
182 | 4 | View Code Duplication | private function fetchMultipleUsers(Request $request) |
192 | |||
193 | /** |
||
194 | * Fetches multiple pins and processes the response. |
||
195 | * |
||
196 | * @param Request $request |
||
197 | * @param $fields array The fields to require. |
||
198 | * |
||
199 | * @throws RateLimitedReached |
||
200 | * |
||
201 | * @return Response The response |
||
202 | */ |
||
203 | 2 | View Code Duplication | private function fetchMultiplePins(Request $request, array $fields = null) |
214 | |||
215 | /** |
||
216 | * Get a user. |
||
217 | * |
||
218 | * @param string $usernameOrId The username or identifier of the user. |
||
219 | * |
||
220 | * @return Response The response |
||
221 | */ |
||
222 | 4 | public function getUser($usernameOrId) |
|
232 | |||
233 | /** |
||
234 | * Get a board. |
||
235 | * |
||
236 | * @param string $boardId The board id. |
||
237 | * |
||
238 | * @return Response The response |
||
239 | */ |
||
240 | 2 | public function getBoard($boardId) |
|
250 | |||
251 | /** |
||
252 | * Update a board. |
||
253 | * |
||
254 | * @param Board $board The updated board. |
||
255 | * |
||
256 | * @return Response The response |
||
257 | */ |
||
258 | 2 | public function updateBoard(Board $board) |
|
278 | |||
279 | /** |
||
280 | * Get the boards of the authenticated user. |
||
281 | * |
||
282 | * @return Response The response |
||
283 | */ |
||
284 | 2 | public function getUserBoards() |
|
290 | |||
291 | /** |
||
292 | * Get the pins of the authenticated user. |
||
293 | * |
||
294 | * @return Response The response |
||
295 | */ |
||
296 | 2 | public function getUserPins() |
|
302 | |||
303 | /** |
||
304 | * Get the authenticated user. |
||
305 | * |
||
306 | * @return Response The response |
||
307 | */ |
||
308 | 2 | public function getCurrentUser() |
|
314 | |||
315 | /** |
||
316 | * Get the followers of the authenticated user. |
||
317 | * |
||
318 | * @return Response The response |
||
319 | */ |
||
320 | 2 | public function getUserFollowers() |
|
326 | |||
327 | /** |
||
328 | * Get the boards that the authenticated user follows. |
||
329 | * |
||
330 | * @return Response The response |
||
331 | */ |
||
332 | 2 | public function getUserFollowingBoards() |
|
338 | |||
339 | /** |
||
340 | * Get the users that the authenticated user follows. |
||
341 | * |
||
342 | * @return Response The response |
||
343 | */ |
||
344 | 2 | public function getUserFollowing() |
|
350 | |||
351 | /** |
||
352 | * Get the interests that the authenticated user follows. |
||
353 | * |
||
354 | * @link https://www.pinterest.com/explore/901179409185 |
||
355 | * |
||
356 | * @return Response The response |
||
357 | */ |
||
358 | 2 | public function getUserInterests() |
|
364 | |||
365 | /** |
||
366 | * Follow a user. |
||
367 | * |
||
368 | * @param string $username The username of the user to follow. |
||
369 | * |
||
370 | * @return Response The response |
||
371 | */ |
||
372 | 2 | View Code Duplication | public function followUser($username) |
388 | |||
389 | /** |
||
390 | * Create a board. |
||
391 | * |
||
392 | * @param string $name The board name. |
||
393 | * @param string $description The board description. |
||
394 | * |
||
395 | * @return Response The response |
||
396 | */ |
||
397 | 2 | public function createBoard($name, $description = null) |
|
415 | |||
416 | /** |
||
417 | * Delete a board. |
||
418 | * |
||
419 | * @param int $boardId The board id. |
||
420 | * |
||
421 | * @return Response The response |
||
422 | */ |
||
423 | 2 | View Code Duplication | public function deleteBoard($boardId) |
433 | |||
434 | /** |
||
435 | * Create a pin on a board. |
||
436 | * |
||
437 | * @param string $boardId The board id. |
||
438 | * @param string $note The note. |
||
439 | * @param Image $image The image. |
||
440 | * @param string|null $link The link (Optional). |
||
441 | * |
||
442 | * @return Response The response |
||
443 | */ |
||
444 | 8 | public function createPin($boardId, $note, Image $image, $link = null) |
|
445 | { |
||
446 | 8 | if (empty($boardId)) { |
|
447 | 8 | throw new InvalidArgumentException('The board id should not be empty.'); |
|
448 | } |
||
449 | |||
450 | if (empty($note)) { |
||
451 | throw new InvalidArgumentException('The note should not be empty.'); |
||
452 | } |
||
453 | |||
454 | $params = array( |
||
455 | 'board' => $boardId, |
||
456 | 'note' => (string) $note, |
||
457 | ); |
||
458 | |||
459 | if (!empty($link)) { |
||
460 | $params['link'] = (string) $link; |
||
461 | } |
||
462 | |||
463 | $imageKey = $image->isUrl() ? 'image_url' : ($image->isBase64() ? 'image_base64' : 'image'); |
||
464 | |||
465 | if ($image->isFile()) { |
||
466 | $params[$imageKey] = $image; |
||
467 | } else { |
||
468 | $params[$imageKey] = $image->getData(); |
||
469 | } |
||
470 | |||
471 | $request = new Request('POST', 'pins/', $params); |
||
472 | |||
473 | return $this->fetchPin($request); |
||
474 | } |
||
475 | |||
476 | /** |
||
477 | * Delete a Pin. |
||
478 | * |
||
479 | * @param string $pinId The id of the pin to delete. |
||
480 | * |
||
481 | * @return Response The response |
||
482 | */ |
||
483 | View Code Duplication | public function deletePin($pinId) |
|
484 | { |
||
485 | if (empty($pinId)) { |
||
486 | throw new InvalidArgumentException('The pin id should not be empty.'); |
||
487 | } |
||
488 | |||
489 | $request = new Request('DELETE', sprintf('pins/%d/', $pinId)); |
||
490 | |||
491 | return $this->execute($request); |
||
492 | } |
||
493 | |||
494 | /** |
||
495 | * Get the next items for a paged list. |
||
496 | * |
||
497 | * @param PagedList $pagedList |
||
498 | * |
||
499 | * @return Response The response |
||
500 | */ |
||
501 | 2 | public function getNextItems(PagedList $pagedList) |
|
502 | { |
||
503 | 2 | if (!$pagedList->hasNext()) { |
|
504 | 2 | throw new InvalidArgumentException('The list has no more items'); |
|
505 | } |
||
506 | |||
507 | $items = $pagedList->items(); |
||
508 | |||
509 | if (empty($items)) { |
||
510 | throw new InvalidArgumentException( |
||
511 | 'Unable to detect object type because the list contains no items' |
||
512 | ); |
||
513 | } |
||
514 | |||
515 | $item = reset($items); |
||
516 | $objectClassName = get_class($item); |
||
517 | $objectInstance = new $objectClassName(); |
||
518 | |||
519 | $request = $this->buildRequestForPagedList($pagedList); |
||
520 | |||
521 | return $this->execute($request, function (Response $response) use ($objectInstance) { |
||
522 | $mapper = new Mapper($objectInstance); |
||
523 | |||
524 | return $mapper->toList($response); |
||
525 | }); |
||
526 | } |
||
527 | |||
528 | /** |
||
529 | * Build a request to get the next items of a paged list. |
||
530 | * |
||
531 | * @param PagedList $pagedList |
||
532 | * |
||
533 | * @return Request |
||
534 | */ |
||
535 | private function buildRequestForPagedList(PagedList $pagedList) |
||
550 | |||
551 | /** |
||
552 | * Get the pins of a board. |
||
553 | * |
||
554 | * @param string $boardId |
||
555 | * |
||
556 | * @return Response The response |
||
557 | */ |
||
558 | 2 | public function getBoardPins($boardId) |
|
569 | } |
||
570 |
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.