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 Connection 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 Connection, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
11 | class Connection extends \React\Socket\Connection{ |
||
12 | const STATUS_NEW = 0; |
||
13 | const STATUS_INIT = 1; |
||
14 | const STATUS_FROM = 2; |
||
15 | const STATUS_TO = 3; |
||
16 | const STATUS_HEADERS = 4; |
||
17 | const STATUS_UNFOLDING = 5; |
||
18 | const STATUS_BODY = 6; |
||
19 | |||
20 | |||
21 | /** |
||
22 | * This status is used when all mail data has been received and the system is deciding whether to accept or reject. |
||
23 | */ |
||
24 | const STATUS_PROCESSING = 7; |
||
25 | |||
26 | |||
27 | const REGEXES = [ |
||
28 | 'Quit' => '/^QUIT$/', |
||
29 | 'Helo' => '/^HELO (.*)$/', |
||
30 | 'Ehlo' => '/^EHLO (.*)$/', |
||
31 | 'MailFrom' => '/^MAIL FROM:\s*(.*)$/', |
||
32 | 'Reset' => '/^RSET$/', |
||
33 | 'RcptTo' => '/^RCPT TO:\s*(.*)$/', |
||
34 | 'StartData' => '/^DATA$/', |
||
35 | 'StartHeader' => '/^(\w+):\s*(.*)$/', |
||
36 | 'StartBody' => '/^$/', |
||
37 | 'Unfold' => '/^ (.*)$/', |
||
38 | 'EndData' => '/^\.$/', |
||
39 | 'BodyLine' => '/^(.*)$/', |
||
40 | 'EndBody' => '/^\.$/' |
||
41 | ]; |
||
42 | |||
43 | protected $states = [ |
||
44 | self::STATUS_NEW => [ |
||
45 | 'Quit', 'Helo', 'Ehlo' |
||
46 | ], |
||
47 | self::STATUS_INIT => [ |
||
48 | 'MailFrom', |
||
49 | 'Quit' |
||
50 | |||
51 | ], |
||
52 | self::STATUS_FROM => [ |
||
53 | 'RcptTo', |
||
54 | 'Quit', |
||
55 | 'Reset', |
||
56 | ], |
||
57 | self::STATUS_TO => [ |
||
58 | 'Quit', |
||
59 | 'StartData', |
||
60 | 'Reset', |
||
61 | 'RcptTo', |
||
62 | |||
63 | ], |
||
64 | self::STATUS_HEADERS => [ |
||
65 | 'EndBody', |
||
66 | 'StartHeader', |
||
67 | 'StartBody', |
||
68 | ], |
||
69 | self::STATUS_UNFOLDING => [ |
||
70 | 'StartBody', |
||
71 | 'EndBody', |
||
72 | 'Unfold', |
||
73 | 'StartHeader', |
||
74 | ], |
||
75 | self::STATUS_BODY => [ |
||
76 | 'EndBody', |
||
77 | 'BodyLine' |
||
78 | ], |
||
79 | self::STATUS_PROCESSING => [ |
||
80 | |||
81 | ] |
||
82 | |||
83 | |||
84 | |||
85 | ]; |
||
86 | |||
87 | protected $state = self::STATUS_NEW; |
||
88 | |||
89 | protected $banner = 'Welcome to ReactPHP SMTP Server'; |
||
90 | /** |
||
91 | * @var bool Accept messages by default |
||
92 | */ |
||
93 | protected $acceptByDefault = true; |
||
94 | /** |
||
95 | * If there are event listeners, how long will they get to accept or reject a message? |
||
96 | * @var int |
||
97 | */ |
||
98 | protected $defaultActionTimeout = 5; |
||
99 | /** |
||
100 | * The timer for the default action, canceled in [accept] and [reject] |
||
101 | * @var TimerInterface |
||
102 | */ |
||
103 | protected $defaultActionTimer; |
||
104 | /** |
||
105 | * The current line buffer used by handleData. |
||
106 | * @var string |
||
107 | */ |
||
108 | protected $lineBuffer = ''; |
||
109 | |||
110 | /** |
||
111 | * @var string Name of the header in the foldBuffer. |
||
112 | */ |
||
113 | protected $foldHeader = ''; |
||
114 | /** |
||
115 | * Buffer used for unfolding multiline headers.. |
||
116 | * @var string |
||
117 | */ |
||
118 | protected $foldBuffer = ''; |
||
119 | protected $from; |
||
120 | protected $recipients = []; |
||
121 | /** |
||
122 | * @var Message |
||
123 | */ |
||
124 | protected $message; |
||
125 | |||
126 | public $bannerDelay = 0; |
||
127 | |||
128 | |||
129 | public $recipientLimit = 100; |
||
130 | |||
131 | 1 | public function __construct($stream, LoopInterface $loop) |
|
151 | |||
152 | /** |
||
153 | * We read until we find an and of line sequence for SMTP. |
||
154 | * http://www.jebriggs.com/blog/2010/07/smtp-maximum-line-lengths/ |
||
155 | * @param $stream |
||
156 | */ |
||
157 | public function handleData($stream) |
||
183 | |||
184 | /** |
||
185 | * Parses the command from the beginning of the line. |
||
186 | * |
||
187 | * @param string $line |
||
188 | * @return string[] An array containing the command and all arguments. |
||
189 | */ |
||
190 | protected function parseCommand($line) |
||
204 | |||
205 | protected function handleCommand($line) |
||
218 | |||
219 | 1 | protected function sendReply($code, $message, $close = false) |
|
237 | |||
238 | protected function handleResetCommand() |
||
239 | { |
||
240 | $this->reset(); |
||
241 | $this->sendReply(250, "Reset OK"); |
||
242 | } |
||
243 | protected function handleHeloCommand($domain) |
||
248 | |||
249 | protected function handleEhloCommand($domain) |
||
254 | |||
255 | View Code Duplication | protected function handleMailFromCommand($arguments) |
|
268 | |||
269 | protected function handleQuitCommand() |
||
270 | { |
||
271 | $this->sendReply(221, "Goodbye.", true); |
||
272 | |||
273 | } |
||
274 | |||
275 | View Code Duplication | protected function handleRcptToCommand($arguments) { |
|
286 | |||
287 | protected function handleStartDataCommand() |
||
292 | |||
293 | protected function handleUnfoldCommand($content) |
||
297 | |||
298 | protected function handleStartHeaderCommand($name, $content) |
||
309 | |||
310 | protected function handleStartBodyCommand() |
||
319 | |||
320 | protected function handleEndBodyCommand() |
||
352 | |||
353 | /** |
||
354 | * Reset the SMTP session. |
||
355 | * By default goes to the initialized state (ie no new EHLO or HELO is required / possible.) |
||
356 | * |
||
357 | * @param int $state The state to go to. |
||
358 | */ |
||
359 | 1 | protected function reset($state = self::STATUS_INIT) { |
|
365 | |||
366 | View Code Duplication | public function accept($message = "OK") { |
|
375 | |||
376 | View Code Duplication | public function reject($code = 550, $message = "Message not accepted") { |
|
385 | |||
386 | /** |
||
387 | * Delay the default action by $seconds. |
||
388 | * @param int $seconds |
||
389 | */ |
||
390 | public function delay($seconds) { |
||
396 | |||
397 | } |
||
398 |
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.