Total Complexity | 77 |
Total Lines | 580 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like WebClient 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.
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 WebClient, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
15 | class WebClient extends Client |
||
16 | { |
||
17 | const MODE = 'web'; |
||
18 | |||
19 | /** |
||
20 | * Cached responses to avoid multiple request for the same file |
||
21 | * |
||
22 | * @var array |
||
23 | */ |
||
24 | protected $cache = []; |
||
25 | |||
26 | /** |
||
27 | * Apache Tika server host |
||
28 | * |
||
29 | * @var string |
||
30 | */ |
||
31 | protected $host = null; |
||
32 | |||
33 | /** |
||
34 | * Apache Tika server port |
||
35 | * |
||
36 | * @var int |
||
37 | */ |
||
38 | protected $port = null; |
||
39 | |||
40 | /** |
||
41 | * Number of retries on server error |
||
42 | * |
||
43 | * @var int |
||
44 | */ |
||
45 | protected $retries = 3; |
||
46 | |||
47 | /** |
||
48 | * Default cURL options |
||
49 | * |
||
50 | * @var array |
||
51 | */ |
||
52 | protected $options = |
||
53 | [ |
||
54 | CURLINFO_HEADER_OUT => true, |
||
55 | CURLOPT_HTTPHEADER => [], |
||
56 | CURLOPT_PUT => true, |
||
57 | CURLOPT_RETURNTRANSFER => true, |
||
58 | CURLOPT_TIMEOUT => 5 |
||
59 | ]; |
||
60 | |||
61 | /** |
||
62 | * Configure class and test if server is running |
||
63 | * |
||
64 | * @param string $host |
||
65 | * @param int $port |
||
66 | * @param array $options |
||
67 | * @param bool $check |
||
68 | * @throws \Exception |
||
69 | */ |
||
70 | public function __construct($host = null, $port = null, $options = [], $check = true) |
||
71 | { |
||
72 | parent::__construct(); |
||
73 | |||
74 | if(is_string($host) && filter_var($host, FILTER_VALIDATE_URL)) |
||
75 | { |
||
76 | $this->setUrl($host); |
||
77 | } |
||
78 | elseif($host) |
||
79 | { |
||
80 | $this->setHost($host); |
||
81 | } |
||
82 | |||
83 | if(is_numeric($port)) |
||
84 | { |
||
85 | $this->setPort($port); |
||
86 | } |
||
87 | |||
88 | if(!empty($options)) |
||
89 | { |
||
90 | $this->setOptions($options); |
||
91 | } |
||
92 | |||
93 | $this->setDownloadRemote(true); |
||
94 | |||
95 | if($check === true) |
||
96 | { |
||
97 | $this->check(); |
||
98 | } |
||
99 | } |
||
100 | |||
101 | /** |
||
102 | * Get the base URL |
||
103 | * |
||
104 | * @return string |
||
105 | */ |
||
106 | public function getUrl() |
||
107 | { |
||
108 | return sprintf('http://%s:%d', $this->host, $this->port ?: 9998); |
||
109 | } |
||
110 | |||
111 | /** |
||
112 | * Set the host and port using an URL |
||
113 | * |
||
114 | * @param string $url |
||
115 | * @return $this |
||
116 | */ |
||
117 | public function setUrl($url) |
||
118 | { |
||
119 | $url = parse_url($url); |
||
120 | |||
121 | $this->setHost($url['host']); |
||
122 | |||
123 | if(isset($url['port'])) |
||
124 | { |
||
125 | $this->setPort($url['port']); |
||
126 | } |
||
127 | |||
128 | return $this; |
||
129 | } |
||
130 | |||
131 | /** |
||
132 | * Get the host |
||
133 | * |
||
134 | * @return null|string |
||
135 | */ |
||
136 | public function getHost() |
||
137 | { |
||
138 | return $this->host; |
||
139 | } |
||
140 | |||
141 | /** |
||
142 | * Set the host |
||
143 | * |
||
144 | * @param string $host |
||
145 | * @return $this |
||
146 | */ |
||
147 | public function setHost($host) |
||
148 | { |
||
149 | $this->host = $host; |
||
150 | |||
151 | return $this; |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * Get the port |
||
156 | * |
||
157 | * @return null|int |
||
158 | */ |
||
159 | public function getPort() |
||
160 | { |
||
161 | return $this->port; |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * Set the port |
||
166 | * |
||
167 | * @param int $port |
||
168 | * @return $this |
||
169 | */ |
||
170 | public function setPort($port) |
||
171 | { |
||
172 | $this->port = $port; |
||
173 | |||
174 | return $this; |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * Get the number of retries |
||
179 | * |
||
180 | * @return int |
||
181 | */ |
||
182 | public function getRetries() |
||
183 | { |
||
184 | return $this->retries; |
||
185 | } |
||
186 | |||
187 | /** |
||
188 | * Set the number of retries |
||
189 | * |
||
190 | * @param int $retries |
||
191 | * @return $this |
||
192 | */ |
||
193 | public function setRetries($retries) |
||
194 | { |
||
195 | $this->retries = $retries; |
||
196 | |||
197 | return $this; |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * Get all the options |
||
202 | * |
||
203 | * @return null|array |
||
204 | */ |
||
205 | public function getOptions() |
||
206 | { |
||
207 | return $this->options; |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * Get an specified option |
||
212 | * |
||
213 | * @param string $key |
||
214 | * @return mixed |
||
215 | */ |
||
216 | public function getOption($key) |
||
219 | } |
||
220 | |||
221 | /** |
||
222 | * Set a cURL option to be set with curl_setopt() |
||
223 | * |
||
224 | * @link http://php.net/manual/en/curl.constants.php |
||
225 | * @link http://php.net/manual/en/function.curl-setopt.php |
||
226 | * @param string $key |
||
227 | * @param mixed $value |
||
228 | * @return $this |
||
229 | * @throws \Exception |
||
230 | */ |
||
231 | public function setOption($key, $value) |
||
232 | { |
||
233 | if(in_array($key, [CURLINFO_HEADER_OUT, CURLOPT_PUT, CURLOPT_RETURNTRANSFER])) |
||
234 | { |
||
235 | throw new Exception("Value for cURL option $key cannot be modified", 3); |
||
236 | } |
||
237 | |||
238 | $this->options[$key] = $value; |
||
239 | |||
240 | return $this; |
||
241 | } |
||
242 | |||
243 | /** |
||
244 | * Set the cURL options |
||
245 | * |
||
246 | * @param array $options |
||
247 | * @return $this |
||
248 | * @throws \Exception |
||
249 | */ |
||
250 | public function setOptions($options) |
||
251 | { |
||
252 | foreach($options as $key => $value) |
||
253 | { |
||
254 | $this->setOption($key, $value); |
||
255 | } |
||
256 | |||
257 | return $this; |
||
258 | } |
||
259 | |||
260 | /** |
||
261 | * Get the timeout value for cURL |
||
262 | * |
||
263 | * @return int |
||
264 | */ |
||
265 | public function getTimeout() |
||
266 | { |
||
267 | return $this->getOption(CURLOPT_TIMEOUT); |
||
268 | } |
||
269 | |||
270 | /** |
||
271 | * Set the timeout value for cURL |
||
272 | * |
||
273 | * @param int $value |
||
274 | * @return $this |
||
275 | * @throws \Exception |
||
276 | */ |
||
277 | public function setTimeout($value) |
||
278 | { |
||
279 | $this->setOption(CURLOPT_TIMEOUT, (int) $value); |
||
280 | |||
281 | return $this; |
||
282 | } |
||
283 | |||
284 | /** |
||
285 | * Check if server is running |
||
286 | * |
||
287 | * @throws \Exception |
||
288 | */ |
||
289 | public function check() |
||
290 | { |
||
291 | if($this->isChecked() === false) |
||
292 | { |
||
293 | $this->setChecked(true); |
||
294 | |||
295 | // throws an exception if server is unreachable or can't connect |
||
296 | $this->request('version'); |
||
297 | } |
||
298 | } |
||
299 | |||
300 | /** |
||
301 | * Configure, make a request and return its results |
||
302 | * |
||
303 | * @param string $type |
||
304 | * @param string $file |
||
305 | * @return string |
||
306 | * @throws \Exception |
||
307 | */ |
||
308 | public function request($type, $file = null) |
||
378 | } |
||
379 | |||
380 | /** |
||
381 | * Make a request to Apache Tika Server |
||
382 | * |
||
383 | * @param array $options |
||
384 | * @return array |
||
385 | * @throws \Exception |
||
386 | */ |
||
387 | protected function exec(array $options = []) |
||
388 | { |
||
389 | // cURL init and options |
||
390 | $curl = curl_init(); |
||
391 | |||
392 | // add options only if cURL init doesn't fails |
||
393 | if(is_resource($curl)) |
||
394 | { |
||
395 | // we avoid curl_setopt_array($curl, $options) because strange Windows behaviour (issue #8) |
||
396 | foreach($options as $option => $value) |
||
397 | { |
||
398 | curl_setopt($curl, $option, $value); |
||
399 | } |
||
400 | |||
401 | // make the request directly |
||
402 | if(is_null($this->callback)) |
||
403 | { |
||
404 | $this->response = curl_exec($curl) ?: ''; |
||
|
|||
405 | } |
||
406 | // with a callback, the response is appended on each block inside the callback |
||
407 | else |
||
408 | { |
||
409 | $this->response = ''; |
||
410 | curl_exec($curl); |
||
411 | } |
||
412 | } |
||
413 | |||
414 | // exception if cURL fails |
||
415 | if($curl === false) |
||
416 | { |
||
417 | throw new Exception('Unexpected error'); |
||
418 | } |
||
419 | elseif(curl_errno($curl)) |
||
420 | { |
||
421 | throw new Exception(curl_error($curl), curl_errno($curl)); |
||
422 | } |
||
423 | |||
424 | // return the response and the status code |
||
425 | return [trim($this->response), curl_getinfo($curl, CURLINFO_HTTP_CODE)]; |
||
426 | } |
||
427 | |||
428 | /** |
||
429 | * Throws an exception for an error status code |
||
430 | * |
||
431 | * @codeCoverageIgnore |
||
432 | * |
||
433 | * @param int $status |
||
434 | * @param string $resource |
||
435 | * @param string $file |
||
436 | * @throws \Exception |
||
437 | */ |
||
438 | protected function error($status, $resource, $file = null) |
||
439 | { |
||
440 | switch($status) |
||
441 | { |
||
442 | // method not allowed |
||
443 | case 405: |
||
444 | throw new Exception('Method not allowed', 405); |
||
445 | break; |
||
446 | |||
447 | // unsupported media type |
||
448 | case 415: |
||
449 | throw new Exception('Unsupported media type', 415); |
||
450 | break; |
||
451 | |||
452 | // unprocessable entity |
||
453 | case 422: |
||
454 | $message = 'Unprocessable document'; |
||
455 | |||
456 | // using remote files require Tika server to be launched with specific options |
||
457 | if($this->downloadRemote == false && preg_match('/^http/', $file)) |
||
458 | { |
||
459 | $message .= ' (is server launched using "-enableUnsecureFeatures -enableFileUrl" arguments?)'; |
||
460 | } |
||
461 | |||
462 | throw new Exception($message, 422); |
||
463 | break; |
||
464 | |||
465 | // server error |
||
466 | case 500: |
||
467 | throw new Exception('Error while processing document', 500); |
||
468 | break; |
||
469 | |||
470 | // unexpected |
||
471 | default: |
||
472 | throw new Exception("Unexpected response for /$resource ($status)", 501); |
||
473 | } |
||
474 | } |
||
475 | |||
476 | /** |
||
477 | * Get the parameters to make the request |
||
478 | * |
||
479 | * @link https://wiki.apache.org/tika/TikaJAXRS#Specifying_a_URL_Instead_of_Putting_Bytes |
||
480 | * @param string $type |
||
481 | * @param string $file |
||
482 | * @return array |
||
483 | * @throws \Exception |
||
484 | */ |
||
485 | protected function getParameters($type, $file = null) |
||
541 | } |
||
542 | |||
543 | /** |
||
544 | * Get the cURL options |
||
545 | * |
||
546 | * @param string $type |
||
547 | * @param string $file |
||
548 | * @return array |
||
549 | * @throws \Exception |
||
550 | */ |
||
551 | protected function getCurlOptions($type, $file = null) |
||
595 | } |
||
596 | } |
||
597 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.