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 Autodiscover 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 Autodiscover, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
35 | class Autodiscover |
||
36 | { |
||
37 | /** |
||
38 | * The path appended to the various schemes and hostnames used during |
||
39 | * autodiscovery. |
||
40 | * |
||
41 | * @var string |
||
42 | */ |
||
43 | const AUTODISCOVER_PATH = '/autodiscover/autodiscover.xml'; |
||
44 | |||
45 | /** |
||
46 | * Server was discovered using the TLD method. |
||
47 | * |
||
48 | * @var integer |
||
49 | */ |
||
50 | const AUTODISCOVERED_VIA_TLD = 10; |
||
51 | |||
52 | /** |
||
53 | * Server was discovered using the subdomain method. |
||
54 | * |
||
55 | * @var integer |
||
56 | */ |
||
57 | const AUTODISCOVERED_VIA_SUBDOMAIN = 11; |
||
58 | |||
59 | /** |
||
60 | * Server was discovered using the unauthenticated GET method. |
||
61 | * |
||
62 | * @var integer |
||
63 | */ |
||
64 | const AUTODISCOVERED_VIA_UNAUTHENTICATED_GET = 12; |
||
65 | |||
66 | /** |
||
67 | * Server was discovered using the DNS SRV redirect method. |
||
68 | * |
||
69 | * @var integer |
||
70 | */ |
||
71 | const AUTODISCOVERED_VIA_SRV_RECORD = 13; |
||
72 | |||
73 | /** |
||
74 | * Server was discovered using the HTTP redirect method. |
||
75 | * |
||
76 | * @var integer |
||
77 | * |
||
78 | * @todo We do not currently support this. |
||
79 | */ |
||
80 | const AUTODISCOVERED_VIA_RESPONSE_REDIRECT = 14; |
||
81 | |||
82 | /** |
||
83 | * The email address to attempt autodiscovery against. |
||
84 | * |
||
85 | * @var string |
||
86 | */ |
||
87 | protected $email; |
||
88 | |||
89 | /** |
||
90 | * The password to present during autodiscovery. |
||
91 | * |
||
92 | * @var string |
||
93 | */ |
||
94 | protected $password; |
||
95 | |||
96 | /** |
||
97 | * The Exchange username to use during authentication. If unspecified, |
||
98 | * the provided email address will be used as the username. |
||
99 | * |
||
100 | * @var string |
||
101 | */ |
||
102 | protected $username; |
||
103 | |||
104 | /** |
||
105 | * The top-level domain name, extracted from the provided email address. |
||
106 | * |
||
107 | * @var string |
||
108 | */ |
||
109 | protected $tld; |
||
110 | |||
111 | /** |
||
112 | * The Autodiscover XML request. Since it's used repeatedly, it's cached |
||
113 | * in this property to avoid redundant re-generation. |
||
114 | * |
||
115 | * @var string |
||
116 | */ |
||
117 | protected $requestxml; |
||
118 | |||
119 | /** |
||
120 | * The Certificate Authority path. Should point to a directory containing |
||
121 | * one or more certificates to use in SSL verification. |
||
122 | * |
||
123 | * @var string |
||
124 | */ |
||
125 | protected $capath; |
||
126 | |||
127 | /** |
||
128 | * The path to a specific Certificate Authority file. Get one and use it |
||
129 | * for full Autodiscovery compliance. |
||
130 | * |
||
131 | * @var string |
||
132 | * |
||
133 | * @link http://curl.haxx.se/ca/cacert.pem |
||
134 | * @link http://curl.haxx.se/ca/ |
||
135 | */ |
||
136 | protected $cainfo; |
||
137 | |||
138 | /** |
||
139 | * Skip SSL verification. Bad idea, and violates the strict Autodiscover |
||
140 | * protocol. But, here in case you have no other option. |
||
141 | * Defaults to FALSE. |
||
142 | * |
||
143 | * @var boolean |
||
144 | */ |
||
145 | protected $skip_ssl_verification = false; |
||
146 | |||
147 | /** |
||
148 | * The body of the last response. |
||
149 | * |
||
150 | * @var string |
||
151 | */ |
||
152 | public $last_response; |
||
153 | |||
154 | /** |
||
155 | * An associative array of response headers that resulted from the |
||
156 | * last request. Keys are lowercased for easy checking. |
||
157 | * |
||
158 | * @var array |
||
159 | */ |
||
160 | public $last_response_headers; |
||
161 | |||
162 | /** |
||
163 | * The output of curl_info() relating to the most recent cURL request. |
||
164 | * |
||
165 | * @var array |
||
166 | */ |
||
167 | public $last_info; |
||
168 | |||
169 | /** |
||
170 | * The cURL error code associated with the most recent cURL request. |
||
171 | * |
||
172 | * @var integer |
||
173 | */ |
||
174 | public $last_curl_errno; |
||
175 | |||
176 | /** |
||
177 | * Human-readable description of the most recent cURL error. |
||
178 | * |
||
179 | * @var string |
||
180 | */ |
||
181 | public $last_curl_error; |
||
182 | |||
183 | /** |
||
184 | * The value in seconds to use for Autodiscover host connection timeouts. |
||
185 | * Default connection timeout is 2 seconds, so that unresponsive methods |
||
186 | * can be bypassed quickly. |
||
187 | * |
||
188 | * @var integer |
||
189 | */ |
||
190 | public $connection_timeout = 2; |
||
191 | |||
192 | /** |
||
193 | * Information about an Autodiscover Response containing an error will |
||
194 | * be stored here. |
||
195 | * |
||
196 | * @var mixed |
||
197 | */ |
||
198 | public $error = false; |
||
199 | |||
200 | /** |
||
201 | * Information about an Autodiscover Response with a redirect will be |
||
202 | * retained here. |
||
203 | * |
||
204 | * @var mixed |
||
205 | */ |
||
206 | public $redirect = false; |
||
207 | |||
208 | /** |
||
209 | * A successful, non-error and non-redirect parsed Autodiscover response |
||
210 | * will be stored here. |
||
211 | * |
||
212 | * @var mixed |
||
213 | */ |
||
214 | public $discovered = null; |
||
215 | |||
216 | /** |
||
217 | * Constructor for the EWSAutodiscover class. |
||
218 | * |
||
219 | * @param string $email |
||
220 | * @param string $password |
||
221 | * @param string $username If left blank, the email provided will be used. |
||
222 | */ |
||
223 | public function __construct($email, $password, $username = null) |
||
235 | |||
236 | /** |
||
237 | * Execute the full discovery chain of events in the correct sequence |
||
238 | * until a valid response is received, or all methods have failed. |
||
239 | * |
||
240 | * @return An AUTODISCOVERED_VIA_* constant or FALSE on failure. |
||
241 | * |
||
242 | * @todo Throw an exception on failure. |
||
243 | */ |
||
244 | public function discover() |
||
262 | |||
263 | /** |
||
264 | * Return the settings discovered from the Autodiscover process. |
||
265 | * |
||
266 | * NULL indicates discovery hasn't completed (or been attempted) |
||
267 | * FALSE indicates discovery wasn't successful. Check for errors |
||
268 | * or redirects. |
||
269 | * An array will be returned with discovered settings on success. |
||
270 | * |
||
271 | * @return mixed |
||
272 | */ |
||
273 | public function discoveredSettings() |
||
277 | |||
278 | /** |
||
279 | * Toggle skipping of SSL verification in cURL requests. |
||
280 | * |
||
281 | * @param boolean $skip To skip, or not. |
||
282 | * @return self |
||
283 | */ |
||
284 | public function skipSSLVerification($skip = true) |
||
290 | |||
291 | /** |
||
292 | * Parse the hex ServerVersion value and return a valid |
||
293 | * Client::VERSION_* constant. |
||
294 | * |
||
295 | * @return string|boolean A known version constant, or FALSE if it could not |
||
296 | * be determined. |
||
297 | * |
||
298 | * @link http://msdn.microsoft.com/en-us/library/bb204122(v=exchg.140).aspx |
||
299 | * @link http://blogs.msdn.com/b/pcreehan/archive/2009/09/21/parsing-serverversion-when-an-int-is-really-5-ints.aspx |
||
300 | * @link http://office.microsoft.com/en-us/outlook-help/determine-the-version-of-microsoft-exchange-server-my-account-connects-to-HA001191800.aspx |
||
301 | * |
||
302 | * @todo Update to include Exchange 2013 versions. |
||
303 | */ |
||
304 | public function parseServerVersion($version_hex) |
||
331 | |||
332 | /** |
||
333 | * Method to return a new Client object, auto-configured |
||
334 | * with the proper hostname. |
||
335 | * |
||
336 | * @return mixed Client object on success, FALSE on failure. |
||
337 | */ |
||
338 | public function newEWS() |
||
387 | |||
388 | /** |
||
389 | * Static method may fail if there are issues surrounding SSL certificates. |
||
390 | * In such cases, set up the object as needed, and then call newEWS(). |
||
391 | * |
||
392 | * @param string $email |
||
393 | * @param string $password |
||
394 | * @param string $username If left blank, the email provided will be used. |
||
395 | * @return mixed |
||
396 | */ |
||
397 | public static function getEWS($email, $password, $username = null) |
||
402 | |||
403 | /** |
||
404 | * Perform an NTLM authenticated HTTPS POST to the top-level |
||
405 | * domain of the email address. |
||
406 | * |
||
407 | * @return An AUTODISCOVERED_VIA_* constant or FALSE on failure. |
||
408 | */ |
||
409 | public function tryTLD() |
||
414 | |||
415 | /** |
||
416 | * Perform an NTLM authenticated HTTPS POST to the 'autodiscover' |
||
417 | * subdomain of the email address' TLD. |
||
418 | * |
||
419 | * @return An AUTODISCOVERED_VIA_* constant or FALSE on failure. |
||
420 | */ |
||
421 | public function trySubdomain() |
||
428 | |||
429 | /** |
||
430 | * Perform an unauthenticated HTTP GET in an attempt to get redirected |
||
431 | * via 302 to the correct location to perform the HTTPS POST. |
||
432 | * |
||
433 | * @return An AUTODISCOVERED_VIA_* constant or FALSE on failure. |
||
434 | */ |
||
435 | public function trySubdomainUnauthenticatedGet() |
||
469 | |||
470 | /** |
||
471 | * Attempt to retrieve the autodiscover host from an SRV DNS record. |
||
472 | * |
||
473 | * @link http://support.microsoft.com/kb/940881 |
||
474 | * @return self::AUTODISCOVERED_VIA_SRV_RECORD or false |
||
475 | */ |
||
476 | public function trySRVRecord() |
||
490 | |||
491 | /** |
||
492 | * Set the path to the file to be used by CURLOPT_CAINFO. |
||
493 | * |
||
494 | * @param string $path Path to a certificate file such as cacert.pem |
||
495 | * @return self |
||
496 | */ |
||
497 | public function setCAInfo($path) |
||
505 | |||
506 | /** |
||
507 | * Set the path to the file to be used by CURLOPT_CAPATH. |
||
508 | * |
||
509 | * @param string $path Path to a directory containing one or more CA |
||
510 | * certificates. |
||
511 | * @return self |
||
512 | */ |
||
513 | public function setCAPath($path) |
||
521 | |||
522 | /** |
||
523 | * Set a connection timeout for the POST methods. |
||
524 | * |
||
525 | * @param integer $seconds Seconds to wait for a connection. |
||
526 | * @return self |
||
527 | */ |
||
528 | public function setConnectionTimeout($seconds) |
||
534 | |||
535 | /** |
||
536 | * Perform the NTLM authenticated post against one of the chosen |
||
537 | * endpoints. |
||
538 | * |
||
539 | * @param string $url URL to try posting to |
||
540 | * @param integer $timeout Overall cURL timeout for this request |
||
541 | * @return boolean |
||
542 | */ |
||
543 | public function doNTLMPost($url, $timeout = 6) |
||
594 | |||
595 | /** |
||
596 | * Parse the Autoresponse Payload, particularly to determine if an |
||
597 | * additional request is necessary. |
||
598 | * |
||
599 | * @return boolean|array FALSE if response isn't XML or parsed response |
||
600 | * array. |
||
601 | */ |
||
602 | protected function parseAutodiscoverResponse() |
||
634 | |||
635 | /** |
||
636 | * Set the top-level domain to be used with autodiscover attempts based |
||
637 | * on the provided email address. |
||
638 | * |
||
639 | * @return boolean |
||
640 | */ |
||
641 | protected function setTLD() |
||
651 | |||
652 | /** |
||
653 | * Reset the response-related structures. Called before making a new |
||
654 | * request. |
||
655 | * |
||
656 | * @return self |
||
657 | */ |
||
658 | public function reset() |
||
667 | |||
668 | /** |
||
669 | * Return the generated Autodiscover XML request body. |
||
670 | * |
||
671 | * @return string |
||
672 | */ |
||
673 | public function getAutodiscoverRequest() |
||
701 | |||
702 | /** |
||
703 | * Utility function to pick headers off of the incoming cURL response. |
||
704 | * Used with CURLOPT_HEADERFUNCTION. |
||
705 | * |
||
706 | * @param resource $_ch cURL handle |
||
707 | * @param string $str Header string to read |
||
708 | * @return integer Bytes read |
||
709 | */ |
||
710 | public function readHeaders($_ch, $str) |
||
721 | |||
722 | /** |
||
723 | * Utility function to parse XML payloads from the response into easier |
||
724 | * to manage associative arrays. |
||
725 | * |
||
726 | * @param string $xml XML to parse |
||
727 | * @return array |
||
728 | */ |
||
729 | public function responseToArray($xml) |
||
737 | |||
738 | /** |
||
739 | * Recursive method for parsing DOM nodes. |
||
740 | * |
||
741 | * @param \DOMElement $node DOMNode object. |
||
742 | * @return mixed |
||
743 | * |
||
744 | * @link https://github.com/gaarf/XML-string-to-PHP-array |
||
745 | */ |
||
746 | protected function nodeToArray($node) |
||
795 | |||
796 | /** |
||
797 | * Parses the version of an Exchange 2007 server. |
||
798 | * |
||
799 | * @param integer $minorversion Minor server version. |
||
800 | * @return string Server version. |
||
801 | */ |
||
802 | View Code Duplication | protected function parseVersion2007($minorversion) { |
|
816 | |||
817 | /** |
||
818 | * Parses the version of an Exchange 2010 server. |
||
819 | * |
||
820 | * @param integer $minorversion Minor server version. |
||
821 | * @return string Server version. |
||
822 | */ |
||
823 | View Code Duplication | protected function parseVersion2010($minorversion) { |
|
835 | |||
836 | /** |
||
837 | * Parses the version of an Exchange 2013 server. |
||
838 | * |
||
839 | * @param integer $majorbuild Major build version. |
||
840 | * @return string Server version. |
||
841 | */ |
||
842 | protected function parseVersion2013($majorbuild) { |
||
847 | |||
848 | /** |
||
849 | * Parses the version of an Exchange 2016 server. |
||
850 | * |
||
851 | * @param integer $majorbuild Major build version. |
||
852 | * @return string Server version. |
||
853 | */ |
||
854 | protected function parseVersion2016($majorbuild) { |
||
857 | |||
858 | /** |
||
859 | * Attempts an autodiscover via a URL. |
||
860 | * |
||
861 | * @param string $url Url to attempt an autodiscover. |
||
862 | * @return boolean |
||
863 | */ |
||
864 | protected function tryViaUrl($url, $timeout = 6) |
||
869 | } |
||
870 |
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break
.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.