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 Options 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 Options, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
8 | trait Options |
||
9 | { |
||
10 | /** @var string */ |
||
11 | private static $CONTENT_TYPE_JSON = 'application/json'; |
||
12 | |||
13 | /** @var string */ |
||
14 | private static $CONTENT_TYPE_PLAIN = 'text/plain'; |
||
15 | |||
16 | /** @var string */ |
||
17 | private static $CONTENT_TYPE_MULTIPART = 'multipart/form-data'; |
||
18 | |||
19 | /** @var string */ |
||
20 | private static $CONTENT_TYPE_FORM_PARAMS = 'application/x-www-form-urlencoded'; |
||
21 | |||
22 | /** @var string */ |
||
23 | protected static $CUSTOM_DATA_HEADER = 'X-Custom-Data'; |
||
24 | |||
25 | /** @var array<mixed> */ |
||
26 | protected $options = []; |
||
27 | |||
28 | /** @var bool */ |
||
29 | protected $throwErrors = false; |
||
30 | |||
31 | /** @var array<String> */ |
||
32 | protected $bodyFormats = ['json', 'multipart', 'form_params']; |
||
33 | |||
34 | /** @var string */ |
||
35 | protected $bodyFormat = 'json'; |
||
36 | |||
37 | /** |
||
38 | * Set authentication auth bearer token |
||
39 | * |
||
40 | * @param string $token |
||
41 | * |
||
42 | * @return $this |
||
43 | */ |
||
44 | public function authBearer(string $token): self |
||
48 | |||
49 | /** |
||
50 | * Set authentication auth basic. If password is null |
||
51 | * only username will be used |
||
52 | * |
||
53 | * @param string $username |
||
54 | * @param string $password |
||
55 | * |
||
56 | * @return $this |
||
57 | */ |
||
58 | public function authBasic(string $username, string $password = ''): self |
||
68 | |||
69 | /** |
||
70 | * @return $this |
||
71 | */ |
||
72 | public function throwErrors(): self |
||
78 | |||
79 | /** |
||
80 | * @return bool |
||
81 | */ |
||
82 | public function shouldThrowErrors(): bool |
||
86 | |||
87 | /** |
||
88 | * Values for existing header keys will be replaced |
||
89 | * |
||
90 | * @param string $key |
||
91 | * @param string $value |
||
92 | * |
||
93 | * @return $this |
||
94 | */ |
||
95 | public function header(string $key, string $value): self |
||
101 | |||
102 | /** |
||
103 | * Values for existing header keys will be replaced |
||
104 | * |
||
105 | * @param array<String> $headers |
||
106 | * |
||
107 | * @return $this |
||
108 | */ |
||
109 | public function headers(array $headers): self |
||
117 | |||
118 | /** |
||
119 | * Set the base uri. |
||
120 | * |
||
121 | * @param string $uri |
||
122 | * |
||
123 | * @return $this |
||
124 | */ |
||
125 | public function baseUri(string $uri): self |
||
129 | |||
130 | /** |
||
131 | * Disallows redirects. |
||
132 | * |
||
133 | * @return $this |
||
134 | */ |
||
135 | public function disallowRedirects(): self |
||
139 | |||
140 | /** |
||
141 | * @param int $max |
||
142 | * |
||
143 | * @return $this |
||
144 | */ |
||
145 | public function allowRedirects(int $max = 0): self |
||
149 | |||
150 | /** |
||
151 | * Float describing the timeout of the request in seconds. |
||
152 | * use 0 to wait indefinitely |
||
153 | * |
||
154 | * @param float $seconds |
||
155 | * |
||
156 | * @return $this |
||
157 | */ |
||
158 | public function timeout(float $seconds): self |
||
162 | |||
163 | /** |
||
164 | * Pass a string to specify an HTTP proxy, or an array to specify different proxies for different protocols. |
||
165 | * |
||
166 | * @param string|array<String> $proxy |
||
167 | * |
||
168 | * @return $this |
||
169 | */ |
||
170 | public function useProxy($proxy): self |
||
174 | |||
175 | /** |
||
176 | * @param float $seconds |
||
177 | * |
||
178 | * @return $this |
||
179 | */ |
||
180 | public function maxDuration(float $seconds): self |
||
184 | |||
185 | /** |
||
186 | * Does not verify SSL certificates |
||
187 | * |
||
188 | * @return $this |
||
189 | */ |
||
190 | public function doNotVerifySsl(): self |
||
197 | |||
198 | /** |
||
199 | * Does verify SSL certificates |
||
200 | * |
||
201 | * @return $this |
||
202 | */ |
||
203 | public function verifySsl(): self |
||
210 | |||
211 | /** |
||
212 | * Set the content type |
||
213 | * |
||
214 | * @param string $type |
||
215 | * |
||
216 | * @return $this |
||
217 | */ |
||
218 | public function contentType(string $type): self |
||
222 | |||
223 | /** |
||
224 | * Set the clients user agent |
||
225 | * |
||
226 | * @param string $agent |
||
227 | * |
||
228 | * @return $this |
||
229 | */ |
||
230 | public function userAgent(string $agent): self |
||
234 | |||
235 | /** |
||
236 | * Set accept header |
||
237 | * |
||
238 | * @param string $value |
||
239 | * |
||
240 | * @return $this |
||
241 | */ |
||
242 | public function accept(string $value): self |
||
246 | |||
247 | /** |
||
248 | * Set option according guzzle request options |
||
249 | * |
||
250 | * @param string $key |
||
251 | * @param mixed $value |
||
252 | * |
||
253 | * @return $this |
||
254 | */ |
||
255 | public function option(string $key, $value): self |
||
261 | |||
262 | /** |
||
263 | * Set param for the query url |
||
264 | * |
||
265 | * @param string $key |
||
266 | * @param string $value |
||
267 | * |
||
268 | * @return $this |
||
269 | */ |
||
270 | public function queryParam(string $key, string $value): self |
||
276 | |||
277 | /** |
||
278 | * Set multiple params for query url |
||
279 | * in form of [<key> => <value>, <key2> => <value2>] |
||
280 | * |
||
281 | * @param array<String> $params |
||
282 | * |
||
283 | * @return $this |
||
284 | */ |
||
285 | public function queryParams(array $params): self |
||
293 | |||
294 | /** |
||
295 | * Any extra data to attach to the response header |
||
296 | * Available in response->customData(). Useful when using asynchronous requests |
||
297 | * to identify the request |
||
298 | * |
||
299 | * @param mixed $data |
||
300 | * |
||
301 | * @return $this |
||
302 | */ |
||
303 | public function customData($data): self |
||
307 | |||
308 | /** |
||
309 | * @param string $format |
||
310 | * |
||
311 | * @return $this |
||
312 | */ |
||
313 | protected function bodyFormat(string $format): self |
||
319 | |||
320 | /** |
||
321 | * @return $this |
||
322 | */ |
||
323 | public function asPlainText() |
||
329 | |||
330 | /** |
||
331 | * @return $this |
||
332 | */ |
||
333 | public function asJson(): self |
||
339 | |||
340 | /** |
||
341 | * @return $this |
||
342 | */ |
||
343 | public function asFormParams(): self |
||
349 | |||
350 | /** |
||
351 | * Set the content type multipart form-data |
||
352 | * |
||
353 | * @return $this |
||
354 | */ |
||
355 | public function asMultipart(): self |
||
361 | |||
362 | /** |
||
363 | * @return array<mixed> |
||
364 | */ |
||
365 | public function getHeaders(): array |
||
369 | |||
370 | /** |
||
371 | * @return null|string |
||
372 | */ |
||
373 | public function getContentType() |
||
379 | |||
380 | /** |
||
381 | * @return string |
||
382 | */ |
||
383 | public function getBodyFormat(): string |
||
387 | |||
388 | /** |
||
389 | * Indicates if the payload must be an array |
||
390 | * depending on body format |
||
391 | * |
||
392 | * @return bool |
||
393 | */ |
||
394 | protected function payloadMustBeArray(): bool |
||
401 | |||
402 | /** |
||
403 | * @param mixed $payload |
||
404 | * |
||
405 | * @return $this |
||
406 | */ |
||
407 | protected function throwExceptionWhenPayloadIsNotArray($payload): self |
||
415 | |||
416 | /** |
||
417 | * Set any payload text, array |
||
418 | * |
||
419 | * @param mixed $payload |
||
420 | * |
||
421 | * @return $this |
||
422 | */ |
||
423 | public function payload($payload): self |
||
427 | |||
428 | /** |
||
429 | * @param string $option |
||
430 | * |
||
431 | * @return mixed |
||
432 | */ |
||
433 | protected function getOption(string $option) |
||
437 | |||
438 | /** |
||
439 | * @return $this |
||
440 | */ |
||
441 | protected function resolvePayload(): self |
||
491 | |||
492 | /** |
||
493 | * remove an option |
||
494 | * |
||
495 | * @param string $option |
||
496 | * |
||
497 | * @return $this |
||
498 | */ |
||
499 | protected function removeOption(string $option): self |
||
505 | |||
506 | /** |
||
507 | * @param array<String> $options |
||
508 | * |
||
509 | * @return $this |
||
510 | */ |
||
511 | protected function removeOptions(array $options): self |
||
523 | |||
524 | /** |
||
525 | * @param mixed $body |
||
526 | * |
||
527 | * @return $this |
||
528 | */ |
||
529 | protected function body($body): self |
||
533 | |||
534 | } |
||
535 |
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.