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 CopyRequest 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 CopyRequest, and based on these observations, apply Extract Interface, too.
| 1 | <?php | ||
| 13 | class CopyRequest | ||
| 14 | { | ||
| 15 | private $scheme; | ||
| 16 | private $user; | ||
| 17 | private $pass; | ||
| 18 | private $host; | ||
| 19 | private $port; | ||
| 20 | private $path; | ||
| 21 | private $query = array(); | ||
| 22 | |||
| 23 | /** @var [string => string] */ | ||
| 24 | private $headers = array(); | ||
| 25 | |||
| 26 | /** @var string */ | ||
| 27 | private $destination; | ||
| 28 | |||
| 29 | /** @var resource<stream<plainfile>> */ | ||
| 30 | private $fp; | ||
| 31 | |||
| 32 | private $success = false; | ||
| 33 | |||
| 34 | private static $defaultCurlOptions = array( | ||
| 35 | CURLOPT_HTTPGET => true, | ||
| 36 | CURLOPT_FOLLOWLOCATION => true, | ||
| 37 | CURLOPT_MAXREDIRS => 20, | ||
| 38 | CURLOPT_ENCODING => '', | ||
| 39 | ); | ||
| 40 | |||
| 41 | private $githubDomains = array(); | ||
| 42 | private $gitlabDomains = array(); | ||
| 43 | |||
| 44 | private static $NSS_CIPHERS = array( | ||
| 45 | 'rsa_3des_sha', | ||
| 46 | 'rsa_des_sha', | ||
| 47 | 'rsa_null_md5', | ||
| 48 | 'rsa_null_sha', | ||
| 49 | 'rsa_rc2_40_md5', | ||
| 50 | 'rsa_rc4_128_md5', | ||
| 51 | 'rsa_rc4_128_sha', | ||
| 52 | 'rsa_rc4_40_md5', | ||
| 53 | 'fips_des_sha', | ||
| 54 | 'fips_3des_sha', | ||
| 55 | 'rsa_des_56_sha', | ||
| 56 | 'rsa_rc4_56_sha', | ||
| 57 | 'rsa_aes_128_sha', | ||
| 58 | 'rsa_aes_256_sha', | ||
| 59 | 'rsa_aes_128_gcm_sha_256', | ||
| 60 | 'dhe_rsa_aes_128_gcm_sha_256', | ||
| 61 | 'ecdh_ecdsa_null_sha', | ||
| 62 | 'ecdh_ecdsa_rc4_128_sha', | ||
| 63 | 'ecdh_ecdsa_3des_sha', | ||
| 64 | 'ecdh_ecdsa_aes_128_sha', | ||
| 65 | 'ecdh_ecdsa_aes_256_sha', | ||
| 66 | 'ecdhe_ecdsa_null_sha', | ||
| 67 | 'ecdhe_ecdsa_rc4_128_sha', | ||
| 68 | 'ecdhe_ecdsa_3des_sha', | ||
| 69 | 'ecdhe_ecdsa_aes_128_sha', | ||
| 70 | 'ecdhe_ecdsa_aes_256_sha', | ||
| 71 | 'ecdh_rsa_null_sha', | ||
| 72 | 'ecdh_rsa_128_sha', | ||
| 73 | 'ecdh_rsa_3des_sha', | ||
| 74 | 'ecdh_rsa_aes_128_sha', | ||
| 75 | 'ecdh_rsa_aes_256_sha', | ||
| 76 | 'echde_rsa_null', | ||
| 77 | 'ecdhe_rsa_rc4_128_sha', | ||
| 78 | 'ecdhe_rsa_3des_sha', | ||
| 79 | 'ecdhe_rsa_aes_128_sha', | ||
| 80 | 'ecdhe_rsa_aes_256_sha', | ||
| 81 | 'ecdhe_ecdsa_aes_128_gcm_sha_256', | ||
| 82 | 'ecdhe_rsa_aes_128_gcm_sha_256', | ||
| 83 | ); | ||
| 84 | |||
| 85 | /** | ||
| 86 | * @param string $url | ||
| 87 | * @param string $destination | ||
| 88 | * @param bool $useRedirector | ||
| 89 | * @param IO\IOInterface $io | ||
| 90 | * @param Config $config | ||
| 91 | */ | ||
| 92 | 7 | public function __construct($url, $destination, $useRedirector, IO\IOInterface $io, Config $config) | |
| 100 | |||
| 101 | 6 | public function __destruct() | |
| 102 |     { | ||
| 103 | 6 |         if ($this->fp) { | |
| 104 | 6 | fclose($this->fp); | |
| 105 | 6 | } | |
| 106 | |||
| 107 | 6 |         if (!$this->success) { | |
| 108 | 6 |             if (file_exists($this->destination)) { | |
| 109 | 6 | unlink($this->destination); | |
| 110 | 6 | } | |
| 111 | 6 | } | |
| 112 | 6 | } | |
| 113 | |||
| 114 | /** | ||
| 115 | * @return string | ||
| 116 | */ | ||
| 117 | 4 | public function getURL() | |
| 118 |     { | ||
| 119 | 4 | $url = self::ifOr($this->scheme, '', '://'); | |
| 120 | 4 |         if ($this->user) { | |
| 121 | 1 | $user = $this->user; | |
| 122 | 1 | $user .= self::ifOr($this->pass, ':'); | |
| 123 | 1 | $url .= $user . '@'; | |
| 124 | 1 | } | |
| 125 | 4 | $url .= self::ifOr($this->host); | |
| 126 | 4 | $url .= self::ifOr($this->port, ':'); | |
| 127 | 4 | $url .= self::ifOr($this->path); | |
| 128 | 4 | $url .= self::ifOr(http_build_query($this->query), '?'); | |
| 129 | 4 | return $url; | |
| 130 | } | ||
| 131 | |||
| 132 | /** | ||
| 133 | * @return string user/pass/access_token masked url | ||
| 134 | */ | ||
| 135 | 1 | public function getMaskedURL() | |
| 143 | |||
| 144 | 5 | private static function ifOr($str, $pre = '', $post = '') | |
| 151 | |||
| 152 | /** | ||
| 153 | * @param string $url | ||
| 154 | */ | ||
| 155 | 7 | public function setURL($url) | |
| 156 |     { | ||
| 157 | 7 | $struct = parse_url($url); | |
| 158 | 7 |         foreach ($struct as $key => $val) { | |
| 159 | 7 |             if ($key === 'query') { | |
| 160 | 3 | parse_str($val, $this->query); | |
| 161 | 3 |             } else { | |
| 162 | 7 | $this->$key = $val; | |
| 163 | } | ||
| 164 | 7 | } | |
| 165 | 7 | } | |
| 166 | |||
| 167 | 1 | public function addParam($key, $val) | |
| 171 | |||
| 172 | 1 | public function addHeader($key, $val) | |
| 176 | |||
| 177 | 1 | public function makeSuccess() | |
| 181 | |||
| 182 | /** | ||
| 183 | * @return array | ||
| 184 | */ | ||
| 185 | 2 | public function getCurlOptions() | |
| 186 |     { | ||
| 187 | 2 | $headers = array(); | |
| 188 | 2 |         foreach ($this->headers as $key => $val) { | |
| 189 | 1 | $headers[] = strtr(ucwords(strtr($key, '-', ' ')), ' ', '-') . ': ' . $val; | |
| 190 | 2 | } | |
| 191 | |||
| 192 | 2 | $url = $this->getURL(); | |
| 193 | |||
| 194 | $curlOpts = array( | ||
| 195 | 2 | CURLOPT_URL => $url, | |
| 196 | 2 | CURLOPT_HTTPHEADER => $headers, | |
| 197 | 2 | CURLOPT_USERAGENT => ConfigFacade::getUserAgent(), | |
| 198 | 2 | CURLOPT_FILE => $this->fp, | |
| 199 | //CURLOPT_VERBOSE => true, //for debug | ||
| 200 | 2 | ); | |
| 201 | 2 | $curlOpts += self::$defaultCurlOptions; | |
| 202 | |||
| 203 | 2 |         if ($ciphers = $this->nssCiphers()) { | |
| 204 | $curlOpts[CURLOPT_SSL_CIPHER_LIST] = $ciphers; | ||
| 205 | } | ||
| 206 | 2 |         if ($proxy = $this->getProxy($url)) { | |
| 207 | 1 | $curlOpts[CURLOPT_PROXY] = $proxy; | |
| 208 | 1 | } | |
| 209 | |||
| 210 | 2 | return $curlOpts; | |
| 211 | } | ||
| 212 | |||
| 213 | /** | ||
| 214 | * @param IO\IOInterface $io | ||
| 215 | * @param bool $useRedirector | ||
| 216 | */ | ||
| 217 | 6 | private function setupAuthentication(IO\IOInterface $io, $useRedirector) | |
| 218 |     { | ||
| 219 | 6 |         if (preg_match('/\.github\.com$/', $this->host)) { | |
| 220 | 1 | $authKey = 'github.com'; | |
| 221 | 1 |             if ($useRedirector) { | |
| 222 | 1 |                 if ($this->host === 'api.github.com' && preg_match('%^/repos(/[^/]+/[^/]+/)zipball(.+)$%', $this->path, $_)) { | |
| 223 | 1 | $this->host = 'codeload.github.com'; | |
| 224 | 1 | $this->path = $_[1] . 'legacy.zip' . $_[2]; | |
| 225 | 1 | } | |
| 226 | 1 | } | |
| 227 | 1 |         } else { | |
| 228 | 5 | $authKey = $this->host; | |
| 229 | } | ||
| 230 | 6 |         if (!$io->hasAuthentication($authKey)) { | |
| 231 | 4 |             if ($this->user || $this->pass) { | |
| 232 | 2 | $io->setAuthentication($authKey, $this->user, $this->pass); | |
| 233 | 2 |             } else { | |
| 234 | 2 | return; | |
| 235 | } | ||
| 236 | 2 | } | |
| 237 | |||
| 238 | 4 | $auth = $io->getAuthentication($authKey); | |
| 239 | |||
| 240 | // is github | ||
| 241 | 4 |         if (in_array($authKey, $this->githubDomains) && 'x-oauth-basic' === $auth['password']) { | |
| 242 | 1 |             $this->addParam('access_token', $auth['username']); | |
| 243 | 1 | $this->user = $this->pass = null; | |
| 244 | 1 | return; | |
| 245 | } | ||
| 246 | // is gitlab | ||
| 247 | 3 |         if (in_array($authKey, $this->gitlabDomains) && 'oauth2' === $auth['password']) { | |
| 248 | 1 |             $this->addHeader('authorization', 'Bearer ' . $auth['username']); | |
| 249 | 1 | $this->user = $this->pass = null; | |
| 250 | 1 | return; | |
| 251 | } | ||
| 252 | // others, includes bitbucket | ||
| 253 | 2 | $this->user = $auth['username']; | |
| 254 | 2 | $this->pass = $auth['password']; | |
| 255 | 2 | } | |
| 256 | |||
| 257 | 2 | private function getProxy($url) | |
| 258 |     { | ||
| 259 | 2 |         if (isset($_SERVER['no_proxy'])) { | |
| 260 | 1 | $pattern = new Util\NoProxyPattern($_SERVER['no_proxy']); | |
| 261 | 1 |             if ($pattern->test($url)) { | |
| 262 | 1 | return null; | |
| 263 | } | ||
| 264 | 1 | } | |
| 265 | |||
| 266 | 2 | View Code Duplication |         if ($this->scheme === 'https') { | 
| 267 | 1 |             if (isset($_SERVER['HTTPS_PROXY'])) { | |
| 268 | return $_SERVER['HTTPS_PROXY']; | ||
| 269 | } | ||
| 270 | 1 |             if (isset($_SERVER['https_proxy'])) { | |
| 271 | return $_SERVER['https_proxy']; | ||
| 272 | } | ||
| 273 | 1 | } | |
| 274 | |||
| 275 | 2 | View Code Duplication |         if ($this->scheme === 'http') { | 
| 276 | 1 |             if (isset($_SERVER['HTTP_PROXY'])) { | |
| 277 | 1 | return $_SERVER['HTTP_PROXY']; | |
| 278 | } | ||
| 279 |             if (isset($_SERVER['http_proxy'])) { | ||
| 280 | return $_SERVER['http_proxy']; | ||
| 281 | } | ||
| 282 | } | ||
| 283 | 1 | return null; | |
| 284 | } | ||
| 285 | |||
| 286 | /** | ||
| 287 | * enable ECC cipher suites in cURL/NSS | ||
| 288 | */ | ||
| 289 | 2 | public static function nssCiphers() | |
| 301 | |||
| 302 | /** | ||
| 303 | * @param string | ||
| 304 | */ | ||
| 305 | 7 | public function setDestination($destination) | |
| 306 |     { | ||
| 307 | 7 | $this->destination = $destination; | |
| 323 | |||
| 324 | 6 | private function createDir($fileName) | |
| 335 | } | ||
| 336 | 
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..