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 Telegram 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 Telegram, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
26 | class Telegram |
||
27 | { |
||
28 | /** |
||
29 | * Version |
||
30 | * |
||
31 | * @var string |
||
32 | */ |
||
33 | protected $version = '0.41.0'; |
||
34 | |||
35 | /** |
||
36 | * Telegram API key |
||
37 | * |
||
38 | * @var string |
||
39 | */ |
||
40 | protected $api_key = ''; |
||
41 | |||
42 | /** |
||
43 | * Telegram Bot name |
||
44 | * |
||
45 | * @var string |
||
46 | */ |
||
47 | protected $bot_name = ''; |
||
48 | |||
49 | /** |
||
50 | * Raw request data (json) for webhook methods |
||
51 | * |
||
52 | * @var string |
||
53 | */ |
||
54 | protected $input; |
||
55 | |||
56 | /** |
||
57 | * Custom commands paths |
||
58 | * |
||
59 | * @var array |
||
60 | */ |
||
61 | protected $commands_paths = []; |
||
62 | |||
63 | /** |
||
64 | * Current Update object |
||
65 | * |
||
66 | * @var \Longman\TelegramBot\Entities\Update |
||
67 | */ |
||
68 | protected $update; |
||
69 | |||
70 | /** |
||
71 | * Upload path |
||
72 | * |
||
73 | * @var string |
||
74 | */ |
||
75 | protected $upload_path; |
||
76 | |||
77 | /** |
||
78 | * Download path |
||
79 | * |
||
80 | * @var string |
||
81 | */ |
||
82 | protected $download_path; |
||
83 | |||
84 | /** |
||
85 | * MySQL integration |
||
86 | * |
||
87 | * @var boolean |
||
88 | */ |
||
89 | protected $mysql_enabled = false; |
||
90 | |||
91 | /** |
||
92 | * PDO object |
||
93 | * |
||
94 | * @var \PDO |
||
95 | */ |
||
96 | protected $pdo; |
||
97 | |||
98 | /** |
||
99 | * Commands config |
||
100 | * |
||
101 | * @var array |
||
102 | */ |
||
103 | protected $commands_config = []; |
||
104 | |||
105 | /** |
||
106 | * Admins list |
||
107 | * |
||
108 | * @var array |
||
109 | */ |
||
110 | protected $admins_list = []; |
||
111 | |||
112 | /** |
||
113 | * ServerResponse of the last Command execution |
||
114 | * |
||
115 | * @var \Longman\TelegramBot\Entities\ServerResponse |
||
116 | */ |
||
117 | protected $last_command_response; |
||
118 | |||
119 | /** |
||
120 | * Botan.io integration |
||
121 | * |
||
122 | * @var boolean |
||
123 | */ |
||
124 | protected $botan_enabled = false; |
||
125 | |||
126 | /** |
||
127 | * Telegram constructor. |
||
128 | * |
||
129 | * @param string $api_key |
||
130 | * @param string $bot_name |
||
131 | * |
||
132 | * @throws \Longman\TelegramBot\Exception\TelegramException |
||
133 | */ |
||
134 | 41 | public function __construct($api_key, $bot_name) |
|
135 | { |
||
136 | 41 | if (empty($api_key)) { |
|
137 | 1 | throw new TelegramException('API KEY not defined!'); |
|
138 | } |
||
139 | |||
140 | 41 | if (empty($bot_name)) { |
|
141 | 1 | throw new TelegramException('Bot Username not defined!'); |
|
142 | } |
||
143 | |||
144 | 41 | $this->api_key = $api_key; |
|
145 | 41 | $this->bot_name = $bot_name; |
|
146 | |||
147 | //Set default download and upload path |
||
148 | 41 | $this->setDownloadPath(BASE_PATH . '/../Download'); |
|
149 | 41 | $this->setUploadPath(BASE_PATH . '/../Upload'); |
|
150 | |||
151 | //Add default system commands path |
||
152 | 41 | $this->addCommandsPath(BASE_COMMANDS_PATH . '/SystemCommands'); |
|
153 | |||
154 | 41 | Request::initialize($this); |
|
155 | 41 | } |
|
156 | |||
157 | /** |
||
158 | * Initialize Database connection |
||
159 | * |
||
160 | * @param array $credential |
||
161 | * @param string $table_prefix |
||
162 | * @param string $encoding |
||
163 | * |
||
164 | * @return \Longman\TelegramBot\Telegram |
||
165 | * @throws \Longman\TelegramBot\Exception\TelegramException |
||
166 | */ |
||
167 | 9 | View Code Duplication | public function enableMySql(array $credential, $table_prefix = null, $encoding = 'utf8mb4') |
|
|||
168 | { |
||
169 | 9 | $this->pdo = DB::initialize($credential, $this, $table_prefix, $encoding); |
|
170 | 9 | ConversationDB::initializeConversation(); |
|
171 | 9 | $this->mysql_enabled = true; |
|
172 | |||
173 | 9 | return $this; |
|
174 | } |
||
175 | |||
176 | /** |
||
177 | * Initialize Database external connection |
||
178 | * |
||
179 | * @param PDO $external_pdo_connection PDO database object |
||
180 | * @param string $table_prefix |
||
181 | * |
||
182 | * @return \Longman\TelegramBot\Telegram |
||
183 | * @throws \Longman\TelegramBot\Exception\TelegramException |
||
184 | */ |
||
185 | View Code Duplication | public function enableExternalMySql($external_pdo_connection, $table_prefix = null) |
|
186 | { |
||
187 | $this->pdo = DB::externalInitialize($external_pdo_connection, $this, $table_prefix); |
||
188 | ConversationDB::initializeConversation(); |
||
189 | $this->mysql_enabled = true; |
||
190 | |||
191 | return $this; |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * Get commands list |
||
196 | * |
||
197 | * @return array $commands |
||
198 | * @throws \Longman\TelegramBot\Exception\TelegramException |
||
199 | */ |
||
200 | 11 | public function getCommandsList() |
|
201 | { |
||
202 | 11 | $commands = []; |
|
203 | |||
204 | 11 | foreach ($this->commands_paths as $path) { |
|
205 | try { |
||
206 | //Get all "*Command.php" files |
||
207 | 11 | $files = new RegexIterator( |
|
208 | 11 | new RecursiveIteratorIterator( |
|
209 | 11 | new RecursiveDirectoryIterator($path) |
|
210 | ), |
||
211 | 11 | '/^.+Command.php$/' |
|
212 | ); |
||
213 | |||
214 | 11 | foreach ($files as $file) { |
|
215 | //Remove "Command.php" from filename |
||
216 | 11 | $command = $this->sanitizeCommand(substr($file->getFilename(), 0, -11)); |
|
217 | 11 | $command_name = strtolower($command); |
|
218 | |||
219 | 11 | if (array_key_exists($command_name, $commands)) { |
|
220 | continue; |
||
221 | } |
||
222 | |||
223 | 11 | require_once $file->getPathname(); |
|
224 | |||
225 | 11 | $command_obj = $this->getCommandObject($command); |
|
226 | 11 | if ($command_obj instanceof Command) { |
|
227 | 11 | $commands[$command_name] = $command_obj; |
|
228 | } |
||
229 | } |
||
230 | } catch (Exception $e) { |
||
231 | 11 | throw new TelegramException('Error getting commands from path: ' . $path); |
|
232 | } |
||
233 | } |
||
234 | |||
235 | 11 | return $commands; |
|
236 | } |
||
237 | |||
238 | /** |
||
239 | * Get an object instance of the passed command |
||
240 | * |
||
241 | * @param string $command |
||
242 | * |
||
243 | * @return \Longman\TelegramBot\Commands\Command|null |
||
244 | */ |
||
245 | 12 | public function getCommandObject($command) |
|
246 | { |
||
247 | 12 | $which = ['System']; |
|
248 | 12 | $this->isAdmin() && $which[] = 'Admin'; |
|
249 | 12 | $which[] = 'User'; |
|
250 | |||
251 | 12 | foreach ($which as $auth) { |
|
252 | 12 | $command_namespace = __NAMESPACE__ . '\\Commands\\' . $auth . 'Commands\\' . $this->ucfirstUnicode($command) . 'Command'; |
|
253 | 12 | if (class_exists($command_namespace)) { |
|
254 | 12 | return new $command_namespace($this, $this->update); |
|
255 | } |
||
256 | } |
||
257 | |||
258 | return null; |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * Set custom input string for debug purposes |
||
263 | * |
||
264 | * @param string $input (json format) |
||
265 | * |
||
266 | * @return \Longman\TelegramBot\Telegram |
||
267 | */ |
||
268 | public function setCustomInput($input) |
||
269 | { |
||
270 | $this->input = $input; |
||
271 | |||
272 | return $this; |
||
273 | } |
||
274 | |||
275 | /** |
||
276 | * Get custom input string for debug purposes |
||
277 | * |
||
278 | * @return string |
||
279 | */ |
||
280 | public function getCustomInput() |
||
281 | { |
||
282 | return $this->input; |
||
283 | } |
||
284 | |||
285 | /** |
||
286 | * Get the ServerResponse of the last Command execution |
||
287 | * |
||
288 | * @return \Longman\TelegramBot\Entities\ServerResponse |
||
289 | */ |
||
290 | public function getLastCommandResponse() |
||
291 | { |
||
292 | return $this->last_command_response; |
||
293 | } |
||
294 | |||
295 | /** |
||
296 | * Handle getUpdates method |
||
297 | * |
||
298 | * @param int|null $limit |
||
299 | * @param int|null $timeout |
||
300 | * |
||
301 | * @return \Longman\TelegramBot\Entities\ServerResponse |
||
302 | * @throws \Longman\TelegramBot\Exception\TelegramException |
||
303 | */ |
||
304 | public function handleGetUpdates($limit = null, $timeout = null) |
||
305 | { |
||
306 | if (!DB::isDbConnected()) { |
||
307 | return new ServerResponse( |
||
308 | [ |
||
309 | 'ok' => false, |
||
310 | 'description' => 'getUpdates needs MySQL connection!', |
||
311 | ], |
||
312 | $this->bot_name |
||
313 | ); |
||
314 | } |
||
315 | |||
316 | //DB Query |
||
317 | $last_update = DB::selectTelegramUpdate(1); |
||
318 | $last_update = reset($last_update); |
||
319 | |||
320 | //As explained in the telegram bot api documentation |
||
321 | $offset = isset($last_update['id']) ? $last_update['id'] + 1 : null; |
||
322 | |||
323 | $response = Request::getUpdates( |
||
324 | [ |
||
325 | 'offset' => $offset, |
||
326 | 'limit' => $limit, |
||
327 | 'timeout' => $timeout, |
||
328 | ] |
||
329 | ); |
||
330 | |||
331 | if ($response->isOk()) { |
||
332 | //Process all updates |
||
333 | /** @var Update $result */ |
||
334 | foreach ((array) $response->getResult() as $result) { |
||
335 | $this->processUpdate($result); |
||
336 | } |
||
337 | } |
||
338 | |||
339 | return $response; |
||
340 | } |
||
341 | |||
342 | /** |
||
343 | * Handle bot request from webhook |
||
344 | * |
||
345 | * @return bool |
||
346 | * |
||
347 | * @throws \Longman\TelegramBot\Exception\TelegramException |
||
348 | */ |
||
349 | public function handle() |
||
350 | { |
||
351 | $this->input = Request::getInput(); |
||
352 | |||
353 | if (empty($this->input)) { |
||
354 | throw new TelegramException('Input is empty!'); |
||
355 | } |
||
356 | |||
357 | $post = json_decode($this->input, true); |
||
358 | if (empty($post)) { |
||
359 | throw new TelegramException('Invalid JSON!'); |
||
360 | } |
||
361 | |||
362 | if ($response = $this->processUpdate(new Update($post, $this->bot_name))) { |
||
363 | return $response->isOk(); |
||
364 | } |
||
365 | |||
366 | return false; |
||
367 | } |
||
368 | |||
369 | /** |
||
370 | * Get the command name from the command type |
||
371 | * |
||
372 | * @param string $type |
||
373 | * |
||
374 | * @return string |
||
375 | */ |
||
376 | private function getCommandFromType($type) |
||
380 | |||
381 | /** |
||
382 | * Process bot Update request |
||
383 | * |
||
384 | * @param \Longman\TelegramBot\Entities\Update $update |
||
385 | * |
||
386 | * @return \Longman\TelegramBot\Entities\ServerResponse |
||
387 | * @throws \Longman\TelegramBot\Exception\TelegramException |
||
388 | */ |
||
389 | public function processUpdate(Update $update) |
||
390 | { |
||
438 | |||
439 | /** |
||
440 | * Execute /command |
||
441 | * |
||
442 | * @param string $command |
||
443 | * |
||
444 | * @return mixed |
||
445 | * @throws \Longman\TelegramBot\Exception\TelegramException |
||
446 | */ |
||
447 | public function executeCommand($command) |
||
477 | |||
478 | /** |
||
479 | * Sanitize Command |
||
480 | * |
||
481 | * @param string $command |
||
482 | * |
||
483 | * @return string |
||
484 | */ |
||
485 | 11 | protected function sanitizeCommand($command) |
|
489 | |||
490 | /** |
||
491 | * Enable a single Admin account |
||
492 | * |
||
493 | * @param integer $admin_id Single admin id |
||
494 | * |
||
495 | * @return \Longman\TelegramBot\Telegram |
||
496 | */ |
||
497 | 1 | public function enableAdmin($admin_id) |
|
507 | |||
508 | /** |
||
509 | * Enable a list of Admin Accounts |
||
510 | * |
||
511 | * @param array $admin_ids List of admin ids |
||
512 | * |
||
513 | * @return \Longman\TelegramBot\Telegram |
||
514 | */ |
||
515 | 1 | public function enableAdmins(array $admin_ids) |
|
523 | |||
524 | /** |
||
525 | * Get list of admins |
||
526 | * |
||
527 | * @return array |
||
528 | */ |
||
529 | 1 | public function getAdminList() |
|
533 | |||
534 | /** |
||
535 | * Check if the passed user is an admin |
||
536 | * |
||
537 | * If no user id is passed, the current update is checked for a valid message sender. |
||
538 | * |
||
539 | * @param int|null $user_id |
||
540 | * |
||
541 | * @return bool |
||
542 | */ |
||
543 | 12 | public function isAdmin($user_id = null) |
|
567 | |||
568 | /** |
||
569 | * Check if user required the db connection |
||
570 | * |
||
571 | * @return bool |
||
572 | */ |
||
573 | public function isDbEnabled() |
||
581 | |||
582 | /** |
||
583 | * Add a single custom commands path |
||
584 | * |
||
585 | * @param string $path Custom commands path to add |
||
586 | * @param bool $before If the path should be prepended or appended to the list |
||
587 | * |
||
588 | * @return \Longman\TelegramBot\Telegram |
||
589 | */ |
||
590 | 41 | public function addCommandsPath($path, $before = true) |
|
604 | |||
605 | /** |
||
606 | * Add multiple custom commands paths |
||
607 | * |
||
608 | * @param array $paths Custom commands paths to add |
||
609 | * @param bool $before If the paths should be prepended or appended to the list |
||
610 | * |
||
611 | * @return \Longman\TelegramBot\Telegram |
||
612 | */ |
||
613 | 1 | public function addCommandsPaths(array $paths, $before = true) |
|
621 | |||
622 | /** |
||
623 | * Return the list of commands paths |
||
624 | * |
||
625 | * @return array |
||
626 | */ |
||
627 | 1 | public function getCommandsPaths() |
|
631 | |||
632 | /** |
||
633 | * Set custom upload path |
||
634 | * |
||
635 | * @param string $path Custom upload path |
||
636 | * |
||
637 | * @return \Longman\TelegramBot\Telegram |
||
638 | */ |
||
639 | 41 | public function setUploadPath($path) |
|
645 | |||
646 | /** |
||
647 | * Get custom upload path |
||
648 | * |
||
649 | * @return string |
||
650 | */ |
||
651 | public function getUploadPath() |
||
655 | |||
656 | /** |
||
657 | * Set custom download path |
||
658 | * |
||
659 | * @param string $path Custom download path |
||
660 | * |
||
661 | * @return \Longman\TelegramBot\Telegram |
||
662 | */ |
||
663 | 41 | public function setDownloadPath($path) |
|
669 | |||
670 | /** |
||
671 | * Get custom download path |
||
672 | * |
||
673 | * @return string |
||
674 | */ |
||
675 | public function getDownloadPath() |
||
679 | |||
680 | /** |
||
681 | * Set command config |
||
682 | * |
||
683 | * Provide further variables to a particular commands. |
||
684 | * For example you can add the channel name at the command /sendtochannel |
||
685 | * Or you can add the api key for external service. |
||
686 | * |
||
687 | * @param string $command |
||
688 | * @param array $config |
||
689 | * |
||
690 | * @return \Longman\TelegramBot\Telegram |
||
691 | */ |
||
692 | 14 | public function setCommandConfig($command, array $config) |
|
698 | |||
699 | /** |
||
700 | * Get command config |
||
701 | * |
||
702 | * @param string $command |
||
703 | * |
||
704 | * @return array |
||
705 | */ |
||
706 | 26 | public function getCommandConfig($command) |
|
710 | |||
711 | /** |
||
712 | * Get API key |
||
713 | * |
||
714 | * @return string |
||
715 | */ |
||
716 | 1 | public function getApiKey() |
|
720 | |||
721 | /** |
||
722 | * Get Bot name |
||
723 | * |
||
724 | * @return string |
||
725 | */ |
||
726 | 7 | public function getBotName() |
|
730 | |||
731 | /** |
||
732 | * Get Version |
||
733 | * |
||
734 | * @return string |
||
735 | */ |
||
736 | 2 | public function getVersion() |
|
740 | |||
741 | /** |
||
742 | * Set Webhook for bot |
||
743 | * |
||
744 | * @param string $url |
||
745 | * @param array $data Optional parameters. |
||
746 | * |
||
747 | * @return \Longman\TelegramBot\Entities\ServerResponse |
||
748 | * @throws \Longman\TelegramBot\Exception\TelegramException |
||
749 | */ |
||
750 | public function setWebhook($url, array $data = []) |
||
766 | |||
767 | /** |
||
768 | * Deprecated alias for deleteWebhook |
||
769 | * |
||
770 | * This is kept for backwards compatibility! |
||
771 | * |
||
772 | * @return mixed |
||
773 | * @throws \Longman\TelegramBot\Exception\TelegramException |
||
774 | */ |
||
775 | public function unsetWebhook() |
||
779 | |||
780 | /** |
||
781 | * Delete any assigned webhook |
||
782 | * |
||
783 | * @return mixed |
||
784 | * @throws \Longman\TelegramBot\Exception\TelegramException |
||
785 | */ |
||
786 | public function deleteWebhook() |
||
798 | |||
799 | /** |
||
800 | * Replace function `ucwords` for UTF-8 characters in the class definition and commands |
||
801 | * |
||
802 | * @param string $str |
||
803 | * @param string $encoding (default = 'UTF-8') |
||
804 | * |
||
805 | * @return string |
||
806 | */ |
||
807 | 11 | protected function ucwordsUnicode($str, $encoding = 'UTF-8') |
|
811 | |||
812 | /** |
||
813 | * Replace function `ucfirst` for UTF-8 characters in the class definition and commands |
||
814 | * |
||
815 | * @param string $str |
||
816 | * @param string $encoding (default = 'UTF-8') |
||
817 | * |
||
818 | * @return string |
||
819 | */ |
||
820 | 12 | protected function ucfirstUnicode($str, $encoding = 'UTF-8') |
|
826 | |||
827 | /** |
||
828 | * Enable Botan.io integration |
||
829 | * |
||
830 | * @param string $token |
||
831 | * @param array $options |
||
832 | * |
||
833 | * @return \Longman\TelegramBot\Telegram |
||
834 | * @throws \Longman\TelegramBot\Exception\TelegramException |
||
835 | */ |
||
836 | public function enableBotan($token, array $options = []) |
||
843 | |||
844 | /** |
||
845 | * Enable requests limiter |
||
846 | */ |
||
847 | public function enableLimiter() |
||
853 | |||
854 | /** |
||
855 | * Run provided commands |
||
856 | * |
||
857 | * @param array $commands |
||
858 | * |
||
859 | * @throws TelegramException |
||
860 | */ |
||
861 | public function runCommands($commands) |
||
906 | } |
||
907 |
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.