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 BounceMailHandler 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 BounceMailHandler, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
32 | class BounceMailHandler |
||
33 | { |
||
34 | const VERBOSE_QUIET = 0; // suppress output |
||
35 | const VERBOSE_SIMPLE = 1; // simple report |
||
36 | const VERBOSE_REPORT = 2; // detailed report |
||
37 | const VERBOSE_DEBUG = 3; // detailed report plus debug info |
||
38 | |||
39 | /** |
||
40 | * mail-server |
||
41 | * |
||
42 | * @var string |
||
43 | */ |
||
44 | public $mailhost = 'localhost'; |
||
45 | |||
46 | /** |
||
47 | * the username of mailbox |
||
48 | * |
||
49 | * @var string |
||
50 | */ |
||
51 | public $mailboxUserName; |
||
52 | |||
53 | /** |
||
54 | * the password needed to access mailbox |
||
55 | * |
||
56 | * @var string |
||
57 | */ |
||
58 | public $mailboxPassword; |
||
59 | |||
60 | /** |
||
61 | * the last error msg |
||
62 | * |
||
63 | * @var string |
||
64 | */ |
||
65 | public $errorMessage; |
||
66 | |||
67 | /** |
||
68 | * maximum limit messages processed in one batch |
||
69 | * |
||
70 | * @var int |
||
71 | */ |
||
72 | public $maxMessages = 3000; |
||
73 | |||
74 | /** |
||
75 | * callback Action function name the function that handles the bounce mail. Parameters: |
||
76 | * |
||
77 | * int $msgnum the message number returned by Bounce Mail Handler |
||
78 | * string $bounce_type the bounce type: |
||
79 | * 'antispam', |
||
80 | * 'autoreply', |
||
81 | * 'concurrent', |
||
82 | * 'content_reject', |
||
83 | * 'command_reject', |
||
84 | * 'internal_error', |
||
85 | * 'defer', |
||
86 | * 'delayed' |
||
87 | * => |
||
88 | * array( |
||
89 | * 'remove' => 0, |
||
90 | * 'bounce_type' => 'temporary' |
||
91 | * ), |
||
92 | * 'dns_loop', |
||
93 | * 'dns_unknown', |
||
94 | * 'full', |
||
95 | * 'inactive', |
||
96 | * 'latin_only', |
||
97 | * 'other', |
||
98 | * 'oversize', |
||
99 | * 'outofoffice', |
||
100 | * 'unknown', |
||
101 | * 'unrecognized', |
||
102 | * 'user_reject', |
||
103 | * 'warning' |
||
104 | * string $email the target email address string $subject the subject, ignore now string $xheader |
||
105 | * the XBounceHeader from the mail |
||
106 | * 1 or 0 $remove delete status, 0 is not deleted, 1 is deleted |
||
107 | * string $rule_no bounce mail detect rule no. |
||
108 | * string $rule_cat bounce mail detect rule category |
||
109 | * int $totalFetched total number of messages in the mailbox |
||
110 | * |
||
111 | * @var mixed |
||
112 | */ |
||
113 | public $actionFunction = 'callbackAction'; |
||
114 | |||
115 | /** |
||
116 | * Callback custom body rules |
||
117 | * ``` |
||
118 | * function customBodyRulesCallback( $result, $body, $structure, $debug ) |
||
119 | * { |
||
120 | * return $result; |
||
121 | * } |
||
122 | * ``` |
||
123 | * |
||
124 | * @var null|callable |
||
125 | */ |
||
126 | public $customBodyRulesCallback = null; |
||
127 | |||
128 | /** |
||
129 | * Callback custom DSN (Delivery Status Notification) rules |
||
130 | * ``` |
||
131 | * function customDSNRulesCallback( $result, $dsnMsg, $dsnReport, $debug ) |
||
132 | * { |
||
133 | * return $result; |
||
134 | * } |
||
135 | * ``` |
||
136 | * |
||
137 | * @var null|callable |
||
138 | */ |
||
139 | public $customDSNRulesCallback = null; |
||
140 | |||
141 | /** |
||
142 | * test-mode, if true will not delete messages |
||
143 | * |
||
144 | * @var boolean |
||
145 | */ |
||
146 | public $testMode = false; |
||
147 | |||
148 | /** |
||
149 | * purge the unknown messages (or not) |
||
150 | * |
||
151 | * @var boolean |
||
152 | */ |
||
153 | public $purgeUnprocessed = false; |
||
154 | |||
155 | /** |
||
156 | * control the debug output, default is VERBOSE_SIMPLE |
||
157 | * |
||
158 | * @var int |
||
159 | */ |
||
160 | public $verbose = self::VERBOSE_SIMPLE; |
||
161 | |||
162 | /** |
||
163 | * control the failed DSN rules output |
||
164 | * |
||
165 | * @var boolean |
||
166 | */ |
||
167 | public $debugDsnRule = false; |
||
168 | |||
169 | /** |
||
170 | * control the failed BODY rules output |
||
171 | * |
||
172 | * @var boolean |
||
173 | */ |
||
174 | public $debugBodyRule = false; |
||
175 | |||
176 | /** |
||
177 | * Control the method to process the mail header |
||
178 | * if set true, uses the imap_fetchstructure function |
||
179 | * otherwise, detect message type directly from headers, |
||
180 | * a bit faster than imap_fetchstructure function and take less resources. |
||
181 | * |
||
182 | * however - the difference is negligible |
||
183 | * |
||
184 | * @var boolean |
||
185 | */ |
||
186 | public $useFetchstructure = true; |
||
187 | |||
188 | /** |
||
189 | * If disableDelete is equal to true, it will disable the delete function. |
||
190 | * |
||
191 | * @var boolean |
||
192 | */ |
||
193 | public $disableDelete = false; |
||
194 | |||
195 | /** |
||
196 | * defines new line ending |
||
197 | * |
||
198 | * @var string |
||
199 | */ |
||
200 | public $bmhNewLine = "<br />\n"; |
||
201 | |||
202 | /** |
||
203 | * defines port number, default is '143', other common choices are '110' (pop3), '993' (gmail) |
||
204 | * |
||
205 | * @var integer |
||
206 | */ |
||
207 | public $port = 143; |
||
208 | |||
209 | /** |
||
210 | * defines service, default is 'imap', choice includes 'pop3' |
||
211 | * |
||
212 | * @var string |
||
213 | */ |
||
214 | public $service = 'imap'; |
||
215 | |||
216 | /** |
||
217 | * defines service option, default is 'notls', other choices are 'tls', 'ssl' |
||
218 | * |
||
219 | * @var string |
||
220 | */ |
||
221 | public $serviceOption = 'notls'; |
||
222 | |||
223 | /** |
||
224 | * mailbox type, default is 'INBOX', other choices are (Tasks, Spam, Replies, etc.) |
||
225 | * |
||
226 | * @var string |
||
227 | */ |
||
228 | public $boxname = 'INBOX'; |
||
229 | |||
230 | /** |
||
231 | * determines if soft bounces will be moved to another mailbox folder |
||
232 | * |
||
233 | * @var boolean |
||
234 | */ |
||
235 | public $moveSoft = false; |
||
236 | |||
237 | /** |
||
238 | * mailbox folder to move soft bounces to, default is 'soft' |
||
239 | * |
||
240 | * @var string |
||
241 | */ |
||
242 | public $softMailbox = 'INBOX.soft'; |
||
243 | |||
244 | /** |
||
245 | * determines if hard bounces will be moved to another mailbox folder |
||
246 | * |
||
247 | * NOTE: If true, this will disable delete and perform a move operation instead |
||
248 | * |
||
249 | * @var boolean |
||
250 | */ |
||
251 | public $moveHard = false; |
||
252 | |||
253 | /** |
||
254 | * mailbox folder to move hard bounces to, default is 'hard' |
||
255 | * |
||
256 | * @var string |
||
257 | */ |
||
258 | public $hardMailbox = 'INBOX.hard'; |
||
259 | |||
260 | /* |
||
261 | * Mailbox folder to move unprocessed mails |
||
262 | * @var string |
||
263 | */ |
||
264 | public $unprocessedBox = 'INBOX.unprocessed'; |
||
265 | |||
266 | /** |
||
267 | * deletes messages globally prior to date in variable |
||
268 | * |
||
269 | * NOTE: excludes any message folder that includes 'sent' in mailbox name |
||
270 | * format is same as MySQL: 'yyyy-mm-dd' |
||
271 | * if variable is blank, will not process global delete |
||
272 | * |
||
273 | * @var string |
||
274 | */ |
||
275 | public $deleteMsgDate = ''; |
||
276 | |||
277 | /** |
||
278 | * Holds Bounce Mail Handler version. |
||
279 | * |
||
280 | * @var string |
||
281 | */ |
||
282 | private $version = '5.3-dev'; |
||
283 | |||
284 | /** |
||
285 | * (internal variable) |
||
286 | * |
||
287 | * The resource handler for the opened mailbox (POP3/IMAP/NNTP/etc.) |
||
288 | * |
||
289 | * @var resource |
||
290 | */ |
||
291 | private $mailboxLink = false; |
||
292 | |||
293 | /** |
||
294 | * get version |
||
295 | * |
||
296 | * @return string |
||
297 | */ |
||
298 | public function getVersion() |
||
302 | |||
303 | /** |
||
304 | * open a mail box |
||
305 | * |
||
306 | * @return boolean |
||
307 | */ |
||
308 | 1 | public function openMailbox() |
|
309 | { |
||
310 | // before starting the processing, let's check the delete flag and do global deletes if true |
||
311 | 1 | if (trim($this->deleteMsgDate) != '') { |
|
312 | echo 'processing global delete based on date of ' . $this->deleteMsgDate . '<br />'; |
||
313 | $this->globalDelete(); |
||
314 | } |
||
315 | |||
316 | // disable move operations if server is Gmail ... Gmail does not support mailbox creation |
||
317 | 1 | if (false !== stripos($this->mailhost, 'gmail')) { |
|
318 | $this->moveSoft = false; |
||
319 | $this->moveHard = false; |
||
320 | } |
||
321 | |||
322 | 1 | $port = $this->port . '/' . $this->service . '/' . $this->serviceOption; |
|
323 | |||
324 | 1 | set_time_limit(6000); |
|
325 | |||
326 | 1 | if (!$this->testMode) { |
|
327 | 1 | $this->mailboxLink = imap_open('{' . $this->mailhost . ':' . $port . '}' . $this->boxname, $this->mailboxUserName, $this->mailboxPassword, CL_EXPUNGE | ($this->testMode ? OP_READONLY : 0)); |
|
328 | 1 | } else { |
|
329 | $this->mailboxLink = imap_open('{' . $this->mailhost . ':' . $port . '}' . $this->boxname, $this->mailboxUserName, $this->mailboxPassword, ($this->testMode ? OP_READONLY : 0)); |
||
330 | } |
||
331 | |||
332 | 1 | if (!$this->mailboxLink) { |
|
333 | $this->errorMessage = 'Cannot create ' . $this->service . ' connection to ' . $this->mailhost . $this->bmhNewLine . 'Error MSG: ' . imap_last_error(); |
||
334 | $this->output(); |
||
335 | |||
336 | return false; |
||
337 | } else { |
||
338 | 1 | $this->output('Connected to: ' . $this->mailhost . ' (' . $this->mailboxUserName . ')'); |
|
339 | |||
340 | 1 | return true; |
|
341 | } |
||
342 | } |
||
343 | |||
344 | /** |
||
345 | * Function to delete messages in a mailbox, based on date |
||
346 | * |
||
347 | * NOTE: this is global ... will affect all mailboxes except any that have 'sent' in the mailbox name |
||
348 | */ |
||
349 | public function globalDelete() |
||
399 | |||
400 | /** |
||
401 | * output additional msg for debug |
||
402 | * |
||
403 | * @param bool|false $msg if not given, output the last error msg |
||
404 | * @param int $verboseLevel the output level of this message |
||
405 | */ |
||
406 | 3 | public function output($msg = false, $verboseLevel = self::VERBOSE_SIMPLE) |
|
416 | |||
417 | /** |
||
418 | * open a mail box in local file system |
||
419 | * |
||
420 | * @param string $filePath The local mailbox file path |
||
421 | * |
||
422 | * @return boolean |
||
423 | */ |
||
424 | 2 | public function openLocal($filePath) |
|
445 | |||
446 | /** |
||
447 | * process the messages in a mailbox |
||
448 | * |
||
449 | * @param bool|false $max $max maximum limit messages processed in one batch, if not given uses the property |
||
450 | * $maxMessages |
||
451 | * |
||
452 | * @return bool |
||
453 | */ |
||
454 | 3 | public function processMailbox($max = false) |
|
650 | |||
651 | /** |
||
652 | * Function to determine if a particular value is found in a imap_fetchstructure key. |
||
653 | * |
||
654 | * @param array $currParameters imap_fetstructure parameters |
||
655 | * @param string $varKey imap_fetstructure key |
||
656 | * @param string $varValue value to check for |
||
657 | * |
||
658 | * @return boolean |
||
659 | */ |
||
660 | 2 | public function isParameter($currParameters, $varKey, $varValue) |
|
674 | |||
675 | /** |
||
676 | * Function to process each individual message. |
||
677 | * |
||
678 | * @param int $pos message number |
||
679 | * @param string $type DNS or BODY type |
||
680 | * @param string $totalFetched total number of messages in mailbox |
||
681 | * |
||
682 | * @return boolean |
||
683 | */ |
||
684 | 3 | public function processBounce($pos, $type, $totalFetched) |
|
850 | |||
851 | /** |
||
852 | * Function to check if a mailbox exists - if not found, it will create it. |
||
853 | * |
||
854 | * @param string $mailbox the mailbox name, must be in 'INBOX.checkmailbox' format |
||
855 | * @param boolean $create whether or not to create the checkmailbox if not found, defaults to true |
||
856 | * |
||
857 | * @return boolean |
||
858 | */ |
||
859 | 3 | public function mailboxExist($mailbox, $create = true) |
|
906 | } |
||
907 |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.