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 Request 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 Request, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
23 | class Request extends Http |
||
24 | { |
||
25 | /** |
||
26 | * you can implement more traits |
||
27 | */ |
||
28 | protected static $curlAlias = array( |
||
29 | 'url' => 'CURLOPT_URL', |
||
30 | 'uri' => 'CURLOPT_URL', |
||
31 | 'debug' => 'CURLOPT_VERBOSE',//for debug verbose |
||
32 | 'method' => 'CURLOPT_CUSTOMREQUEST', |
||
33 | 'data' => 'CURLOPT_POSTFIELDS', // array or string , file begin with '@' |
||
34 | 'ua' => 'CURLOPT_USERAGENT', |
||
35 | 'timeout' => 'CURLOPT_TIMEOUT', // (secs) 0 means indefinitely |
||
36 | 'connect_timeout' => 'CURLOPT_CONNECTTIMEOUT', |
||
37 | 'referer' => 'CURLOPT_REFERER', |
||
38 | 'binary' => 'CURLOPT_BINARYTRANSFER', |
||
39 | 'port' => 'CURLOPT_PORT', |
||
40 | 'header' => 'CURLOPT_HEADER', // TRUE:include header |
||
41 | 'headers' => 'CURLOPT_HTTPHEADER', // array |
||
42 | 'download' => 'CURLOPT_FILE', // writing file stream (using fopen()), default is STDOUT |
||
43 | 'upload' => 'CURLOPT_INFILE', // reading file stream |
||
44 | 'transfer' => 'CURLOPT_RETURNTRANSFER', // TRUE:return string; FALSE:output directly (curl_exec) |
||
45 | 'follow_location' => 'CURLOPT_FOLLOWLOCATION', |
||
46 | 'timeout_ms' => 'CURLOPT_TIMEOUT_MS', // milliseconds, libcurl version > 7.36.0 , |
||
47 | 'expects_mime' => null, //expected mime |
||
48 | 'send_mime' => null, //send mime |
||
49 | 'ip' => null,//specify ip to send request |
||
50 | 'callback' => null,//callback on end |
||
51 | |||
52 | ); |
||
53 | protected static $loggerHandler; |
||
54 | public |
||
55 | $curlHandle, |
||
56 | $uri, |
||
57 | $sendMime, |
||
58 | $expectsMime, |
||
59 | $timeout, |
||
60 | $maxRedirects, |
||
61 | $encoding, |
||
62 | $payload, |
||
63 | $retryTimes, |
||
64 | /** |
||
65 | * @var int seconds |
||
66 | */ |
||
67 | $retryDuration, |
||
68 | $followRedirects; |
||
69 | |||
70 | protected |
||
71 | $body, |
||
72 | $endCallback, |
||
73 | $withURIQuery, |
||
74 | $hasInitialized = false, |
||
75 | /** |
||
76 | * @var array |
||
77 | */ |
||
78 | $options = array( |
||
79 | 'CURLOPT_MAXREDIRS' => 10, |
||
80 | 'CURLOPT_SSL_VERIFYPEER' => false,//for https |
||
81 | 'CURLOPT_SSL_VERIFYHOST' => 0,//for https |
||
82 | 'CURLOPT_IPRESOLVE' => CURL_IPRESOLVE_V4,//ipv4 first |
||
83 | 'CURLOPT_SAFE_UPLOAD' => false,// compatible with PHP 5.6.0 |
||
84 | 'CURLOPT_USERAGENT' => 'Mozilla/5.0 (compatible;)', |
||
85 | 'header' => true, |
||
86 | 'method' => self::GET, |
||
87 | 'transfer' => true, |
||
88 | 'headers' => array(), |
||
89 | 'follow_location' => true, |
||
90 | 'timeout' => 0, |
||
91 | // 'ip' => null, //host, in string, .e.g: 172.16.1.1:888 |
||
92 | 'retry_times' => 1,//redo task when failed |
||
93 | 'retry_duration' => 0,//in seconds |
||
94 | 'send_mime' => 'form',//in seconds |
||
95 | ); |
||
96 | |||
97 | |||
98 | /** |
||
99 | * Request constructor. |
||
100 | */ |
||
101 | 7 | protected function __construct() |
|
102 | { |
||
103 | 7 | } |
|
104 | |||
105 | /** |
||
106 | * @return Request |
||
107 | */ |
||
108 | 7 | public static function create() |
|
109 | { |
||
110 | 7 | return new self; |
|
111 | } |
||
112 | |||
113 | /** |
||
114 | * @param callable $handler |
||
115 | */ |
||
116 | 1 | public static function setLogHandler(callable $handler) |
|
117 | { |
||
118 | 1 | self::$loggerHandler = $handler; |
|
119 | 1 | } |
|
120 | /** |
||
121 | * Specify timeout |
||
122 | * @param float|int $timeout seconds to timeout the HTTP call |
||
123 | * @return Request |
||
124 | */ |
||
125 | public function timeout($timeout) |
||
126 | { |
||
127 | $this->timeout = $timeout; |
||
128 | return $this; |
||
129 | } |
||
130 | |||
131 | /** |
||
132 | * @return Request |
||
133 | */ |
||
134 | public function noFollow() |
||
135 | { |
||
136 | return $this->follow(0); |
||
137 | } |
||
138 | |||
139 | /** |
||
140 | * If the response is a 301 or 302 redirect, automatically |
||
141 | * send off another request to that location |
||
142 | * @param int $follow follow or not to follow or maximal number of redirects |
||
143 | * @return Request |
||
144 | */ |
||
145 | public function follow($follow) |
||
146 | { |
||
147 | $this->maxRedirects = abs($follow); |
||
148 | $this->followRedirects = $follow > 0; |
||
149 | return $this; |
||
150 | } |
||
151 | |||
152 | /** |
||
153 | * @param $parsedComponents |
||
154 | * @return string |
||
155 | */ |
||
156 | 7 | private static function combineUrl($parsedComponents) |
|
157 | { |
||
158 | 7 | $scheme = isset($parsedComponents['scheme']) ? $parsedComponents['scheme'] . '://' : ''; |
|
159 | 7 | $host = isset($parsedComponents['host']) ? $parsedComponents['host'] : ''; |
|
160 | 7 | $port = isset($parsedComponents['port']) ? ':' . $parsedComponents['port'] : ''; |
|
161 | 7 | $user = isset($parsedComponents['user']) ? $parsedComponents['user'] : ''; |
|
162 | 7 | $pass = isset($parsedComponents['pass']) ? ':' . $parsedComponents['pass'] : ''; |
|
163 | 7 | $pass = ($user || $pass) ? "$pass@" : ''; |
|
164 | 7 | $path = isset($parsedComponents['path']) ? $parsedComponents['path'] : ''; |
|
165 | 7 | $query = isset($parsedComponents['query']) ? '?' . $parsedComponents['query'] : ''; |
|
166 | 7 | $fragment = isset($parsedComponents['fragment']) ? '#' . $parsedComponents['fragment'] : ''; |
|
167 | 7 | return "$scheme$user$pass$host$port$path$query$fragment"; |
|
168 | } |
||
169 | |||
170 | /** |
||
171 | * @param string $mime |
||
172 | * @return $this |
||
173 | */ |
||
174 | 1 | public function expectsMime($mime = 'json') |
|
175 | { |
||
176 | 1 | $this->expectsMime = $mime; |
|
177 | 1 | $this->options['expects_mime'] = $mime; |
|
178 | 1 | return $this; |
|
179 | } |
||
180 | |||
181 | /** |
||
182 | * @param string $mime |
||
183 | * @return Request |
||
184 | */ |
||
185 | 2 | public function sendMime($mime = 'json') |
|
186 | { |
||
187 | 2 | $this->sendMime = $mime; |
|
188 | 2 | $this->options['send_mime'] = $mime; |
|
189 | // $this->addHeader('Content-type', Mime::getFullMime($mime)); |
||
190 | 2 | return $this; |
|
191 | } |
||
192 | |||
193 | /** |
||
194 | * @param $headerName |
||
195 | * @param $value , can be rawurlencode |
||
196 | * @return $this |
||
197 | */ |
||
198 | 1 | public function addHeader($headerName, $value) |
|
199 | { |
||
200 | 1 | $this->options['headers'][] = $headerName . ': ' . $value; |
|
201 | 1 | return $this; |
|
202 | } |
||
203 | |||
204 | /** |
||
205 | * @param $uri |
||
206 | * @return $this |
||
207 | */ |
||
208 | public function uri($uri) |
||
209 | { |
||
210 | $this->uri = $uri; |
||
211 | return $this; |
||
212 | } |
||
213 | |||
214 | |||
215 | |||
216 | /** |
||
217 | * @param array $headers |
||
218 | * @return $this |
||
219 | */ |
||
220 | 1 | public function addHeaders(array $headers) |
|
221 | { |
||
222 | 1 | foreach ($headers as $header => $value) { |
|
223 | 1 | $this->addHeader($header, $value); |
|
224 | 1 | } |
|
225 | 1 | return $this; |
|
226 | } |
||
227 | /** |
||
228 | * @return mixed |
||
229 | */ |
||
230 | public function endCallback() |
||
231 | { |
||
232 | return $this->endCallback; |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * @return bool |
||
237 | */ |
||
238 | 2 | public function hasEndCallback() |
|
239 | { |
||
240 | 2 | return isset($this->endCallback); |
|
241 | } |
||
242 | |||
243 | /** |
||
244 | * @param $field alias or field name |
||
245 | * @return bool|mixed |
||
246 | */ |
||
247 | 2 | public function getIni($field = null) |
|
248 | { |
||
249 | 2 | if(!$field) return $this->options; |
|
250 | 2 | $full = self::fullOption($field); |
|
251 | 2 | return isset($this->options[$full]) ? $this->options[$full] : false; |
|
252 | } |
||
253 | |||
254 | /** |
||
255 | * @param $key |
||
256 | * @return mixed |
||
257 | */ |
||
258 | 7 | protected static function fullOption($key) |
|
259 | { |
||
260 | 7 | $full = false; |
|
261 | 7 | if (isset(self::$curlAlias[$key])) { |
|
262 | 7 | $full = self::$curlAlias[$key]; |
|
263 | 7 | } elseif ((substr($key, 0, strlen('CURLOPT_')) == 'CURLOPT_') && defined($key)) { |
|
264 | 7 | $full = $key; |
|
265 | 7 | } |
|
266 | 7 | return $full; |
|
267 | } |
||
268 | |||
269 | /** |
||
270 | * @param $queryData |
||
271 | * @return $this |
||
272 | */ |
||
273 | 1 | public function addQuery($queryData) |
|
274 | { |
||
275 | 1 | if (!empty($queryData)) { |
|
276 | 1 | if (is_array($queryData)) { |
|
277 | 1 | $this->withURIQuery = http_build_query($queryData); |
|
278 | 1 | } else if (is_string($queryData)) { |
|
279 | 1 | $this->withURIQuery = $queryData; |
|
280 | 1 | } else { |
|
281 | throw new InvalidArgumentException('data must be array or string'); |
||
282 | } |
||
283 | 1 | } |
|
284 | 1 | return $this; |
|
285 | } |
||
286 | /** |
||
287 | * @param $uri |
||
288 | * @param null $payload |
||
289 | * @param array $options |
||
290 | * @return Request |
||
291 | */ |
||
292 | 3 | public function post($uri, $payload = null, array $options = array()) |
|
293 | { |
||
294 | 3 | return $this->ini(Http::POST, $uri, $payload, $options); |
|
295 | } |
||
296 | |||
297 | /** |
||
298 | * @param $uri |
||
299 | * @param null $payload |
||
300 | * @param array $options |
||
301 | * @return Request |
||
302 | */ |
||
303 | 1 | public function upload($uri, $payload = null, array $options = array()) |
|
304 | { |
||
305 | 1 | return $this->ini(Http::POST, $uri, $payload, $options)->sendMime('upload'); |
|
306 | } |
||
307 | |||
308 | /** |
||
309 | * @param $uri |
||
310 | * @param null $payload |
||
311 | * @param array $options |
||
312 | * @param null $response |
||
313 | * @return string |
||
314 | */ |
||
315 | 2 | public function quickPost($uri, $payload = null, array $options = array(), &$response = null) |
|
320 | |||
321 | |||
322 | /** |
||
323 | * @param $method |
||
324 | * @param $url |
||
325 | * @param $data |
||
326 | * @param array $options |
||
327 | * @return $this |
||
328 | */ |
||
329 | 6 | View Code Duplication | protected function ini($method, $url, $data, array $options = array()) |
336 | |||
337 | /** |
||
338 | * @param array $options |
||
339 | * @return $this |
||
340 | */ |
||
341 | 7 | public function addOptions(array $options = array()) |
|
347 | |||
348 | /** |
||
349 | * @param $uri |
||
350 | * @param null $payload |
||
351 | * @param array $options |
||
352 | * @return Request |
||
353 | */ |
||
354 | 1 | function put($uri, $payload = null, array $options = array()) |
|
358 | |||
359 | /** |
||
360 | * @param $uri |
||
361 | * @param null $payload |
||
362 | * @param array $options |
||
363 | * @return Request |
||
364 | */ |
||
365 | 1 | function patch($uri, $payload = null, array $options = array()) |
|
369 | |||
370 | /** |
||
371 | * @param $uri |
||
372 | * @param array $options |
||
373 | * @return Request |
||
374 | */ |
||
375 | 3 | public function get($uri, array $options = array()) |
|
379 | |||
380 | |||
381 | /** |
||
382 | * @param $uri |
||
383 | * @param array $options |
||
384 | * @param null $response |
||
385 | * @return string |
||
386 | */ |
||
387 | 2 | public function quickGet($uri, array $options = array(), &$response = null) |
|
392 | |||
393 | /** |
||
394 | * @param $uri |
||
395 | * @param array $options |
||
396 | * @return Request |
||
397 | */ |
||
398 | 1 | function options($uri, array $options = array()) |
|
402 | |||
403 | /** |
||
404 | * @param $uri |
||
405 | * @param array $options |
||
406 | * @return Request |
||
407 | */ |
||
408 | 1 | function head($uri, array $options = array()) |
|
412 | |||
413 | /** |
||
414 | * @param $uri |
||
415 | * @param array $options |
||
416 | * @return Request |
||
417 | */ |
||
418 | 1 | function delete($uri, array $options = array()) |
|
422 | |||
423 | /** |
||
424 | * @param $uri |
||
425 | * @param array $options |
||
426 | * @return Request |
||
427 | */ |
||
428 | 2 | function trace($uri, array $options = array()) |
|
432 | |||
433 | /** |
||
434 | * @param bool $isMultiCurl |
||
435 | * @return Response |
||
436 | */ |
||
437 | 7 | public function send($isMultiCurl = false) |
|
438 | { |
||
439 | try { |
||
440 | 7 | if (!$this->hasInitialized) |
|
441 | 7 | $this->applyOptions(); |
|
442 | 7 | $response = $this->makeResponse($isMultiCurl); |
|
443 | 7 | $response->parse(); |
|
444 | 7 | } catch (\Exception $e) { |
|
445 | if(!isset($response)) $response = Response::create($this, null, null, null, null); |
||
446 | $response->error = $e->getMessage(); |
||
447 | $response->errorCode = 999; |
||
448 | } |
||
449 | |||
450 | 7 | if (self::$loggerHandler) { |
|
451 | 3 | call_user_func(self::$loggerHandler, $response); |
|
452 | 3 | } |
|
453 | 7 | if ($this->endCallback) { |
|
454 | 5 | call_user_func($this->endCallback, $response); |
|
455 | 5 | } |
|
456 | |||
457 | 7 | return $response; |
|
458 | } |
||
459 | |||
460 | /** |
||
461 | * @return $this |
||
462 | */ |
||
463 | 7 | public function applyOptions() |
|
471 | |||
472 | /** |
||
473 | * @return $this |
||
474 | */ |
||
475 | 7 | protected function prepare() |
|
561 | |||
562 | 7 | public function serializeBody() |
|
563 | { |
||
564 | //Passing an array to CURLOPT_POSTFIELDS will encode the data as multipart/form-data, while passing a URL-encoded string will encode the data as application/x-www-form-urlencoded. |
||
565 | 7 | if (isset($this->options['data'])) { |
|
566 | 7 | $this->options[CURLOPT_POST] = true; |
|
567 | 7 | $clz = '\\MultiHttp\\Handler\\'.ucfirst($this->sendMime); |
|
568 | 7 | $inst = new $clz; |
|
569 | 7 | if (!($inst instanceof Handler\IHandler)) throw new InvalidOperationException($clz . ' is not implement of IHandler'); |
|
570 | 7 | $this->body = $inst->encode($this->options['data']); |
|
571 | 7 | } |
|
572 | 7 | } |
|
573 | |||
574 | /** |
||
575 | * @param callable $callback |
||
576 | * @return $this |
||
577 | */ |
||
578 | 5 | public function onEnd(callable $callback) |
|
579 | { |
||
580 | 5 | if (!is_callable($callback)) { |
|
581 | throw new InvalidArgumentException('callback not is callable :' . print_r($callback, 1)); |
||
582 | } |
||
587 | |||
588 | /** |
||
589 | * @param array $options |
||
590 | * @return array |
||
591 | */ |
||
592 | 7 | protected static function filterAndRaw(array &$options) |
|
607 | |||
608 | /** |
||
609 | * @param bool $isMultiCurl |
||
610 | * @return Response |
||
611 | * @throws \Exception |
||
612 | */ |
||
613 | 7 | public function makeResponse($isMultiCurl = false) |
|
629 | |||
630 | |||
631 | } |
||
632 |
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.