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 Client 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 Client, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | final class Client implements ClientInterface |
||
18 | { |
||
19 | /** |
||
20 | * Flag to cache no requests |
||
21 | * |
||
22 | * @const int |
||
23 | */ |
||
24 | const CACHE_MODE_NONE = 0; |
||
25 | |||
26 | /** |
||
27 | * Flag to cache only GET requests |
||
28 | * |
||
29 | * @const int |
||
30 | */ |
||
31 | const CACHE_MODE_GET = 1; |
||
32 | |||
33 | /** |
||
34 | * Flag to cache only TOKEN requests |
||
35 | * |
||
36 | * @const int |
||
37 | */ |
||
38 | const CACHE_MODE_TOKEN = 2; |
||
39 | |||
40 | /** |
||
41 | * Flag to cache ALL requests |
||
42 | * |
||
43 | * @const int |
||
44 | */ |
||
45 | const CACHE_MODE_ALL = 3; |
||
46 | |||
47 | /** |
||
48 | * Flag to refresh cache on ALL requests |
||
49 | * |
||
50 | * @const int |
||
51 | */ |
||
52 | const CACHE_MODE_REFRESH = 4; |
||
53 | |||
54 | /** |
||
55 | * @var array |
||
56 | */ |
||
57 | const CACHE_MODES = [ |
||
58 | self::CACHE_MODE_NONE, |
||
59 | self::CACHE_MODE_GET, |
||
60 | self::CACHE_MODE_TOKEN, |
||
61 | self::CACHE_MODE_ALL, |
||
62 | self::CACHE_MODE_REFRESH, |
||
63 | ]; |
||
64 | |||
65 | /** |
||
66 | * @var string |
||
67 | */ |
||
68 | private $baseUrl; |
||
69 | |||
70 | /** |
||
71 | * @var AdapterInterface |
||
72 | */ |
||
73 | private $adapter; |
||
74 | |||
75 | /** |
||
76 | * @var Authentication |
||
77 | */ |
||
78 | private $authentication; |
||
79 | |||
80 | /** |
||
81 | * @var string |
||
82 | */ |
||
83 | private $accessToken; |
||
84 | |||
85 | /** |
||
86 | * @var string |
||
87 | */ |
||
88 | private $refreshToken; |
||
89 | |||
90 | /** |
||
91 | * @var CacheInterface |
||
92 | */ |
||
93 | private $cache; |
||
94 | |||
95 | /** |
||
96 | * @var int |
||
97 | */ |
||
98 | private $cacheMode; |
||
99 | |||
100 | /** |
||
101 | * Handles set in start() |
||
102 | * |
||
103 | * @var array like [opaqueKey => [cached response (Response), adapter handle (opaque), Request]] |
||
104 | */ |
||
105 | private $handles = []; |
||
106 | |||
107 | /** |
||
108 | * Array of headers that are passed on every request unless they are overridden |
||
109 | * |
||
110 | * @var array |
||
111 | */ |
||
112 | private $defaultHeaders = []; |
||
113 | |||
114 | /** |
||
115 | * Create a new instance of Client |
||
116 | * |
||
117 | * @param AdapterInterface $adapter HTTP Adapter for sending request to the api |
||
118 | * @param Authentication $authentication Oauth authentication implementation |
||
119 | * @param string $baseUrl Base url of the API server |
||
120 | * @param int $cacheMode Strategy for caching |
||
121 | * @param CacheInterface $cache Storage for cached API responses |
||
122 | * @param string $accessToken API access token |
||
123 | * @param string $refreshToken API refresh token |
||
124 | * |
||
125 | * @throws \InvalidArgumentException Thrown if $baseUrl is not a non-empty string |
||
126 | * @throws \InvalidArgumentException Thrown if $cacheMode is not one of the cache mode constants |
||
127 | */ |
||
128 | public function __construct( |
||
152 | |||
153 | /** |
||
154 | * Get access token and refresh token |
||
155 | * |
||
156 | * @return array two string values, access token and refresh token |
||
157 | */ |
||
158 | public function getTokens() : array |
||
162 | |||
163 | /** |
||
164 | * Search the API resource using the specified $filters |
||
165 | * |
||
166 | * @param string $resource |
||
167 | * @param array $filters |
||
168 | * |
||
169 | * @return string opaque handle to be given to endIndex() |
||
170 | */ |
||
171 | public function startIndex(string $resource, array $filters = []) : string |
||
176 | |||
177 | /** |
||
178 | * @see startIndex() |
||
179 | */ |
||
180 | public function index(string $resource, array $filters = []) : Response |
||
184 | |||
185 | /** |
||
186 | * Get the details of an API resource based on $id |
||
187 | * |
||
188 | * @param string $resource |
||
189 | * @param string $id |
||
190 | * @param array $parameters |
||
191 | * |
||
192 | * @return string opaque handle to be given to endGet() |
||
193 | */ |
||
194 | public function startGet(string $resource, string $id, array $parameters = []) : string |
||
203 | |||
204 | /** |
||
205 | * @see startGet() |
||
206 | */ |
||
207 | public function get(string $resource, string $id, array $parameters = []) : Response |
||
211 | |||
212 | /** |
||
213 | * Create a new instance of an API resource using the provided $data |
||
214 | * |
||
215 | * @param string $resource |
||
216 | * @param array $data |
||
217 | * |
||
218 | * @return string opaque handle to be given to endPost() |
||
219 | */ |
||
220 | public function startPost(string $resource, array $data) : string |
||
225 | |||
226 | /** |
||
227 | * @see startPost() |
||
228 | */ |
||
229 | public function post(string $resource, array $data) : Response |
||
233 | |||
234 | /** |
||
235 | * Update an existing instance of an API resource specified by $id with the provided $data |
||
236 | * |
||
237 | * @param string $resource |
||
238 | * @param string $id |
||
239 | * @param array $data |
||
240 | * |
||
241 | * @return string opaque handle to be given to endPut() |
||
242 | */ |
||
243 | public function startPut(string $resource, string $id, array $data) : string |
||
248 | |||
249 | /** |
||
250 | * @see startPut() |
||
251 | */ |
||
252 | public function put(string $resource, string $id, array $data) : Response |
||
256 | |||
257 | /** |
||
258 | * Delete an existing instance of an API resource specified by $id |
||
259 | * |
||
260 | * @param string $resource |
||
261 | * @param string $id |
||
262 | * @param array $data |
||
263 | * |
||
264 | * @return string opaque handle to be given to endDelete() |
||
265 | */ |
||
266 | public function startDelete(string $resource, string $id = null, array $data = null) : string |
||
276 | |||
277 | /** |
||
278 | * @see startDelete() |
||
279 | */ |
||
280 | public function delete(string $resource, string $id = null, array $data = null) : Response |
||
284 | |||
285 | /** |
||
286 | * Get response of start*() method |
||
287 | * |
||
288 | * @param string $handle opaque handle from start*() |
||
289 | * |
||
290 | * @return Response |
||
291 | */ |
||
292 | public function end(string $handle) : Response |
||
331 | |||
332 | /** |
||
333 | * Set the default headers |
||
334 | * |
||
335 | * @param array The default headers |
||
336 | * |
||
337 | * @return void |
||
338 | */ |
||
339 | public function setDefaultHeaders(array $defaultHeaders) |
||
343 | |||
344 | private static function isExpiredToken(ResponseInterface $response) : bool |
||
371 | |||
372 | /** |
||
373 | * Obtains a new access token from the API |
||
374 | * |
||
375 | * @return void |
||
376 | */ |
||
377 | private function refreshAccessToken() |
||
389 | |||
390 | /** |
||
391 | * Helper method to set this clients access token from cache |
||
392 | * |
||
393 | * @return void |
||
394 | */ |
||
395 | private function setTokenFromCache() |
||
410 | |||
411 | /** |
||
412 | * Calls adapter->start() using caches |
||
413 | * |
||
414 | * @param string $url |
||
415 | * @param string $method |
||
416 | * @param string|null $body |
||
417 | * @param array $headers Authorization key will be overwritten with the bearer token, and Accept-Encoding wil be |
||
418 | * overwritten with gzip. |
||
419 | * |
||
420 | * @return string opaque handle to be given to end() |
||
421 | */ |
||
422 | private function start(string $url, string $method, string $body = null, array $headers = []) |
||
452 | |||
453 | private function getCacheKey(RequestInterface $request) : string |
||
457 | } |
||
458 |
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.