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.