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 Bot 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 Bot, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
12 | class Bot extends CoreBot { |
||
13 | |||
14 | /** |
||
15 | * \addtogroup Bot Bot |
||
16 | * \brief Properties and methods to handle the TelegramBot. |
||
17 | * \details Here are listed all the properties and methods that will help the developer create the basic bot functions. |
||
18 | * @{ |
||
19 | */ |
||
20 | |||
21 | /** \brief Text received in messages */ |
||
22 | protected $_text; |
||
23 | |||
24 | /** \brief Data received in callback query */ |
||
25 | protected $_data; |
||
26 | |||
27 | /** \brief Query sent by the user in the inline query */ |
||
28 | protected $_query; |
||
29 | |||
30 | /** \brief Store the inline keyboard */ |
||
31 | public $keyboard; |
||
32 | |||
33 | /** \brief Pdo reference */ |
||
34 | public $pdo; |
||
35 | |||
36 | /** \brief Redis connection */ |
||
37 | public $redis; |
||
38 | |||
39 | /** @} */ |
||
40 | |||
41 | /** |
||
42 | * \addtogroup Core Core(internal) |
||
43 | * @{ |
||
44 | */ |
||
45 | |||
46 | /** \brief Store the command triggered on message. */ |
||
47 | private $message_commands; |
||
48 | |||
49 | /** \brief Does the bot has message commands? Set by initBot. */ |
||
50 | private $message_commands_set; |
||
51 | |||
52 | /** \brief Store the command triggered on callback query. */ |
||
53 | private $callback_commands; |
||
54 | |||
55 | /** \brief Does the bot has message commands? Set by initBot. */ |
||
56 | private $callback_commands_set; |
||
57 | |||
58 | /** @} */ |
||
59 | |||
60 | /** |
||
61 | * \addtogroup Multilanguage Multilanguage |
||
62 | * \brief Methods to create a localized bot. |
||
63 | * @{ |
||
64 | */ |
||
65 | |||
66 | /** \brief Store the language for a multi-language bot */ |
||
67 | public $language; |
||
68 | |||
69 | /** \brief Store localization data */ |
||
70 | public $local; |
||
71 | |||
72 | /** \brief Table contaning bot users data in the sql database. */ |
||
73 | public $user_table = '"User"'; |
||
74 | |||
75 | /** \brief Name of the column that represents the user id in the sql database */ |
||
76 | public $id_column = 'chat_id'; |
||
77 | |||
78 | /** @} */ |
||
79 | |||
80 | /** \addtogroup State |
||
81 | * @{ |
||
82 | */ |
||
83 | |||
84 | /** \brief Status of the bot to handle data inserting and menu-like bot. */ |
||
85 | public $status; |
||
86 | |||
87 | /** @} */ |
||
88 | |||
89 | /** |
||
90 | * \addtogroup Bot |
||
91 | * @{ |
||
92 | */ |
||
93 | |||
94 | /** |
||
95 | * \brief Construct an empy bot. |
||
96 | * \details Construct a bot with commands, multilanguage and status. |
||
97 | */ |
||
98 | public function __construct(string $token) { |
||
110 | |||
111 | /** \brief Descruct the class. */ |
||
112 | public function __destruct() { |
||
124 | |||
125 | /** |
||
126 | * \brief Get the text of the message, if set (for updates of type "message"). |
||
127 | * @return Text of the message, empty string if not set. |
||
128 | */ |
||
129 | public function getMessageText() : string { |
||
140 | |||
141 | /** |
||
142 | * \brief Get the data of callback query, if set (for updates of type "callback_query"). |
||
143 | * @return Data of the callback query, empty string if not set. |
||
144 | */ |
||
145 | public function getCallbackData() : string { |
||
156 | |||
157 | /** |
||
158 | * \brief Get the query received from the inline query (for updates of type "inline_query"). |
||
159 | * @return The query sent by the user, throw exception if the current update is not an inline query. |
||
160 | */ |
||
161 | public function getInlineQuery() : string { |
||
171 | |||
172 | /** |
||
173 | * \brief Get update and process it. |
||
174 | * \details Call this method if you are using webhook. |
||
175 | * It will get update from php::\input, check it and then process it using processUpdate. |
||
176 | */ |
||
177 | public function processWebhookUpdate() { |
||
184 | |||
185 | /** @} */ |
||
186 | |||
187 | /** |
||
188 | * \addtogroup Core Core(Internal) |
||
189 | * @{ |
||
190 | */ |
||
191 | |||
192 | /** |
||
193 | * \brief Init variables to skip parsing commands if there aren't any. |
||
194 | * \details Called internnaly by |
||
195 | * - <code>getUpdatesLocal</code> |
||
196 | * - <code>getUpdatesRedis</code> |
||
197 | * - <code>getUpdatesDatabase</code> |
||
198 | * - <code>processWebhookUpdate</code> |
||
199 | */ |
||
200 | private function initBot() { |
||
209 | |||
210 | /** |
||
211 | * \brief Dispatch each update to the right method (processMessage, processCallbackQuery, etc). |
||
212 | * \details Set $chat_id for each update, $text, $data and $query are set for each update that contains them. |
||
213 | * It also calls commands for each updates, before process methods. |
||
214 | * @param $update Reference to the update received. |
||
215 | * @return The id of the update processed. |
||
216 | */ |
||
217 | public function processUpdate(array $update) : int { |
||
374 | |||
375 | /** @} */ |
||
376 | |||
377 | /** |
||
378 | * \addtogroup Bot Bot |
||
379 | * @{ |
||
380 | */ |
||
381 | |||
382 | /** |
||
383 | * \brief Called every message received by the bot. |
||
384 | * \details Override it to script the bot answer for each message. |
||
385 | * <code>$chat_id</code> and <code>$text</code>, if the message contains text(use getMessageText() to access it), set inside of this function. |
||
386 | * @param $message Reference to the message received. |
||
387 | */ |
||
388 | protected function processMessage($message) {} |
||
389 | |||
390 | /** |
||
391 | * \brief Called every callback query received by the bot. |
||
392 | * \details Override it to script the bot answer for each callback. |
||
393 | * <code>$chat_id</code> and <code>$data</code>, if set in the callback query(use getCallbackData() to access it) set inside of this function. |
||
394 | * @param $callback_query Reference to the callback query received. |
||
395 | */ |
||
396 | protected function processCallbackQuery($callback_query) {} |
||
397 | |||
398 | /** |
||
399 | * \brief Called every inline query received by the bot. |
||
400 | * \details Override it to script the bot answer for each inline query. |
||
401 | * $chat_id and $query(use getInlineQuery() to access it) set inside of this function. |
||
402 | * @param $inline_query Reference to the inline query received. |
||
403 | */ |
||
404 | protected function processInlineQuery($inline_query) {} |
||
405 | |||
406 | /** |
||
407 | * \brief Called every chosen inline result received by the bot. |
||
408 | * \details Override it to script the bot answer for each chosen inline result. |
||
409 | * <code>$chat_id</code> set inside of this function. |
||
410 | * @param $chosen_inline_result Reference to the chosen inline result received. |
||
411 | */ |
||
412 | protected function processChosenInlineResult($chosen_inline_result) {} |
||
413 | |||
414 | /** |
||
415 | * \brief Called every chosen edited message received by the bot. |
||
416 | * \details Override it to script the bot answer for each edited message. |
||
417 | * <code>$chat_id</code> set inside of this function. |
||
418 | * @param $edited_message The message edited by the user. |
||
419 | */ |
||
420 | protected function processEditedMessage($edited_message) {} |
||
421 | |||
422 | /** |
||
423 | * \brief Called every new post in the channel where the bot is in. |
||
424 | * \details Override it to script the bot answer for each post sent in a channel. |
||
425 | * <code>$chat_id</code> set inside of this function. |
||
426 | * @param $post The message sent in the channel. |
||
427 | */ |
||
428 | protected function processChannelPost($post) {} |
||
429 | |||
430 | /** |
||
431 | * \brief Called every time a post get edited in the channel where the bot is in. |
||
432 | * \details Override it to script the bot answer for each post edited in a channel. |
||
433 | * <code>$chat_id</code> set inside of this function. |
||
434 | * @param $post The message edited in the channel. |
||
435 | */ |
||
436 | protected function processEditedChannelPost($edited_post) {} |
||
437 | |||
438 | /** |
||
439 | * \brief Get updates received by the bot, using redis to save and get the last offset. |
||
440 | * \details It check if an offset exists on redis, then get it, or call getUpdates to set it. |
||
441 | * Then it start an infinite loop where it process updates and update the offset on redis. |
||
442 | * Each update is surrounded by a try/catch. |
||
443 | * @see getUpdates |
||
444 | * @param $limit <i>Optional</i>. Limits the number of updates to be retrieved. Values between 1—100 are accepted. |
||
445 | * @param $timeout <i>Optional</i>. Timeout in seconds for long polling. |
||
446 | * @param $offset_key <i>Optional</i>. Name of the variable where the offset is saved on Redis |
||
447 | */ |
||
448 | public function getUpdatesRedis(int $limit = 100, int $timeout = 60, string $offset_key = 'offset') { |
||
507 | |||
508 | /** |
||
509 | * \brief Get updates received by the bot, and hold the offset in $offset. |
||
510 | * \details Get the update_id of the first update to parse, set it in $offset and |
||
511 | * then it start an infinite loop where it processes updates and keep $offset on the update_id of the last update received. |
||
512 | * Each processUpdate() method call is surrounded by a try/catch. |
||
513 | * @see getUpdates |
||
514 | * @param $limit <i>Optional</i>. Limits the number of updates to be retrieved. Values between 1—100 are accepted. |
||
515 | * @param $timeout <i>Optional</i>. Timeout in seconds for long polling. |
||
516 | */ |
||
517 | public function getUpdatesLocal(int $limit = 100, int $timeout = 60) { |
||
571 | |||
572 | /** |
||
573 | * \brief Get updates received by the bot, using the sql database to store and get the last offset. |
||
574 | * \details It check if an offset exists on redis, then get it, or call getUpdates to set it. |
||
575 | * Then it start an infinite loop where it process updates and update the offset on redis. |
||
576 | * Each update is surrounded by a try/catch. |
||
577 | * @see getUpdates |
||
578 | * @param $limit <i>Optional</i>. Limits the number of updates to be retrieved. Values between 1—100 are accepted. |
||
579 | * @param $timeout <i>Optional</i>. Timeout in seconds for long polling. |
||
580 | * @param $table_name <i>Optional</i>. Name of the table where offset is saved in the database |
||
581 | * @param $column_name <i>Optional</i>. Name of the column where the offset is saved in the database |
||
582 | */ |
||
583 | public function getUpdatesDatabase(int $limit = 100, int $timeout = 0, string $table_name = 'telegram', string $column_name = 'bot_offset') { |
||
650 | |||
651 | /** |
||
652 | * \brief Add a function that will be executed everytime a message contain the selected command |
||
653 | * \details Use this syntax: |
||
654 | * |
||
655 | * addMessageCommand("start", function($bot, $message) { |
||
656 | * $bot->sendMessage("Hi"); }); |
||
657 | * @param $command The command that will trigger this function (without slash). Eg: "start", "help", "about" |
||
658 | * @param $script The function that will be triggered by a command. Must take an object(the bot) and an array(the message received). |
||
659 | */ |
||
660 | public function addMessageCommand(string $command, callable $script) { |
||
670 | |||
671 | /** |
||
672 | * \brief Add a function that will be executed everytime a message contain a command that match the regex |
||
673 | * \details Use this syntax: |
||
674 | * |
||
675 | * addMessageCommandRegex("number\d", function($bot, $message, $result) { |
||
676 | * $bot->sendMessage("You sent me a number"); }); |
||
677 | * @param $regex_rule Regex rule that will called for evalueting the command received. |
||
678 | * @param $script The function that will be triggered by a command. Must take an object(the bot) and an array(the message received). |
||
679 | */ |
||
680 | public function addMessageCommandRegex(string $regex_rule, callable $script) { |
||
689 | |||
690 | /** |
||
691 | * \brief Add a function that will be executed everytime a callback query contains a string as data |
||
692 | * \details Use this syntax: |
||
693 | * |
||
694 | * addMessageCommand("menu", function($bot, $callback_query) { |
||
695 | * $bot->editMessageText($callback_query['message']['message_id'], "This is the menu"); }); |
||
696 | * @param $data The string that will trigger this function. |
||
697 | * @param $script The function that will be triggered by the callback query if it contains the $data string. Must take an object(the bot) and an array(the callback query received). |
||
698 | */ |
||
699 | public function addCallbackCommand(string $data, callable $script) { |
||
707 | |||
708 | /** @} */ |
||
709 | |||
710 | /** |
||
711 | * \addtogroup Multilanguage Multilanguage |
||
712 | * @{ |
||
713 | */ |
||
714 | |||
715 | /** |
||
716 | * \brief Get current user language from the database, and set it in $language. |
||
717 | * @param $default_language <i>Optional</i>. Default language to return in case of errors. |
||
718 | * @return Language set for the current user, $default_language on errors. |
||
719 | */ |
||
720 | public function getLanguageDatabase($default_language = 'en') { |
||
769 | |||
770 | /** |
||
771 | * \brief Get current user language from redis, and set it in language. |
||
772 | * \details Using redis database we get language stored and the value does not expires. |
||
773 | * @param $default_language <i>Optional</i>. Default language to return in case of errors. |
||
774 | * @return Language for the current user, $default_language on errors. |
||
775 | */ |
||
776 | public function getLanguageRedis($default_language = 'en') : string { |
||
802 | |||
803 | /** |
||
804 | * \brief Get current user language from redis, as a cache, and set it in language. |
||
805 | * \details Using redis database as cache, seeks the language in it, if there isn't |
||
806 | * then get the language from the sql database and store it (with default expiring of one day) in redis. |
||
807 | * It also change $language parameter of the bot to the language returned. |
||
808 | * @param $default_language <i>Optional</i>. Default language to return in case of errors. |
||
809 | * @param $expiring_time <i>Optional</i>. Set the expiring time for the language on redis each time it is took from the sql database. |
||
810 | * @return Language for the current user, $default_language on errors. |
||
811 | */ |
||
812 | public function getLanguageRedisAsCache($default_language = 'en', $expiring_time = '86400') : string { |
||
838 | |||
839 | /** |
||
840 | * \brief Set the current user language in both redis, sql database and $language. |
||
841 | * \details Save it on database first, then create the expiring key on redis. |
||
842 | * @param $language The new language to set. |
||
843 | * @param $expiring_time <i>Optional</i>. Time for the language key in redis to expire. |
||
844 | * @return On sucess, return true, throw exception otherwise. |
||
845 | */ |
||
846 | public function setLanguageRedisAsCache($language, $expiring_time = '86400') { |
||
877 | |||
878 | /** |
||
879 | * \brief Load localization files (JSON-serialized) from a folder and set them in $local variable. |
||
880 | * \details Save all localization files, saved as json format, from a directory and put the contents in $local variable. |
||
881 | * Each file will be saved into $local with the first two letters of the filename as the index. |
||
882 | * Access the english data as $this->local["en"]["Your key"]. |
||
883 | * File <code>./localization/en.json</code>: |
||
884 | * |
||
885 | * {"Hello_Msg": "Hello"} |
||
886 | * |
||
887 | * File <code>./localization/it.json</code>: |
||
888 | * |
||
889 | * {"Hello_Msg": "Ciao"} |
||
890 | * |
||
891 | * Usage in <code>processMessage()</code>: |
||
892 | * |
||
893 | * $sendMessage($this->local[$this->language]["Hello_Msg"]); |
||
894 | * |
||
895 | * @param $dir Directory where the localization files are saved. |
||
896 | */ |
||
897 | public function loadLocalization($dir = './localization') { |
||
927 | |||
928 | /** @} */ |
||
929 | |||
930 | /** |
||
931 | * \addtogroup State |
||
932 | * \brief Create a state based bot using these methods. |
||
933 | * \details Bot will answer in different way based on the state. |
||
934 | * Here is an example where we use save user credential using bot states: |
||
935 | * |
||
936 | * <?php |
||
937 | * |
||
938 | * // Include the framework |
||
939 | * require './vendor/autoload.php'; |
||
940 | * |
||
941 | * // Define bot state |
||
942 | * define("SEND_USERNAME", 1); |
||
943 | * define("SEND_PASSWORD", 2); |
||
944 | * |
||
945 | * // Create the class for the bot that will handle login |
||
946 | * class LoginBot extends DanySpin97\PhpBotFramework\Bot { |
||
947 | * |
||
948 | * // Add the function for processing messages |
||
949 | * protected function processMessage($message) { |
||
950 | * |
||
951 | * switch($this->getStatus()) { |
||
952 | * |
||
953 | * // If we are expecting a username from the user |
||
954 | * case SEND_USERNAME: |
||
955 | * |
||
956 | * // Save the username |
||
957 | * |
||
958 | * // Say the user to insert the password |
||
959 | * $this->sendMessage("Please, send your password."); |
||
960 | * |
||
961 | * // Update the bot state |
||
962 | * $this->setStatus(SEND_PASSWORD); |
||
963 | * |
||
964 | * break; |
||
965 | * |
||
966 | * // Or if we are expecting a password from the user |
||
967 | * case SEND_PASSWORD: |
||
968 | * |
||
969 | * // Save the password |
||
970 | * |
||
971 | * // Say the user he completed the process |
||
972 | * $this->sendMessage("The registration is complete"); |
||
973 | * |
||
974 | * break; |
||
975 | * } |
||
976 | * |
||
977 | * } |
||
978 | * |
||
979 | * } |
||
980 | * |
||
981 | * // Create the bot |
||
982 | * $bot = new LoginBot("token"); |
||
983 | * |
||
984 | * // Create redis object |
||
985 | * $bot->redis = new Redis(); |
||
986 | * |
||
987 | * // Connect to redis database |
||
988 | * $bot->redis->connect('127.0.0.1'); |
||
989 | * |
||
990 | * // Create the awnser to the <code>/start</code> command |
||
991 | * $start_closure = function($bot, $message) { |
||
992 | * |
||
993 | * // saying the user to enter a username |
||
994 | * $bot->sendMessage("Please, send your username."); |
||
995 | * |
||
996 | * // and update the status |
||
997 | * $bot->setStatus(SEND_USERNAME); |
||
998 | * }; |
||
999 | * |
||
1000 | * // Add the answer |
||
1001 | * $bot->addMessageCommand("start", $start_closure); |
||
1002 | * |
||
1003 | * $bot->getUpdatesLocal(); |
||
1004 | * @{ |
||
1005 | */ |
||
1006 | |||
1007 | /** |
||
1008 | * \brief Get current user status from redis and set it in status variable. |
||
1009 | * \details Throw exception if redis connection is missing. |
||
1010 | * @param $default_status <i>Optional</i>. The default status to return in case there is no status for the current user. |
||
1011 | * @return The status for the current user, $default_status if missing. |
||
1012 | */ |
||
1013 | public function getStatus(int $default_status = -1) : int { |
||
1034 | |||
1035 | /** \brief Set the status of the bot in both redis and $status. |
||
1036 | * \details Throw exception if redis connection is missing. |
||
1037 | * @param $status The new status of the bot. |
||
1038 | */ |
||
1039 | public function setStatus(int $status) { |
||
1046 | |||
1047 | /** @} */ |
||
1048 | |||
1049 | /** |
||
1050 | * \addtogroup Users-handle Users handling |
||
1051 | * \brief Handle bot users on the database. |
||
1052 | * @{ |
||
1053 | */ |
||
1054 | |||
1055 | /** \brief Add a user to the database. |
||
1056 | * \details Add a user to the database in Bot::$user_table table and Bot::$id_column column using Bot::$pdo connection. |
||
1057 | * @param $chat_id chat_id of the user to add. |
||
1058 | * @return True on success. |
||
1059 | */ |
||
1060 | public function addUser($chat_id) : bool { |
||
1098 | |||
1099 | /** |
||
1100 | * \brief Broadcast a message to all user registred on the database. |
||
1101 | * \details Send a message to all users subscribed, change Bot::$user_table and Bot::$id_column to match your database structure is. |
||
1102 | * This method requires Bot::$pdo connection set. |
||
1103 | * All parameters are the same as CoreBot::sendMessage. |
||
1104 | * Because a limitation of Telegram Bot API the bot will have a delay after 20 messages sent in different chats. |
||
1105 | * @see CoreBot::sendMessage |
||
1106 | */ |
||
1107 | public function broadcastMessage($text, string $reply_markup = null, string $parse_mode = 'HTML', bool $disable_web_preview = true, bool $disable_notification = false) { |
||
1152 | |||
1153 | /** @} */ |
||
1154 | |||
1155 | } |
||
1156 |
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
Both the
$myVar
assignment in line 1 and the$higher
assignment in line 2 are dead. The first because$myVar
is never used and the second because$higher
is always overwritten for every possible time line.