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 Requests_IDNAEncoder 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 Requests_IDNAEncoder, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 13 | * @see https://tools.ietf.org/html/rfc3492 Punycode/Bootstrap specification |
||
| 14 | */ |
||
| 15 | class IDNAEncoder { |
||
| 16 | /** |
||
| 17 | * ACE prefix used for IDNA |
||
| 18 | * |
||
| 19 | * @see https://tools.ietf.org/html/rfc3490#section-5 |
||
| 20 | * @var string |
||
| 21 | */ |
||
| 22 | const ACE_PREFIX = 'xn--'; |
||
| 23 | |||
| 24 | /**#@+ |
||
| 25 | * Bootstrap constant for Punycode |
||
| 26 | * |
||
| 27 | * @see https://tools.ietf.org/html/rfc3492#section-5 |
||
| 28 | * @var int |
||
| 29 | */ |
||
| 30 | const BOOTSTRAP_BASE = 36; |
||
| 31 | const BOOTSTRAP_TMIN = 1; |
||
| 32 | const BOOTSTRAP_TMAX = 26; |
||
| 33 | const BOOTSTRAP_SKEW = 38; |
||
| 34 | const BOOTSTRAP_DAMP = 700; |
||
| 35 | const BOOTSTRAP_INITIAL_BIAS = 72; |
||
| 36 | const BOOTSTRAP_INITIAL_N = 128; |
||
| 37 | /**#@-*/ |
||
| 38 | |||
| 39 | /** |
||
| 40 | * Encode a hostname using Punycode |
||
| 41 | * |
||
| 42 | * @param string $string Hostname |
||
| 43 | * @return string Punycode-encoded hostname |
||
| 44 | */ |
||
| 45 | public static function encode($string) { |
||
| 52 | |||
| 53 | /** |
||
| 54 | * Convert a UTF-8 string to an ASCII string using Punycode |
||
| 55 | * |
||
| 56 | * @throws Rmccue\Requests\Exception Provided string longer than 64 ASCII characters (`idna.provided_too_long`) |
||
| 57 | * @throws Rmccue\Requests\Exception Prepared string longer than 64 ASCII characters (`idna.prepared_too_long`) |
||
| 58 | * @throws Rmccue\Requests\Exception Provided string already begins with xn-- (`idna.provided_is_prefixed`) |
||
| 59 | * @throws Rmccue\Requests\Exception Encoded string longer than 64 ASCII characters (`idna.encoded_too_long`) |
||
| 60 | * |
||
| 61 | * @param string $string ASCII or UTF-8 string (max length 64 characters) |
||
| 62 | * @return string ASCII string |
||
| 63 | */ |
||
| 64 | public static function to_ascii($string) { |
||
| 107 | |||
| 108 | /** |
||
| 109 | * Check whether a given string contains only ASCII characters |
||
| 110 | * |
||
| 111 | * @internal (Testing found regex was the fastest implementation) |
||
| 112 | * |
||
| 113 | * @param string $string |
||
| 114 | * @return bool Is the string ASCII-only? |
||
| 115 | */ |
||
| 116 | protected static function is_ascii($string) { |
||
| 119 | |||
| 120 | /** |
||
| 121 | * Prepare a string for use as an IDNA name |
||
| 122 | * |
||
| 123 | * @todo Implement this based on RFC 3491 and the newer 5891 |
||
| 124 | * @param string $string |
||
| 125 | * @return string Prepared string |
||
| 126 | */ |
||
| 127 | protected static function nameprep($string) { |
||
| 130 | |||
| 131 | /** |
||
| 132 | * Convert a UTF-8 string to a UCS-4 codepoint array |
||
| 133 | * |
||
| 134 | * Based on Rmccue\Requests\IRI::replace_invalid_with_pct_encoding() |
||
| 135 | * |
||
| 136 | * @throws Rmccue\Requests\Exception Invalid UTF-8 codepoint (`idna.invalidcodepoint`) |
||
| 137 | * @param string $input |
||
| 138 | * @return array Unicode code points |
||
| 139 | */ |
||
| 140 | protected static function utf8_to_codepoints($input) { |
||
| 220 | |||
| 221 | /** |
||
| 222 | * RFC3492-compliant encoder |
||
| 223 | * |
||
| 224 | * @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code |
||
| 225 | * @throws Rmccue\Requests\Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`) |
||
| 226 | * |
||
| 227 | * @param string $input UTF-8 encoded string to encode |
||
| 228 | * @return string Punycode-encoded string |
||
| 229 | */ |
||
| 230 | public static function punycode_encode($input) { |
||
| 334 | |||
| 335 | /** |
||
| 336 | * Convert a digit to its respective character |
||
| 337 | * |
||
| 338 | * @see https://tools.ietf.org/html/rfc3492#section-5 |
||
| 339 | * @throws Rmccue\Requests\Exception On invalid digit (`idna.invalid_digit`) |
||
| 340 | * |
||
| 341 | * @param int $digit Digit in the range 0-35 |
||
| 342 | * @return string Single character corresponding to digit |
||
| 343 | */ |
||
| 344 | protected static function digit_to_char($digit) { |
||
| 354 | |||
| 355 | /** |
||
| 356 | * Adapt the bias |
||
| 357 | * |
||
| 358 | * @see https://tools.ietf.org/html/rfc3492#section-6.1 |
||
| 359 | * @param int $delta |
||
| 360 | * @param int $numpoints |
||
| 361 | * @param bool $firsttime |
||
| 362 | * @return int New bias |
||
| 363 | */ |
||
| 364 | protected static function adapt($delta, $numpoints, $firsttime) { |
||
| 365 | # function adapt(delta,numpoints,firsttime): |
||
| 366 | # if firsttime then let delta = delta div damp |
||
| 367 | if ($firsttime) { |
||
| 368 | $delta = floor($delta / self::BOOTSTRAP_DAMP); |
||
| 369 | } |
||
| 370 | # else let delta = delta div 2 |
||
| 371 | else { |
||
| 372 | $delta = floor($delta / 2); |
||
| 373 | } |
||
| 374 | # let delta = delta + (delta div numpoints) |
||
| 375 | $delta += floor($delta / $numpoints); |
||
| 376 | # let k = 0 |
||
| 377 | $k = 0; |
||
| 378 | # while delta > ((base - tmin) * tmax) div 2 do begin |
||
| 379 | $max = floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN) * self::BOOTSTRAP_TMAX) / 2); |
||
| 380 | while ($delta > $max) { |
||
| 381 | # let delta = delta div (base - tmin) |
||
| 382 | $delta = floor($delta / (self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN)); |
||
| 383 | # let k = k + base |
||
| 384 | $k += self::BOOTSTRAP_BASE; |
||
| 385 | # end |
||
| 386 | } |
||
| 387 | # return k + (((base - tmin + 1) * delta) div (delta + skew)) |
||
| 388 | return $k + floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN + 1) * $delta) / ($delta + self::BOOTSTRAP_SKEW)); |
||
| 389 | } |
||
| 390 | } |
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.