Complex classes like CardDavBackend 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 CardDavBackend, and based on these observations, apply Extract Interface, too.
1 | <?php namespace CardDavPHP; |
||
101 | class CardDavBackend |
||
102 | { |
||
103 | /** |
||
104 | * CardDAV PHP Version |
||
105 | * |
||
106 | * @constant string |
||
107 | */ |
||
108 | const VERSION = '0.7'; |
||
109 | |||
110 | /** |
||
111 | * User agent displayed in http requests |
||
112 | * |
||
113 | * @constant string |
||
114 | */ |
||
115 | const USERAGENT = 'CardDAV PHP/'; |
||
116 | |||
117 | /** |
||
118 | * CardDAV server url |
||
119 | * |
||
120 | * @var string |
||
121 | */ |
||
122 | private $url = null; |
||
123 | |||
124 | /** |
||
125 | * CardDAV server url_parts |
||
126 | * |
||
127 | * @var array |
||
128 | */ |
||
129 | private $url_parts = null; |
||
130 | |||
131 | /** |
||
132 | * VCard File URL Extension |
||
133 | * |
||
134 | * @var string |
||
135 | */ |
||
136 | private $url_vcard_extension = '.vcf'; |
||
137 | |||
138 | /** |
||
139 | * Authentication string |
||
140 | * |
||
141 | * @var string |
||
142 | */ |
||
143 | private $auth = null; |
||
144 | |||
145 | /** |
||
146 | * Authentication: username |
||
147 | * |
||
148 | * @var string |
||
149 | */ |
||
150 | private $username = null; |
||
151 | |||
152 | /** |
||
153 | * Authentication: password |
||
154 | * |
||
155 | * @var string |
||
156 | */ |
||
157 | private $password = null; |
||
158 | |||
159 | /** |
||
160 | * Characters used for vCard id generation |
||
161 | * |
||
162 | * @var array |
||
163 | */ |
||
164 | private $vcard_id_chars = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'A', 'B', 'C', 'D', 'E', 'F'); |
||
165 | |||
166 | /** |
||
167 | * CardDAV server connection (curl handle) |
||
168 | * |
||
169 | * @var resource |
||
170 | */ |
||
171 | private $curl; |
||
172 | |||
173 | /** |
||
174 | * Follow redirects (Location Header) |
||
175 | * |
||
176 | * @var boolean |
||
177 | */ |
||
178 | private $follow_redirects = true; |
||
179 | |||
180 | /** |
||
181 | * Maximum redirects to follow |
||
182 | * |
||
183 | * @var integer |
||
184 | */ |
||
185 | private $follow_redirects_count = 3; |
||
186 | |||
187 | /** |
||
188 | * Debug on or off |
||
189 | * |
||
190 | * @var boolean |
||
191 | */ |
||
192 | private $debug = false; |
||
193 | |||
194 | /** |
||
195 | * All available debug information |
||
196 | * |
||
197 | * @var array |
||
198 | */ |
||
199 | private $debug_information = array(); |
||
200 | |||
201 | /** |
||
202 | * Exception codes |
||
203 | */ |
||
204 | const EXCEPTION_WRONG_HTTP_STATUS_CODE_GET = 1000; |
||
205 | const EXCEPTION_WRONG_HTTP_STATUS_CODE_GET_VCARD = 1001; |
||
206 | const EXCEPTION_WRONG_HTTP_STATUS_CODE_GET_XML_VCARD = 1002; |
||
207 | const EXCEPTION_WRONG_HTTP_STATUS_CODE_DELETE = 1003; |
||
208 | const EXCEPTION_WRONG_HTTP_STATUS_CODE_ADD = 1004; |
||
209 | const EXCEPTION_WRONG_HTTP_STATUS_CODE_UPDATE = 1005; |
||
210 | const EXCEPTION_MALFORMED_XML_RESPONSE = 1006; |
||
211 | const EXCEPTION_COULD_NOT_GENERATE_NEW_VCARD_ID = 1007; |
||
212 | |||
213 | |||
214 | /** |
||
215 | * Constructor |
||
216 | * Sets the CardDAV server url |
||
217 | * |
||
218 | * @param string $url CardDAV server url |
||
219 | */ |
||
220 | public function __construct($url = null) |
||
221 | { |
||
222 | if ($url !== null) { |
||
223 | $this->setUrl($url); |
||
224 | } |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Sets debug information |
||
229 | * |
||
230 | * @param array $debug_information Debug information |
||
231 | * @return void |
||
232 | */ |
||
233 | public function setDebug(array $debug_information) |
||
234 | { |
||
235 | $this->debug_information[] = $debug_information; |
||
236 | } |
||
237 | |||
238 | /** |
||
239 | * Sets the CardDAV server url |
||
240 | * |
||
241 | * @param string $url CardDAV server url |
||
242 | * @return void |
||
243 | */ |
||
244 | public function setUrl($url) |
||
245 | { |
||
246 | $this->url = $url; |
||
247 | |||
248 | if (substr($this->url, -1, 1) !== '/') { |
||
249 | $this->url = $this->url . '/'; |
||
250 | } |
||
251 | |||
252 | $this->url_parts = parse_url($this->url); |
||
|
|||
253 | |||
254 | // workaround for providers that don't use the default .vcf extension |
||
255 | if (strpos($this->url, "google.com")) |
||
256 | { |
||
257 | $this->setVcardExtension(""); |
||
258 | } |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * Sets the CardDAV vcard url extension |
||
263 | * |
||
264 | * Most providers do requests handling Vcards with .vcf, however |
||
265 | * this isn't always the case and some providers (such as Google) |
||
266 | * returned a 404 if the .vcf extension is used - or the other |
||
267 | * way around, returning 404 unless .vcf is used. |
||
268 | * |
||
269 | * Both approaches are technically correct, see rfc635 |
||
270 | * http://tools.ietf.org/html/rfc6352 |
||
271 | * |
||
272 | * |
||
273 | * @param string $extension File extension |
||
274 | * @return void |
||
275 | */ |
||
276 | public function setVcardExtension($extension) |
||
277 | { |
||
278 | $this->url_vcard_extension = $extension; |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * Sets authentication information |
||
283 | * |
||
284 | * @param string $username CardDAV server username |
||
285 | * @param string $password CardDAV server password |
||
286 | * @return void |
||
287 | */ |
||
288 | public function setAuth($username, $password) |
||
289 | { |
||
290 | $this->username = $username; |
||
291 | $this->password = $password; |
||
292 | $this->auth = $username . ':' . $password; |
||
293 | } |
||
294 | |||
295 | /** |
||
296 | * Sets wether to follow redirects and if yes how often |
||
297 | * |
||
298 | * @param boolean $follow_redirects |
||
299 | * @param integer $follow_redirects_count |
||
300 | * @return void |
||
301 | */ |
||
302 | public function setFollowRedirects($follow_redirects, $follow_redirects_count = 3) |
||
303 | { |
||
304 | $this->follow_redirects = $follow_redirects && $follow_redirects_count > 0; |
||
305 | $this->follow_redirects_count = $follow_redirects_count > 0 ? $follow_redirects_count : 0; |
||
306 | } |
||
307 | |||
308 | /** |
||
309 | * Gets all available debug information |
||
310 | * |
||
311 | * @return array $this->debug_information All available debug information |
||
312 | */ |
||
313 | public function getDebug() |
||
314 | { |
||
315 | return $this->debug_information; |
||
316 | } |
||
317 | |||
318 | /** |
||
319 | * Gets all vCards including additional information from the CardDAV server |
||
320 | * |
||
321 | * @param boolean $include_vcards Include vCards within the response (simplified only) |
||
322 | * @param boolean $raw Get response raw or simplified |
||
323 | * @return string Raw or simplified XML response |
||
324 | */ |
||
325 | public function get($include_vcards = true, $raw = false) |
||
326 | { |
||
327 | // for owncloud&co. Doesn't work with OpenXchange/Appsuite |
||
328 | $result = $this->query($this->url, 'PROPFIND'); |
||
329 | |||
330 | // for OpenXchange/Appsuite |
||
331 | $content = '<?xml version="1.0" encoding="UTF-8" ?><D:sync-collection xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav"><D:sync-token></D:sync-token><D:prop><D:getcontenttype/><D:getetag/><D:allprop/><C:address-data><C:allprop/></C:address-data></D:prop><C:filter/></D:sync-collection>'; |
||
332 | $content_type = 'application/xml'; |
||
333 | //$result = $this->query($this->url, 'REPORT', $content, $content_type); |
||
334 | |||
335 | // DEBUG: print the response of the carddav-server |
||
336 | //print_r($result); |
||
337 | |||
338 | switch ($result['http_code']) |
||
339 | { |
||
340 | case 200: |
||
341 | case 207: |
||
342 | if ($raw === true) { |
||
343 | return $result['response']; |
||
344 | } else { |
||
345 | return $this->simplify($result['response'], $include_vcards); |
||
346 | } |
||
347 | } |
||
348 | |||
349 | throw new \Exception( |
||
350 | "Woops, something's gone wrong! The CardDAV server returned the http status code {$result['http_code']}.", |
||
351 | self::EXCEPTION_WRONG_HTTP_STATUS_CODE_GET |
||
352 | ); |
||
353 | |||
354 | } |
||
355 | |||
356 | /** |
||
357 | * Gets a clean vCard from the CardDAV server |
||
358 | * |
||
359 | * @param string $vcard_id vCard id on the CardDAV server |
||
360 | * @return string vCard (text/vcard) |
||
361 | */ |
||
362 | public function getVcard($vcard_id) |
||
400 | |||
401 | /** |
||
402 | * Gets a vCard + XML from the CardDAV Server |
||
403 | * |
||
404 | * @param string $vcard_id vCard id on the CardDAV Server |
||
405 | * @return string Raw or simplified vCard (text/xml) |
||
406 | */ |
||
407 | public function getXmlVcard($vcard_id) |
||
408 | { |
||
409 | $vcard_id = str_replace($this->url_vcard_extension, null, $vcard_id); |
||
410 | |||
411 | $xml = new XMLWriter(); |
||
412 | $xml->openMemory(); |
||
413 | $xml->setIndent(4); |
||
414 | $xml->startDocument('1.0', 'utf-8'); |
||
415 | $xml->startElement('C:addressbook-multiget'); |
||
416 | $xml->writeAttribute('xmlns:D', 'DAV:'); |
||
417 | $xml->writeAttribute('xmlns:C', 'urn:ietf:params:xml:ns:carddav'); |
||
418 | $xml->startElement('D:prop'); |
||
419 | $xml->writeElement('D:getetag'); |
||
420 | $xml->writeElement('D:getlastmodified'); |
||
421 | $xml->endElement(); |
||
422 | $xml->writeElement('D:href', $this->url_parts['path'] . $vcard_id . $this->url_vcard_extension); |
||
423 | $xml->endElement(); |
||
424 | $xml->endDocument(); |
||
425 | |||
426 | $result = $this->query($this->url, 'REPORT', $xml->outputMemory(), 'text/xml'); |
||
427 | |||
428 | switch ($result['http_code']) |
||
429 | { |
||
430 | case 200: |
||
431 | case 207: |
||
432 | return $this->simplify($result['response'], true); |
||
433 | |||
434 | } |
||
435 | |||
436 | throw new \Exception( |
||
437 | "Woops, something's gone wrong! The CardDAV server returned the http status code {$result['http_code']}.", |
||
438 | self::EXCEPTION_WRONG_HTTP_STATUS_CODE_GET_XML_VCARD |
||
439 | ); |
||
440 | } |
||
441 | |||
442 | /** |
||
443 | * Enables the debug mode |
||
444 | * |
||
445 | * @return void |
||
446 | */ |
||
447 | public function enableDebug() |
||
448 | { |
||
449 | $this->debug = true; |
||
450 | } |
||
451 | |||
452 | /** |
||
453 | * Checks if the CardDAV server is reachable |
||
454 | * |
||
455 | * @return boolean |
||
456 | */ |
||
457 | public function checkConnection() |
||
458 | { |
||
459 | $result = $this->query($this->url, 'OPTIONS'); |
||
460 | |||
461 | if ($result['http_code'] === 200) { |
||
462 | return true; |
||
463 | } else { |
||
464 | return false; |
||
465 | } |
||
466 | } |
||
467 | |||
468 | /** |
||
469 | * Cleans the vCard |
||
470 | * |
||
471 | * @param string $vcard vCard |
||
472 | * @return string $vcard vCard |
||
473 | */ |
||
474 | private function cleanVcard($vcard) |
||
475 | { |
||
476 | $vcard = str_replace("\t", null, $vcard); |
||
477 | |||
478 | return $vcard; |
||
479 | } |
||
480 | |||
481 | /** |
||
482 | * Deletes an entry from the CardDAV server |
||
483 | * |
||
484 | * @param string $vcard_id vCard id on the CardDAV server |
||
485 | * @return boolean |
||
486 | */ |
||
487 | public function delete($vcard_id) |
||
488 | { |
||
489 | $result = $this->query($this->url . $vcard_id . $this->url_vcard_extension, 'DELETE'); |
||
490 | |||
491 | switch ($result['http_code']) |
||
492 | { |
||
493 | case 204: |
||
494 | return true; |
||
495 | } |
||
496 | |||
497 | throw new \Exception( |
||
498 | "Woops, something's gone wrong! The CardDAV server returned the http status code {$result['http_code']}.", |
||
499 | self::EXCEPTION_WRONG_HTTP_STATUS_CODE_DELETE |
||
500 | ); |
||
501 | } |
||
502 | |||
503 | /** |
||
504 | * Adds an entry to the CardDAV server |
||
505 | * |
||
506 | * @param string $vcard vCard |
||
507 | * @param string $vcard_id vCard id on the CardDAV server |
||
508 | * @return string The new vCard id |
||
509 | */ |
||
510 | public function add($vcard, $vcard_id = null) |
||
511 | { |
||
512 | if ($vcard_id === null) { |
||
513 | $vcard_id = $this->generateVcardId(); |
||
514 | } |
||
515 | $vcard = $this->cleanVcard($vcard); |
||
516 | $result = $this->query($this->url . $vcard_id . $this->url_vcard_extension, 'PUT', $vcard, 'text/vcard'); |
||
517 | |||
518 | switch($result['http_code']) |
||
519 | { |
||
520 | case 201: |
||
521 | return $vcard_id; |
||
522 | } |
||
523 | |||
524 | throw new \Exception( |
||
525 | "Woops, something's gone wrong! The CardDAV server returned the http status code {$result['http_code']}.", |
||
526 | self::EXCEPTION_WRONG_HTTP_STATUS_CODE_ADD |
||
527 | ); |
||
528 | } |
||
529 | |||
530 | /** |
||
531 | * Updates an entry to the CardDAV server |
||
532 | * |
||
533 | * @param string $vcard vCard |
||
534 | * @param string $vcard_id vCard id on the CardDAV server |
||
535 | * @return boolean |
||
536 | */ |
||
537 | public function update($vcard, $vcard_id) |
||
538 | { |
||
539 | try { |
||
540 | return $this->add($vcard, $vcard_id); |
||
541 | } catch (Exception $e) { |
||
542 | throw new \Exception($e->getMessage(), self::EXCEPTION_WRONG_HTTP_STATUS_CODE_UPDATE); |
||
543 | } |
||
544 | } |
||
545 | |||
546 | /** |
||
547 | * Simplify CardDAV XML response |
||
548 | * |
||
549 | * @param string $response CardDAV XML response |
||
550 | * @param boolean $include_vcards Include vCards or not |
||
551 | * @return string Simplified CardDAV XML response |
||
552 | */ |
||
553 | private function simplify($response, $include_vcards = true) |
||
621 | |||
622 | /** |
||
623 | * Cleans CardDAV XML response |
||
624 | * |
||
625 | * @param string $response CardDAV XML response |
||
626 | * @return string $response Cleaned CardDAV XML response |
||
627 | */ |
||
628 | private function cleanResponse($response) |
||
629 | { |
||
630 | $response = utf8_encode($response); |
||
631 | $response = str_replace('D:', null, $response); |
||
632 | $response = str_replace('d:', null, $response); |
||
633 | $response = str_replace('C:', null, $response); |
||
634 | $response = str_replace('c:', null, $response); |
||
635 | |||
636 | return $response; |
||
638 | |||
639 | /** |
||
640 | * Curl initialization |
||
641 | * |
||
642 | * @return void |
||
643 | */ |
||
644 | public function curlInit() |
||
667 | |||
668 | /** |
||
669 | * Query the CardDAV server via curl and returns the response |
||
670 | * |
||
671 | * @param string $url CardDAV server URL |
||
672 | * @param string $method HTTP method like (OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE) |
||
673 | * @param string $content Content for CardDAV queries |
||
674 | * @param string $content_type Set content type |
||
675 | * @return array Raw CardDAV Response and http status code |
||
676 | */ |
||
677 | private function query($url, $method, $content = null, $content_type = null) |
||
721 | |||
722 | /** |
||
723 | * Returns a valid and unused vCard id |
||
724 | * |
||
725 | * @return string $vcard_id Valid vCard id |
||
726 | */ |
||
727 | private function generateVcardId() |
||
754 | |||
755 | /** |
||
756 | * Destructor |
||
757 | * Close curl connection if it's open |
||
758 | * |
||
759 | * @return void |
||
760 | */ |
||
761 | public function __destruct() |
||
767 | } |
||
768 | |||
770 |
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.