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 FrameHandler 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 FrameHandler, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
8 | class FrameHandler { |
||
9 | |||
10 | /** |
||
11 | * The binary message buffer when streaming is disabled. |
||
12 | * |
||
13 | * @var string |
||
14 | */ |
||
15 | protected $binary = ''; |
||
16 | |||
17 | /** |
||
18 | * @var WebSocketClient |
||
19 | */ |
||
20 | protected $client; |
||
21 | |||
22 | /** |
||
23 | * Resume opCode for the `CONTINUE` handler. |
||
24 | * |
||
25 | * @var int|null |
||
26 | */ |
||
27 | protected $continue; |
||
28 | |||
29 | /** |
||
30 | * Max outgoing fragment size. |
||
31 | * |
||
32 | * Each browser has its own standard, so this is generalized. |
||
33 | * |
||
34 | * Defaults to 128 KiB. |
||
35 | * |
||
36 | * @var int |
||
37 | */ |
||
38 | protected $fragmentSize = 128 * 1024; |
||
39 | |||
40 | /** |
||
41 | * Maximum inbound message length. |
||
42 | * |
||
43 | * Defaults to 10 MiB. |
||
44 | * |
||
45 | * @var int |
||
46 | */ |
||
47 | protected $maxLength = 10 * 1024 * 1024; |
||
48 | |||
49 | /** |
||
50 | * The text message buffer when streaming is disabled. |
||
51 | * |
||
52 | * @var string |
||
53 | */ |
||
54 | protected $text = ''; |
||
55 | |||
56 | public function __construct (WebSocketClient $client) { |
||
59 | |||
60 | /** |
||
61 | * @return int |
||
62 | */ |
||
63 | public function getFragmentSize (): int { |
||
66 | |||
67 | /** |
||
68 | * @return int |
||
69 | */ |
||
70 | public function getMaxLength (): int { |
||
73 | |||
74 | /** |
||
75 | * When a `BINARY` frame is received. |
||
76 | * |
||
77 | * Throws by default. |
||
78 | * |
||
79 | * @param Frame $binary |
||
80 | * @throws WebSocketError |
||
81 | */ |
||
82 | View Code Duplication | protected function onBinary (Frame $binary): void { |
|
90 | |||
91 | /** |
||
92 | * When a `CLOSE` frame is received. |
||
93 | * |
||
94 | * https://tools.ietf.org/html/rfc6455#section-5.5.1 |
||
95 | * > If an endpoint receives a Close frame and did not previously send a |
||
96 | * > Close frame, the endpoint MUST send a Close frame in response. (When |
||
97 | * > sending a Close frame in response, the endpoint typically echos the |
||
98 | * > status code it received.) |
||
99 | * |
||
100 | * @param Frame $close |
||
101 | */ |
||
102 | protected function onClose (Frame $close): void { |
||
105 | |||
106 | /** |
||
107 | * When a `CONTINUE` data fragment is received. |
||
108 | * |
||
109 | * @param Frame $frame |
||
110 | * @throws WebSocketError |
||
111 | */ |
||
112 | protected function onContinue (Frame $frame): void { |
||
128 | |||
129 | /** |
||
130 | * When a control frame is received. |
||
131 | * |
||
132 | * https://tools.ietf.org/html/rfc6455#section-5.4 |
||
133 | * > Control frames (see Section 5.5) MAY be injected in the middle of |
||
134 | * > a fragmented message. |
||
135 | * |
||
136 | * @param Frame $control |
||
137 | */ |
||
138 | protected function onControl (Frame $control): void { |
||
149 | |||
150 | /** |
||
151 | * When an initial data frame (not `CONTINUE`) is received. |
||
152 | * |
||
153 | * @param Frame $data |
||
154 | */ |
||
155 | protected function onData (Frame $data): void { |
||
166 | |||
167 | protected function onData_SetContinue (Frame $data): void { |
||
178 | |||
179 | /** |
||
180 | * Invoked by the client when a complete frame has been received. |
||
181 | * |
||
182 | * Delegates to the other handler methods using the control flow outlined in the RFC. |
||
183 | * |
||
184 | * @param Frame $frame |
||
185 | */ |
||
186 | public function onFrame (Frame $frame): void { |
||
199 | |||
200 | /** |
||
201 | * @param Frame $frame |
||
202 | */ |
||
203 | protected function onFrame_CheckLength (Frame $frame): void { |
||
220 | |||
221 | /** |
||
222 | * Throws if unknown RSV bits are received. |
||
223 | * |
||
224 | * @param Frame $frame |
||
225 | * @throws WebSocketError |
||
226 | */ |
||
227 | protected function onFrame_CheckRsv (Frame $frame): void { |
||
233 | |||
234 | /** |
||
235 | * When a `PING` frame is received. |
||
236 | * |
||
237 | * Automatically pongs the payload back by default. |
||
238 | * |
||
239 | * @param Frame $ping |
||
240 | */ |
||
241 | protected function onPing (Frame $ping): void { |
||
244 | |||
245 | /** |
||
246 | * When a `PONG` frame is received. |
||
247 | * |
||
248 | * Does nothing by default. |
||
249 | * |
||
250 | * @param Frame $pong |
||
251 | */ |
||
252 | protected function onPong (Frame $pong): void { |
||
255 | |||
256 | /** |
||
257 | * When a `BINARY` frame is received. |
||
258 | * |
||
259 | * Throws by default. |
||
260 | * |
||
261 | * @param Frame $text |
||
262 | * @throws WebSocketError |
||
263 | */ |
||
264 | View Code Duplication | protected function onText (Frame $text): void { |
|
272 | |||
273 | /** |
||
274 | * @param int $bytes |
||
275 | * @return $this |
||
276 | */ |
||
277 | public function setFragmentSize (int $bytes) { |
||
281 | |||
282 | /** |
||
283 | * @param int $bytes |
||
284 | * @return $this |
||
285 | */ |
||
286 | public function setMaxLength (int $bytes) { |
||
290 | |||
291 | /** |
||
292 | * Fragments data into frames and writes them to the peer. |
||
293 | * |
||
294 | * @param int $opCode |
||
295 | * @param string $payload |
||
296 | */ |
||
297 | public function write (int $opCode, string $payload): void { |
||
309 | |||
310 | /** |
||
311 | * @param string $payload |
||
312 | */ |
||
313 | public function writeBinary (string $payload): void { |
||
316 | |||
317 | /** |
||
318 | * @param int $code |
||
319 | * @param string $reason |
||
320 | */ |
||
321 | public function writeClose (int $code = Frame::CLOSE_NORMAL, string $reason = ''): void { |
||
324 | |||
325 | /** |
||
326 | * Writes a single frame. |
||
327 | * |
||
328 | * @param bool $final |
||
329 | * @param int $opCode |
||
330 | * @param string $payload |
||
331 | */ |
||
332 | protected function writeFrame (bool $final, int $opCode, string $payload): void { |
||
354 | |||
355 | /** |
||
356 | * @param string $payload |
||
357 | */ |
||
358 | public function writePing (string $payload = ''): void { |
||
361 | |||
362 | /** |
||
363 | * @param string $payload |
||
364 | */ |
||
365 | public function writePong (string $payload = ''): void { |
||
368 | |||
369 | /** |
||
370 | * @param string $payload |
||
371 | */ |
||
372 | public function writeText (string $payload): void { |
||
375 | } |
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.