| Total Complexity | 61 | 
| Total Lines | 443 | 
| Duplicated Lines | 0 % | 
| Changes | 1 | ||
| Bugs | 0 | Features | 0 | 
Complex classes like Unread 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.
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 Unread, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 21 | class Unread  | 
            ||
| 22 | { | 
            ||
| 23 | const UNREAD = 0;  | 
            ||
| 24 | const UNREADREPLIES = 1;  | 
            ||
| 25 | |||
| 26 | /** @var bool */  | 
            ||
| 27 | private $_ascending = false;  | 
            ||
| 28 | /** @var string */  | 
            ||
| 29 | private $_sort_query = '';  | 
            ||
| 30 | /** @var int */  | 
            ||
| 31 | private $_num_topics = 0;  | 
            ||
| 32 | /** @var int */  | 
            ||
| 33 | private $_min_message = 0;  | 
            ||
| 34 | /** @var int */  | 
            ||
| 35 | private $_action = self::UNREAD;  | 
            ||
| 36 | /** @var int */  | 
            ||
| 37 | private $_earliest_msg = 0;  | 
            ||
| 38 | /** @var bool */  | 
            ||
| 39 | private $_showing_all_topics = false;  | 
            ||
| 40 | /** @var int */  | 
            ||
| 41 | private $_user_id = 0;  | 
            ||
| 42 | /** @var bool */  | 
            ||
| 43 | private $_post_mod = false;  | 
            ||
| 44 | /** @var bool */  | 
            ||
| 45 | private $_unwatch = false;  | 
            ||
| 46 | /** @var Database|null */  | 
            ||
| 47 | private $_db = null;  | 
            ||
| 48 | /** @var int|string */  | 
            ||
| 49 | private $_preview_bodies = 0;  | 
            ||
| 50 | |||
| 51 | /**  | 
            ||
| 52 | * Parameters for the main query.  | 
            ||
| 53 | */  | 
            ||
| 54 | private $_query_parameters = array();  | 
            ||
| 55 | |||
| 56 | /**  | 
            ||
| 57 | * Constructor  | 
            ||
| 58 | *  | 
            ||
| 59 | * @param int $user - ID of the user  | 
            ||
| 60 | * @param bool|int $post_mod - if post moderation is active or not  | 
            ||
| 61 | * @param bool|int $unwatch - if unwatch topics is active or not  | 
            ||
| 62 | * @param bool|int $showing_all_topics - Is the user looking at all the unread  | 
            ||
| 63 | * replies, or the recent topics?  | 
            ||
| 64 | */  | 
            ||
| 65 | public function __construct($user, $post_mod, $unwatch, $showing_all_topics = false)  | 
            ||
| 66 | 	{ | 
            ||
| 67 | $this->_user_id = (int) $user;  | 
            ||
| 68 | $this->_post_mod = (bool) $post_mod;  | 
            ||
| 69 | $this->_unwatch = (bool) $unwatch;  | 
            ||
| 70 | $this->_showing_all_topics = (bool) $showing_all_topics;  | 
            ||
| 71 | |||
| 72 | $this->_db = database();  | 
            ||
| 73 | }  | 
            ||
| 74 | |||
| 75 | /**  | 
            ||
| 76 | * Sets the boards the member is looking at  | 
            ||
| 77 | *  | 
            ||
| 78 | * @param int|int[] $boards - the id of the boards  | 
            ||
| 79 | */  | 
            ||
| 80 | public function setBoards($boards)  | 
            ||
| 81 | 	{ | 
            ||
| 82 | if (is_array($boards))  | 
            ||
| 83 | $this->_query_parameters['boards'] = $boards;  | 
            ||
| 84 | else  | 
            ||
| 85 | $this->_query_parameters['boards'] = array($boards);  | 
            ||
| 86 | }  | 
            ||
| 87 | |||
| 88 | /**  | 
            ||
| 89 | * The action the user is performing  | 
            ||
| 90 | *  | 
            ||
| 91 | * @param int $action - Unread::UNREAD, Unread::UNREADREPLIES  | 
            ||
| 92 | */  | 
            ||
| 93 | public function setAction($action)  | 
            ||
| 94 | 	{ | 
            ||
| 95 | if (in_array($action, array(self::UNREAD, self::UNREADREPLIES)))  | 
            ||
| 96 | $this->_action = $action;  | 
            ||
| 97 | }  | 
            ||
| 98 | |||
| 99 | /**  | 
            ||
| 100 | * Sets the lower message id to be taken in consideration  | 
            ||
| 101 | *  | 
            ||
| 102 | * @param int $msg_id - id of the earliest message to consider  | 
            ||
| 103 | */  | 
            ||
| 104 | public function setEarliestMsg($msg_id)  | 
            ||
| 105 | 	{ | 
            ||
| 106 | $this->_earliest_msg = (int) $msg_id;  | 
            ||
| 107 | }  | 
            ||
| 108 | |||
| 109 | /**  | 
            ||
| 110 | * Sets the sorting query and the direction  | 
            ||
| 111 | *  | 
            ||
| 112 | * @param string $query - The query to be used in the ORDER clause  | 
            ||
| 113 | * @param bool|int $asc - If the sorting is ascending or not  | 
            ||
| 114 | */  | 
            ||
| 115 | public function setSorting($query, $asc)  | 
            ||
| 116 | 	{ | 
            ||
| 117 | $this->_sort_query = $query;  | 
            ||
| 118 | $this->_ascending = $asc;  | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 119 | }  | 
            ||
| 120 | |||
| 121 | /**  | 
            ||
| 122 | * Return the sorting direction  | 
            ||
| 123 | *  | 
            ||
| 124 | * @return boolean  | 
            ||
| 125 | */  | 
            ||
| 126 | public function isSortAsc()  | 
            ||
| 127 | 	{ | 
            ||
| 128 | return $this->_ascending;  | 
            ||
| 129 | }  | 
            ||
| 130 | |||
| 131 | /**  | 
            ||
| 132 | * Sets if the data returned by the class will include a shorted version  | 
            ||
| 133 | * of the body of the last message.  | 
            ||
| 134 | *  | 
            ||
| 135 | * @param bool|int $chars - The number of chars to retrieve.  | 
            ||
| 136 | * If true it will return the entire body,  | 
            ||
| 137 | * if 0 no preview will be generated.  | 
            ||
| 138 | */  | 
            ||
| 139 | public function bodyPreview($chars)  | 
            ||
| 140 | 	{ | 
            ||
| 141 | if ($chars === true)  | 
            ||
| 142 | $this->_preview_bodies = 'all';  | 
            ||
| 143 | else  | 
            ||
| 144 | $this->_preview_bodies = (int) $chars;  | 
            ||
| 145 | }  | 
            ||
| 146 | |||
| 147 | /**  | 
            ||
| 148 | * Counts the number of unread topics or messages  | 
            ||
| 149 | *  | 
            ||
| 150 | * @param bool $first_login - If this is the first login of the user  | 
            ||
| 151 | * @param int $id_msg_last_visit - highest id_msg found during the last visit  | 
            ||
| 152 | */  | 
            ||
| 153 | public function numUnreads($first_login = false, $id_msg_last_visit = 0)  | 
            ||
| 154 | 	{ | 
            ||
| 155 | if ($this->_action === self::UNREAD)  | 
            ||
| 156 | $this->_countRecentTopics($first_login, $id_msg_last_visit);  | 
            ||
| 157 | else  | 
            ||
| 158 | $this->_countUnreadReplies();  | 
            ||
| 159 | |||
| 160 | return $this->_num_topics;  | 
            ||
| 161 | }  | 
            ||
| 162 | |||
| 163 | /**  | 
            ||
| 164 | * Retrieves unread topics or messages  | 
            ||
| 165 | *  | 
            ||
| 166 | * @param string $join - kind of "JOIN" to execute. If 'topic' JOINs boards on  | 
            ||
| 167 | 	 *                       the topics table, otherwise ('message') the JOIN is on | 
            ||
| 168 | * the messages table  | 
            ||
| 169 | * @param int $start - position to start the query  | 
            ||
| 170 | * @param int $limit - number of entries to grab  | 
            ||
| 171 | * @param bool $include_avatars - if avatars should be retrieved as well  | 
            ||
| 172 | * @return mixed[] - see Topic_Util::prepareContext  | 
            ||
| 173 | */  | 
            ||
| 174 | public function getUnreads($join, $start, $limit, $include_avatars)  | 
            ||
| 180 | }  | 
            ||
| 181 | |||
| 182 | /**  | 
            ||
| 183 | * Retrieves unread topics, used in *all* unread replies with temp table and  | 
            ||
| 184 | * new posts since last visit  | 
            ||
| 185 | *  | 
            ||
| 186 | * @param string $join - kind of "JOIN" to execute. If 'topic' JOINs boards on  | 
            ||
| 187 | 	 *                       the topics table, otherwise ('message') the JOIN is on | 
            ||
| 188 | * the messages table  | 
            ||
| 189 | * @param int $start - position to start the query  | 
            ||
| 190 | * @param int $limit - number of entries to grab  | 
            ||
| 191 | * @param bool|int $include_avatars - if avatars should be retrieved as well  | 
            ||
| 192 | * @return mixed[] - see Topic_Util::prepareContext  | 
            ||
| 193 | */  | 
            ||
| 194 | private function _getUnreadTopics($join, $start, $limit, $include_avatars = false)  | 
            ||
| 195 | 	{ | 
            ||
| 196 | if ($this->_preview_bodies == 'all')  | 
            ||
| 197 | $body_query = 'ml.body AS last_body, ms.body AS first_body,';  | 
            ||
| 198 | else  | 
            ||
| 199 | 		{ | 
            ||
| 200 | // If empty, no preview at all  | 
            ||
| 201 | if (empty($this->_preview_bodies))  | 
            ||
| 202 | $body_query = '';  | 
            ||
| 203 | // Default: a SUBSTRING  | 
            ||
| 204 | else  | 
            ||
| 205 | $body_query = 'SUBSTRING(ml.body, 1, ' . ($this->_preview_bodies + 256) . ') AS last_body, SUBSTRING(ms.body, 1, ' . ($this->_preview_bodies + 256) . ') AS first_body,';  | 
            ||
| 206 | }  | 
            ||
| 207 | |||
| 208 | if (!empty($include_avatars))  | 
            ||
| 209 | 		{ | 
            ||
| 210 | // Double equal comparison for 1 because it is backward compatible with 1.0 where the value was true/false  | 
            ||
| 211 | if ($include_avatars == 1 || $include_avatars === 3)  | 
            ||
| 212 | 			{ | 
            ||
| 213 | 				$custom_selects = array('meml.avatar', 'COALESCE(a.id_attach, 0) AS id_attach', 'a.filename', 'a.attachment_type', 'meml.email_address'); | 
            ||
| 214 | 				$custom_joins = array('LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = ml.id_member AND a.id_member != 0)'); | 
            ||
| 215 | }  | 
            ||
| 216 | else  | 
            ||
| 217 | 			{ | 
            ||
| 218 | $custom_selects = array();  | 
            ||
| 219 | $custom_joins = array();  | 
            ||
| 220 | }  | 
            ||
| 221 | |||
| 222 | if ($include_avatars === 2 || $include_avatars === 3)  | 
            ||
| 223 | 			{ | 
            ||
| 224 | 				$custom_selects = array_merge($custom_selects, array('memf.avatar AS avatar_first', 'COALESCE(af.id_attach, 0) AS id_attach_first', 'af.filename AS filename_first', 'af.attachment_type AS attachment_type_first', 'memf.email_address AS email_address_first')); | 
            ||
| 225 | 				$custom_joins = array_merge($custom_joins, array('LEFT JOIN {db_prefix}attachments AS af ON (af.id_member = mf.id_member AND af.id_member != 0)')); | 
            ||
| 226 | }  | 
            ||
| 227 | }  | 
            ||
| 228 | |||
| 229 | 		$request = $this->_db->query('substring', ' | 
            ||
| 230 | SELECT  | 
            ||
| 231 | ms.subject AS first_subject, ms.poster_time AS first_poster_time, ms.poster_name AS first_member_name,  | 
            ||
| 232 | ms.id_topic, t.id_board, b.name AS bname, t.num_replies, t.num_views, t.num_likes, t.approved,  | 
            ||
| 233 | ms.id_member AS first_id_member, ml.id_member AS last_id_member, ml.poster_name AS last_member_name,  | 
            ||
| 234 | ml.poster_time AS last_poster_time, COALESCE(mems.real_name, ms.poster_name) AS first_display_name,  | 
            ||
| 235 | COALESCE(meml.real_name, ml.poster_name) AS last_display_name, ml.subject AS last_subject,  | 
            ||
| 236 | ml.icon AS last_icon, ms.icon AS first_icon, t.id_poll, t.is_sticky, t.locked, ml.modified_time AS last_modified_time,  | 
            ||
| 237 | COALESCE(lt.id_msg, lmr.id_msg, -1) + 1 AS new_from,  | 
            ||
| 238 | ' . $body_query . '  | 
            ||
| 239 | 				' . (!empty($custom_selects) ? implode(',', $custom_selects) . ', ' : '') . ' | 
            ||
| 240 | ml.smileys_enabled AS last_smileys, ms.smileys_enabled AS first_smileys, t.id_first_msg, t.id_last_msg  | 
            ||
| 241 | 			FROM {db_prefix}messages AS ms | 
            ||
| 242 | 				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ms.id_topic AND t.id_first_msg = ms.id_msg) | 
            ||
| 243 | 				INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)' . ($join == 'topics' ? ' | 
            ||
| 244 | 				LEFT JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)' : ' | 
            ||
| 245 | 				LEFT JOIN {db_prefix}boards AS b ON (b.id_board = ms.id_board)') . ' | 
            ||
| 246 | 				LEFT JOIN {db_prefix}members AS mems ON (mems.id_member = ms.id_member) | 
            ||
| 247 | 				LEFT JOIN {db_prefix}members AS meml ON (meml.id_member = ml.id_member)' . ($this->_have_temp_table ? ' | 
            ||
| 248 | 				LEFT JOIN {db_prefix}log_topics_unread AS lt ON (lt.id_topic = t.id_topic)' : ' | 
            ||
| 249 | 				LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})') . (!empty($custom_joins) ? implode("\n\t\t\t\t", $custom_joins) : '') . ' | 
            ||
| 250 | 				LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member}) | 
            ||
| 251 | 			WHERE t.id_board IN ({array_int:boards}) | 
            ||
| 252 | 				AND t.id_last_msg >= {int:min_message} | 
            ||
| 253 | AND COALESCE(lt.id_msg, lmr.id_msg, 0) < ml.id_msg' .  | 
            ||
| 254 | 				($this->_post_mod ? ' AND ms.approved = {int:is_approved}' : '') . | 
            ||
| 255 | ($this->_unwatch ? ' AND COALESCE(lt.unwatched, 0) != 1' : '') . '  | 
            ||
| 256 | 			ORDER BY {raw:order} | 
            ||
| 257 | 			LIMIT {int:offset}, {int:limit}', | 
            ||
| 258 | array_merge($this->_query_parameters, array(  | 
            ||
| 259 | 'current_member' => $this->_user_id,  | 
            ||
| 260 | 'min_message' => $this->_min_message,  | 
            ||
| 261 | 'is_approved' => 1,  | 
            ||
| 262 | 'order' => $this->_sort_query . ($this->_ascending ? '' : ' DESC'),  | 
            ||
| 263 | 'offset' => $start,  | 
            ||
| 264 | 'limit' => $limit,  | 
            ||
| 265 | ))  | 
            ||
| 266 | );  | 
            ||
| 267 | $topics = array();  | 
            ||
| 268 | while ($row = $this->_db->fetch_assoc($request))  | 
            ||
| 269 | $topics[] = $row;  | 
            ||
| 270 | $this->_db->free_result($request);  | 
            ||
| 271 | |||
| 272 | return Topic_Util::prepareContext($topics, true, ((int) $this->_preview_bodies) + 128);  | 
            ||
| 273 | }  | 
            ||
| 274 | |||
| 275 | /**  | 
            ||
| 276 | * Counts unread replies  | 
            ||
| 277 | */  | 
            ||
| 278 | private function _countUnreadReplies()  | 
            ||
| 279 | 	{ | 
            ||
| 280 | if (!empty($this->_have_temp_table))  | 
            ||
| 281 | 		{ | 
            ||
| 282 | 			$request = $this->_db->query('', ' | 
            ||
| 283 | SELECT COUNT(*)  | 
            ||
| 284 | 				FROM {db_prefix}topics_posted_in AS pi | 
            ||
| 285 | 					LEFT JOIN {db_prefix}log_topics_posted_in AS lt ON (lt.id_topic = pi.id_topic) | 
            ||
| 286 | 				WHERE pi.id_board IN ({array_int:boards}) | 
            ||
| 287 | AND COALESCE(lt.id_msg, pi.id_msg) < pi.id_last_msg',  | 
            ||
| 288 | array_merge($this->_query_parameters, array(  | 
            ||
| 289 | ))  | 
            ||
| 290 | );  | 
            ||
| 291 | list ($this->_num_topics) = $this->_db->fetch_row($request);  | 
            ||
| 292 | $this->_db->free_result($request);  | 
            ||
| 293 | $this->_min_message = 0;  | 
            ||
| 294 | }  | 
            ||
| 295 | else  | 
            ||
| 296 | 		{ | 
            ||
| 297 | 			$request = $this->_db->query('unread_fetch_topic_count', ' | 
            ||
| 298 | SELECT COUNT(DISTINCT t.id_topic), MIN(t.id_last_msg)  | 
            ||
| 299 | 				FROM {db_prefix}topics AS t | 
            ||
| 300 | 					INNER JOIN {db_prefix}messages AS m ON (m.id_topic = t.id_topic) | 
            ||
| 301 | 					LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member}) | 
            ||
| 302 | 					LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_member = {int:current_member}) | 
            ||
| 303 | 				WHERE t.id_board IN ({array_int:boards}) | 
            ||
| 304 | 					AND m.id_member = {int:current_member} | 
            ||
| 305 | AND COALESCE(lt.id_msg, lmr.id_msg, 0) < t.id_last_msg' . ($this->_post_mod ? '  | 
            ||
| 306 | 					AND t.approved = {int:is_approved}' : '') . ($this->_unwatch ? ' | 
            ||
| 307 | AND COALESCE(lt.unwatched, 0) != 1' : ''),  | 
            ||
| 308 | array_merge($this->_query_parameters, array(  | 
            ||
| 309 | 'current_member' => $this->_user_id,  | 
            ||
| 310 | 'is_approved' => 1,  | 
            ||
| 311 | ))  | 
            ||
| 312 | );  | 
            ||
| 313 | list ($this->_num_topics, $this->_min_message) = $this->_db->fetch_row($request);  | 
            ||
| 314 | $this->_db->free_result($request);  | 
            ||
| 315 | }  | 
            ||
| 316 | }  | 
            ||
| 317 | |||
| 318 | /**  | 
            ||
| 319 | * Counts unread topics, used in *all* unread replies with temp table and  | 
            ||
| 320 | * new posts since last visit  | 
            ||
| 321 | *  | 
            ||
| 322 | * @param bool $is_first_login - if the member has already logged in at least  | 
            ||
| 323 | * once, then there is an $id_msg_last_visit  | 
            ||
| 324 | * @param int $id_msg_last_visit - highest id_msg found during the last visit  | 
            ||
| 325 | */  | 
            ||
| 326 | private function _countRecentTopics($is_first_login, $id_msg_last_visit = 0)  | 
            ||
| 349 | }  | 
            ||
| 350 | |||
| 351 | /**  | 
            ||
| 352 | * Retrieves unread replies since last visit  | 
            ||
| 353 | *  | 
            ||
| 354 | * @param int $start - position to start the query  | 
            ||
| 355 | * @param int $limit - number of entries to grab  | 
            ||
| 356 | * @param bool|int $include_avatars - if avatars should be retrieved as well  | 
            ||
| 357 | * @return mixed[] - see Topic_Util::prepareContext  | 
            ||
| 358 | */  | 
            ||
| 359 | private function _getUnreadReplies($start, $limit, $include_avatars = false)  | 
            ||
| 464 | }  | 
            ||
| 465 | }  | 
            ||
| 466 | 
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.