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 Packer 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 Packer, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 17 | class Packer |
||
| 18 | { |
||
| 19 | /** |
||
| 20 | * @var string |
||
| 21 | */ |
||
| 22 | private static $invalidUtf8Regex = '/( |
||
| 23 | [\xC0-\xC1] # Invalid UTF-8 Bytes |
||
| 24 | | [\xF5-\xFF] # Invalid UTF-8 Bytes |
||
| 25 | | \xE0[\x80-\x9F] # Overlong encoding of prior code point |
||
| 26 | | \xF0[\x80-\x8F] # Overlong encoding of prior code point |
||
| 27 | | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start |
||
| 28 | | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start |
||
| 29 | | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start |
||
| 30 | | (?<=[\x0-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle |
||
| 31 | | (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]|[\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence |
||
| 32 | | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence |
||
| 33 | | (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence |
||
| 34 | | (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2) |
||
| 35 | )/x'; |
||
| 36 | |||
| 37 | /** |
||
| 38 | * @var Collection |
||
| 39 | */ |
||
| 40 | private $transformers; |
||
| 41 | |||
| 42 | /** |
||
| 43 | * @param Collection|null $transformers |
||
| 44 | */ |
||
| 45 | 2 | public function setTransformers(Collection $transformers = null) |
|
| 49 | |||
| 50 | /** |
||
| 51 | * @return Collection|null |
||
| 52 | */ |
||
| 53 | 1 | public function getTransformers() |
|
| 54 | { |
||
| 55 | 1 | return $this->transformers; |
|
| 56 | } |
||
| 57 | |||
| 58 | 77 | public function pack($value) |
|
| 59 | { |
||
| 60 | 77 | $type = \gettype($value); |
|
| 61 | |||
| 62 | switch ($type) { |
||
| 63 | 77 | case 'array': return \array_values($value) === $value |
|
| 64 | 14 | ? $this->packArray($value) |
|
| 65 | 14 | : $this->packMap($value); |
|
| 66 | |||
| 67 | 76 | case 'string': return \preg_match(self::$invalidUtf8Regex, $value) |
|
| 68 | 23 | ? $this->packBin($value) |
|
| 69 | 23 | : $this->packStr($value); |
|
| 70 | |||
| 71 | 59 | case 'integer': return $this->packInt($value); |
|
| 72 | 21 | case 'NULL': return $this->packNil(); |
|
| 73 | 20 | case 'boolean': return $this->packBool($value); |
|
| 74 | 15 | case 'double': return $this->packDouble($value); |
|
| 75 | } |
||
| 76 | |||
| 77 | 12 | if ($value instanceof Ext) { |
|
| 78 | 9 | return $this->packExt($value); |
|
| 79 | } |
||
| 80 | |||
| 81 | 3 | if ($this->transformers && $transformer = $this->transformers->match($value)) { |
|
| 82 | 1 | $ext = new Ext($transformer->getId(), $this->pack($transformer->transform($value))); |
|
| 83 | |||
| 84 | 1 | return $this->packExt($ext); |
|
| 85 | } |
||
| 86 | |||
| 87 | 2 | throw new PackingFailedException($value, 'Unsupported type.'); |
|
| 88 | } |
||
| 89 | |||
| 90 | 7 | public function packArray(array $array) |
|
| 91 | { |
||
| 92 | 7 | $size = \count($array); |
|
| 93 | 7 | $data = self::packArrayHeader($size); |
|
| 94 | |||
| 95 | 7 | foreach ($array as $val) { |
|
| 96 | 6 | $data .= $this->pack($val); |
|
| 97 | 7 | } |
|
| 98 | |||
| 99 | 7 | return $data; |
|
| 100 | } |
||
| 101 | |||
| 102 | 7 | View Code Duplication | private static function packArrayHeader($size) |
| 103 | { |
||
| 104 | 7 | if ($size <= 0xf) { |
|
| 105 | 4 | return \chr(0x90 | $size); |
|
| 106 | } |
||
| 107 | 3 | if ($size <= 0xffff) { |
|
| 108 | 2 | return \pack('Cn', 0xdc, $size); |
|
| 109 | } |
||
| 110 | |||
| 111 | 1 | return \pack('CN', 0xdd, $size); |
|
| 112 | } |
||
| 113 | |||
| 114 | 9 | public function packMap(array $map) |
|
| 115 | { |
||
| 116 | 9 | $size = \count($map); |
|
| 117 | 9 | $data = self::packMapHeader($size); |
|
| 118 | |||
| 119 | 9 | foreach ($map as $key => $val) { |
|
| 120 | 9 | $data .= $this->pack($key); |
|
| 121 | 9 | $data .= $this->pack($val); |
|
| 122 | 9 | } |
|
| 123 | |||
| 124 | 9 | return $data; |
|
| 125 | } |
||
| 126 | |||
| 127 | 9 | View Code Duplication | private static function packMapHeader($size) |
| 128 | { |
||
| 129 | 9 | if ($size <= 0xf) { |
|
| 130 | 6 | return \chr(0x80 | $size); |
|
| 131 | } |
||
| 132 | 3 | if ($size <= 0xffff) { |
|
| 133 | 2 | return \pack('Cn', 0xde, $size); |
|
| 134 | } |
||
| 135 | |||
| 136 | 1 | return \pack('CN', 0xdf, $size); |
|
| 137 | } |
||
| 138 | |||
| 139 | 17 | public function packStr($str) |
|
| 140 | { |
||
| 141 | 17 | $len = \strlen($str); |
|
| 142 | |||
| 143 | 17 | if ($len < 32) { |
|
| 144 | 10 | return \chr(0xa0 | $len).$str; |
|
| 145 | } |
||
| 146 | 7 | if ($len <= 0xff) { |
|
| 147 | 4 | return \pack('CC', 0xd9, $len).$str; |
|
| 148 | } |
||
| 149 | 3 | if ($len <= 0xffff) { |
|
| 150 | 2 | return \pack('Cn', 0xda, $len).$str; |
|
| 151 | } |
||
| 152 | |||
| 153 | 1 | return \pack('CN', 0xdb, $len).$str; |
|
| 154 | } |
||
| 155 | |||
| 156 | 8 | public function packBin($str) |
|
| 157 | { |
||
| 158 | 8 | $len = \strlen($str); |
|
| 159 | |||
| 160 | 8 | if ($len <= 0xff) { |
|
| 161 | 6 | return \pack('CC', 0xc4, $len).$str; |
|
| 162 | } |
||
| 163 | 2 | if ($len <= 0xffff) { |
|
| 164 | 1 | return \pack('Cn', 0xc5, $len).$str; |
|
| 165 | } |
||
| 166 | |||
| 167 | 1 | return \pack('CN', 0xc6, $len).$str; |
|
| 168 | } |
||
| 169 | |||
| 170 | 10 | public function packExt(Ext $ext) |
|
| 171 | { |
||
| 172 | 10 | $type = $ext->getType(); |
|
| 173 | 10 | $data = $ext->getData(); |
|
| 174 | 10 | $len = \strlen($data); |
|
| 175 | |||
| 176 | switch ($len) { |
||
| 177 | 10 | case 1: return \pack('CC', 0xd4, $type).$data; |
|
| 178 | 8 | case 2: return \pack('CC', 0xd5, $type).$data; |
|
| 179 | 7 | case 4: return \pack('CC', 0xd6, $type).$data; |
|
| 180 | 6 | case 8: return \pack('CC', 0xd7, $type).$data; |
|
| 181 | 5 | case 16: return \pack('CC', 0xd8, $type).$data; |
|
| 182 | } |
||
| 183 | |||
| 184 | 4 | if ($len <= 0xff) { |
|
| 185 | 2 | return \pack('CCC', 0xc7, $len, $type).$data; |
|
| 186 | } |
||
| 187 | 2 | if ($len <= 0xffff) { |
|
| 188 | 1 | return \pack('CnC', 0xc8, $len, $type).$data; |
|
| 189 | } |
||
| 190 | |||
| 191 | 1 | return \pack('CNC', 0xc9, $len, $type).$data; |
|
| 192 | } |
||
| 193 | |||
| 194 | 3 | public function packNil() |
|
| 195 | { |
||
| 196 | 3 | return "\xc0"; |
|
| 197 | } |
||
| 198 | |||
| 199 | 6 | public function packBool($val) |
|
| 203 | |||
| 204 | 3 | public function packDouble($num) |
|
| 208 | |||
| 209 | 43 | public function packInt($num) |
|
| 210 | { |
||
| 211 | 43 | if ($num >= 0) { |
|
| 212 | 28 | if ($num <= 0x7f) { |
|
| 213 | 16 | return \chr($num); |
|
| 214 | } |
||
| 215 | 16 | if ($num <= 0xff) { |
|
| 216 | 6 | return \pack('CC', 0xcc, $num); |
|
| 217 | } |
||
| 218 | 12 | if ($num <= 0xffff) { |
|
| 243 | |||
| 244 | 6 | private static function packU64($code, $num) |
|
| 251 | } |
||
| 252 |
According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.
}
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.