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 Header 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 Header, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
4 | class Header |
||
5 | { |
||
6 | const CMD_PROXY = 1; |
||
7 | const CMD_LOCAL = 0; |
||
8 | const UNSPECIFIED_PROTOCOL = "\x00"; |
||
9 | const TCP4 = "\x11"; |
||
10 | const UDP4 = "\x12"; |
||
11 | const TCP6 = "\x21"; |
||
12 | const UDP6 = "\x22"; |
||
13 | const USTREAM = "\x31"; |
||
14 | const USOCK = "\x32"; |
||
15 | |||
16 | // 12 bytes. |
||
17 | protected static $signatures = [ |
||
18 | 2 => "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", |
||
19 | 1 => "PROXY" |
||
20 | ]; |
||
21 | // 4 bits |
||
22 | public $version = 2; |
||
23 | // 4 bits |
||
24 | public $command = self::CMD_PROXY; |
||
25 | |||
26 | // 1 byte |
||
27 | public $protocol = self::TCP4; |
||
28 | |||
29 | protected static $lengths = [ |
||
30 | self::TCP4 => 12, |
||
31 | self::UDP4 => 12, |
||
32 | self::TCP6 => 36, |
||
33 | self::UDP6 => 36, |
||
34 | self::USTREAM => 216, |
||
35 | self::USOCK => 216, |
||
36 | ]; |
||
37 | |||
38 | /** |
||
39 | * @var string The address of the client. |
||
40 | */ |
||
41 | public $sourceAddress; |
||
42 | |||
43 | /** |
||
44 | * @var string The address to which the client connected. |
||
45 | */ |
||
46 | public $targetAddress; |
||
47 | |||
48 | /** |
||
49 | * @var int The port of the client |
||
50 | */ |
||
51 | public $sourcePort; |
||
52 | |||
53 | /** |
||
54 | * @var int The port to which the client connected. |
||
55 | */ |
||
56 | public $targetPort; |
||
57 | |||
58 | |||
59 | 1 | protected function getProtocol() |
|
60 | { |
||
61 | 1 | if ($this->version == 2) { |
|
62 | 1 | return $this->protocol; |
|
63 | } else { |
||
64 | return array_flip((new \ReflectionClass($this))->getConstants())[$this->protocol]; |
||
65 | } |
||
66 | } |
||
67 | 1 | protected function getVersionCommand() { |
|
68 | 1 | if ($this->version == 2) { |
|
69 | 1 | return chr(($this->version << 4) + $this->command); |
|
70 | } |
||
71 | } |
||
72 | /** |
||
73 | * @return uint16_t |
||
74 | */ |
||
75 | 1 | protected function getAddressLength() |
|
76 | { |
||
77 | 1 | if ($this->version == 2) { |
|
78 | 1 | return pack('n', self::$lengths[$this->protocol]); |
|
79 | } |
||
80 | |||
81 | } |
||
82 | |||
83 | 1 | View Code Duplication | protected function encodeAddress($address, $protocol) { |
1 ignored issue
–
show
|
|||
84 | 1 | if ($this->version == 1) { |
|
85 | return $address; |
||
86 | } |
||
87 | switch ($protocol) { |
||
88 | 1 | case self::TCP4: |
|
89 | 1 | case self::UDP4: |
|
90 | 1 | case self::TCP6: |
|
91 | 1 | case self::UDP6: |
|
92 | 1 | $result = inet_pton($address); |
|
93 | 1 | break; |
|
94 | case self::USTREAM: |
||
95 | case self::USOCK: |
||
96 | throw new \Exception("Unix socket not (yet) supported."); |
||
97 | break; |
||
98 | default: |
||
99 | throw new \UnexpectedValueException("Invalid protocol."); |
||
100 | |||
101 | } |
||
102 | 1 | return $result; |
|
103 | } |
||
104 | |||
105 | 1 | View Code Duplication | protected static function decodeAddress($version, $address, $protocol) |
1 ignored issue
–
show
|
|||
106 | { |
||
107 | 1 | if ($version == 1) { |
|
108 | return $address; |
||
109 | } |
||
110 | switch ($protocol) { |
||
111 | 1 | case self::TCP4: |
|
112 | 1 | case self::UDP4: |
|
113 | 1 | case self::TCP6: |
|
114 | 1 | case self::UDP6: |
|
115 | 1 | $result = inet_ntop($address); |
|
116 | 1 | break; |
|
117 | case self::USTREAM: |
||
118 | case self::USOCK: |
||
119 | throw new \Exception("Unix socket not (yet) supported."); |
||
120 | break; |
||
121 | default: |
||
122 | throw new \UnexpectedValueException("Invalid protocol."); |
||
123 | |||
124 | } |
||
125 | 1 | return $result; |
|
126 | } |
||
127 | |||
128 | /** |
||
129 | * @return string |
||
130 | * @throws \Exception |
||
131 | */ |
||
132 | 1 | protected function getAddresses() |
|
133 | { |
||
134 | 1 | return $this->encodeAddress($this->sourceAddress, $this->protocol) . ($this->version == 1 ? " " : "") .$this->encodeAddress($this->targetAddress, $this->protocol); |
|
135 | } |
||
136 | |||
137 | 1 | View Code Duplication | protected function encodePort($port, $protocol) { |
1 ignored issue
–
show
|
|||
138 | 1 | if ($this->version == 1) { |
|
139 | return $port; |
||
140 | } |
||
141 | switch ($protocol) { |
||
142 | 1 | case self::TCP4: |
|
143 | 1 | case self::UDP4: |
|
144 | 1 | case self::TCP6: |
|
145 | 1 | case self::UDP6: |
|
146 | 1 | $result = pack('n', $port); |
|
147 | 1 | break; |
|
148 | case self::USTREAM: |
||
149 | case self::USOCK: |
||
150 | throw new \Exception("Unix socket not (yet) supported."); |
||
151 | break; |
||
152 | default: |
||
153 | throw new \UnexpectedValueException("Invalid protocol."); |
||
154 | |||
155 | } |
||
156 | 1 | return $result; |
|
157 | } |
||
158 | |||
159 | /** |
||
160 | * @return string |
||
161 | * @throws \Exception |
||
162 | */ |
||
163 | 1 | protected function getPorts() |
|
164 | { |
||
165 | 1 | return $this->encodePort($this->sourcePort, $this->protocol) . ($this->version == 1 ? " " : "") . $this->encodePort($this->targetPort, $this->protocol); |
|
166 | } |
||
167 | |||
168 | /** |
||
169 | * @return string |
||
170 | */ |
||
171 | 1 | protected function getSignature() |
|
172 | { |
||
173 | 1 | return self::$signatures[$this->version]; |
|
174 | } |
||
175 | |||
176 | /** |
||
177 | * Constructs the header by concatenating all relevant fields. |
||
178 | * @return string |
||
179 | */ |
||
180 | 1 | public function constructProxyHeader() { |
|
181 | 1 | return implode($this->version == 1 ? "\x20" : "", array_filter([ |
|
182 | 1 | $this->getSignature(), |
|
183 | 1 | $this->getVersionCommand(), |
|
184 | 1 | $this->getProtocol(), |
|
185 | 1 | $this->getAddressLength(), |
|
186 | 1 | $this->getAddresses(), |
|
187 | 1 | $this->getPorts(), |
|
188 | 1 | $this->version == 1 ? "\r\n" : null |
|
189 | 1 | ])); |
|
190 | } |
||
191 | |||
192 | 1 | public function __toString() |
|
193 | { |
||
194 | 1 | return $this->constructProxyHeader(); |
|
195 | } |
||
196 | |||
197 | /** |
||
198 | * This function creates the forwarding header. This header should be sent over the upstream connection as soon as |
||
199 | * it is established. |
||
200 | * @param string $sourceAddress |
||
201 | * @param int $sourcePort |
||
202 | * @param string $targetAddress |
||
203 | * @param int $targetPort |
||
204 | * @return self |
||
205 | * @throws \Exception |
||
206 | */ |
||
207 | 2 | public static function createForward4($sourceAddress, $sourcePort, $targetAddress, $targetPort) { |
|
216 | |||
217 | /** |
||
218 | * @param string $data |
||
219 | * @return Header|null |
||
220 | */ |
||
221 | 1 | public static function parseHeader(&$data) |
|
222 | { |
||
223 | 1 | foreach(self::$signatures as $version => $signature) { |
|
224 | // Match. |
||
225 | 1 | if (strncmp($data, $signature, strlen($signature)) === 0) { |
|
226 | 1 | if ($version === 1) { |
|
227 | $result = self::parseVersion1($data); |
||
228 | break; |
||
229 | 1 | } elseif ($version === 2) { |
|
230 | 1 | $result = self::parseVersion2($data); |
|
231 | 1 | break; |
|
232 | } |
||
233 | } |
||
234 | 1 | } |
|
235 | 1 | if (isset($result)) { |
|
236 | 1 | $constructed = $result->constructProxyHeader(); |
|
237 | 1 | if (strncmp($constructed, $data, strlen($constructed)) === 0) { |
|
238 | 1 | $data = substr($data, strlen($constructed)); |
|
239 | 1 | return $result; |
|
240 | } |
||
241 | } |
||
242 | } |
||
243 | |||
244 | protected static function parseVersion1($data) |
||
258 | |||
259 | 1 | protected static function parseVersion2($data) |
|
260 | { |
||
261 | 1 | $version = ord(substr($data, 12, 1)) >> 4; |
|
262 | 1 | $command = ord(substr($data, 12, 1)) % 16; |
|
263 | 1 | $protocol = substr($data, 13, 1); |
|
264 | |||
265 | 1 | $pos = 16; |
|
266 | 1 | $sourceAddress = self::decodeAddress($version, substr($data, $pos, self::$lengths[$protocol] / 2 - 2), $protocol); |
|
267 | 1 | $pos += self::$lengths[$protocol] / 2 - 2; |
|
268 | 1 | $targetAddress = self::decodeAddress($version, substr($data, $pos, self::$lengths[$protocol] / 2 - 2), $protocol); |
|
269 | 1 | $pos += self::$lengths[$protocol] / 2 - 2; |
|
270 | 1 | $sourcePort = unpack('n', substr($data, $pos, 2))[1]; |
|
283 | |||
284 | } |
||
285 |
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.