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 mysql_compiler 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 mysql_compiler, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 4 | class mysql_compiler extends sql_compiler { |
||
| 5 | protected $tbl_prefix; |
||
| 6 | protected $store; |
||
| 7 | protected $in_orderby; |
||
| 8 | protected $nls_join; |
||
| 9 | protected $select_tables; |
||
| 10 | protected $used_tables; |
||
| 11 | protected $custom_id; |
||
| 12 | protected $custom_ref; |
||
| 13 | protected $used_custom_fields; |
||
| 14 | protected $where_s_ext; |
||
| 15 | protected $fulltext_expr; |
||
| 16 | protected $where_s; |
||
| 17 | protected $limit_s; |
||
| 18 | protected $orderby_s; |
||
| 19 | |||
| 20 | |||
| 21 | public function __construct (&$store, $tbl_prefix="") { |
||
| 26 | |||
| 27 | 13 | protected function compile_tree(&$node, $arguments=null) { |
|
| 28 | 13 | if ($arguments) { |
|
| 29 | extract($arguments); |
||
| 30 | } |
||
| 31 | 13 | switch ((string)$node["id"]) { |
|
| 32 | 13 | case 'property': |
|
| 33 | 12 | $table=$this->tbl_prefix.$node["table"]; |
|
| 34 | 12 | $field=$node["field"]; |
|
| 35 | 12 | $record_id=$node["record_id"]; |
|
| 36 | 12 | if (!$record_id) { |
|
| 37 | 12 | View Code Duplication | if ($this->in_orderby && $node["nls"]) { |
| 38 | /* |
||
| 39 | we do a left join so that we will also find non |
||
| 40 | matching objects |
||
| 41 | */ |
||
| 42 | $objects_table = $this->tbl_prefix."objects"; |
||
| 43 | $aliastable = $table.$node['nls']; |
||
| 44 | $this->nls_join[$table.$node['nls']] = "left join $table as order_$aliastable on $objects_table.id=order_$aliastable.object and order_$aliastable.AR_nls='".$node["nls"]."' "; |
||
| 45 | $result = " order_$aliastable.$field "; |
||
| 46 | } else { |
||
| 47 | /* |
||
| 48 | if we are parsing 'orderby' properties we have |
||
| 49 | to join our tables for the whole query |
||
| 50 | */ |
||
| 51 | 12 | $this->select_tables[$table]=$table; |
|
| 52 | 12 | $this->used_tables[$table]=$table; |
|
| 53 | 12 | $result=" $table.$field "; |
|
| 54 | } |
||
| 55 | 12 | } else { |
|
| 56 | $this->used_tables["$table as $table$record_id"] = $table.$record_id; |
||
| 57 | if (!$this->in_orderby && !$no_context_join) { |
||
| 58 | if ($this->join_target_properties[$node["table"]][":$record_id"]) { |
||
| 59 | $result=" $table$record_id.object = target.object and $table$record_id.$field "; |
||
| 60 | } else { |
||
| 61 | $result=" $table$record_id.object = ".$this->tbl_prefix."objects.id and $table$record_id.$field "; |
||
| 62 | } |
||
| 63 | } else { |
||
| 64 | if ($this->join_target_properties[$node["table"]]) { |
||
| 65 | $this->join_target_properties["$table as $table$record_id"] = $table.$record_id; |
||
| 66 | } |
||
| 67 | $this->select_tables["$table as $table$record_id"] = $table.$record_id; |
||
| 68 | $result=" $table$record_id.$field "; |
||
| 69 | } |
||
| 70 | } |
||
| 71 | 12 | break; |
|
| 72 | 13 | View Code Duplication | case 'ident': |
| 73 | 1 | $table=$this->tbl_prefix.$node["table"]; |
|
| 74 | 1 | $field=$node["field"]; |
|
| 75 | 1 | $this->used_tables[$table]=$table; |
|
| 76 | 1 | $result=" $table.$field "; |
|
| 77 | 1 | break; |
|
| 78 | 13 | View Code Duplication | case 'custom': |
| 79 | $table = $this->tbl_prefix."prop_custom"; |
||
| 80 | $field = $node["field"]; |
||
| 81 | $nls = $node["nls"]; |
||
| 82 | $record_id = $node["record_id"]; |
||
| 83 | /* |
||
| 84 | when we are compiling orderby properties we always want |
||
| 85 | to assign it to a new table alias |
||
| 86 | */ |
||
| 87 | if ($this->in_orderby) { |
||
| 88 | $this->custom_id++; |
||
| 89 | } |
||
| 90 | $this->custom_ref++; |
||
| 91 | if (!$record_id) { |
||
| 92 | $this->used_tables[$table." as $table".$this->custom_id] = $table.$this->custom_id; |
||
| 93 | $this->select_tables[$table." as $table".$this->custom_id] = 1; |
||
| 94 | |||
| 95 | $this->used_custom_fields[$field] = true; |
||
| 96 | $result = " $table".$this->custom_id.".AR_name = '$field' "; |
||
| 97 | if ($nls) { |
||
| 98 | $result = " $result and $table".$this->custom_id.".AR_nls = '$nls' "; |
||
| 99 | } |
||
| 100 | |||
| 101 | if (!$this->in_orderby) { |
||
| 102 | $result = " $result and $table".$this->custom_id.".AR_value "; |
||
| 103 | } else { |
||
| 104 | $this->where_s_ext = $result; |
||
| 105 | $result = " $table".$this->custom_id.".AR_value "; |
||
| 106 | } |
||
| 107 | } else { |
||
| 108 | $this->used_tables["$table as $table$record_id"] = $table.$record_id; |
||
| 109 | //$this->select_tables[$table." as $table$record_id"] = 1; |
||
| 110 | |||
| 111 | $result = " $table$record_id.AR_name = '$field' "; |
||
| 112 | if (!$this->in_orderby && !$no_context_join) { |
||
| 113 | if ($this->join_target_properties["prop_my"][":$record_id"]) { |
||
| 114 | $result=" $result and $table$record_id.object = target.object and $table$record_id.AR_value "; |
||
| 115 | } else { |
||
| 116 | $result=" $table$record_id.object = ".$this->tbl_prefix."objects.id and $table$record_id.AR_value "; |
||
| 117 | } |
||
| 118 | } else { |
||
| 119 | if ($this->join_target_properties[$node["table"]]) { |
||
| 120 | $this->join_target_properties["$table as $table$record_id"] = $table.$record_id; |
||
| 121 | } |
||
| 122 | $this->select_tables["$table as $table$record_id"] = $table.$record_id; |
||
| 123 | $result=" $table$record_id.AR_value "; |
||
| 124 | } |
||
| 125 | } |
||
| 126 | break; |
||
| 127 | 13 | case 'string': |
|
| 128 | 12 | $result = "'".addSlashes($node["value"])."'"; |
|
| 129 | 12 | if ($escape_chars) { |
|
| 130 | $result = preg_replace('/([^\\\])_/', '\\1\_', $result); |
||
| 131 | } |
||
| 132 | 12 | return $result; |
|
| 133 | break; |
||
| 134 | 13 | case 'float': |
|
| 135 | 13 | case 'int': |
|
| 136 | 1 | $result=$node["value"]; |
|
| 137 | 1 | break; |
|
| 138 | 13 | View Code Duplication | case 'and': |
| 139 | $cr = $this->custom_ref; |
||
| 140 | $left=$this->compile_tree($node["left"]); |
||
| 141 | if ($this->custom_ref > $cr) { |
||
| 142 | $this->custom_id++; |
||
| 143 | } |
||
| 144 | |||
| 145 | $right=$this->compile_tree($node["right"]); |
||
| 146 | $cr = $this->custom_ref; |
||
| 147 | if ($this->custom_ref > $cr) { |
||
| 148 | $this->custom_id++; |
||
| 149 | } |
||
| 150 | $result=" $left and $right "; |
||
| 151 | break; |
||
| 152 | 13 | View Code Duplication | case 'or': |
| 153 | $left=$this->compile_tree($node["left"]); |
||
| 154 | $right=$this->compile_tree($node["right"]); |
||
| 155 | $result=" $left or $right "; |
||
| 156 | break; |
||
| 157 | 13 | case 'cmp': |
|
| 158 | 13 | $not=""; |
|
| 159 | 13 | switch ($node["operator"]) { |
|
| 160 | 13 | case '=': |
|
| 161 | 13 | case '==': |
|
| 162 | 13 | $operator="="; |
|
| 163 | 13 | break; |
|
| 164 | case '!=': |
||
| 165 | case '<=': |
||
| 166 | case '>=': |
||
| 167 | case '<': |
||
| 168 | case '>': |
||
| 169 | $operator=$node["operator"]; |
||
| 170 | break; |
||
| 171 | case '!~': |
||
| 172 | case '!~~': |
||
| 173 | $not="NOT "; |
||
| 174 | case '~=': |
||
| 175 | case '=~': |
||
| 176 | case '=~~': |
||
| 177 | $likeOp = true; |
||
| 178 | $operator=$not."LIKE"; |
||
| 179 | /* double tildes indicate case-sensitive */ |
||
| 180 | if (strlen($operator)==3) { |
||
| 181 | $operator.=" BINARY"; |
||
| 182 | } |
||
| 183 | break; |
||
| 184 | case '!/': |
||
| 185 | case '!//': |
||
| 186 | $not="NOT "; |
||
| 187 | case '=/': |
||
| 188 | case '=//': |
||
| 189 | $operator=$not."REGEXP"; |
||
| 190 | /* double slashes indicate case-sensitive */ |
||
| 191 | if (strlen($operator)==3) { |
||
| 192 | $operator.=" BINARY"; |
||
| 193 | } |
||
| 194 | break; |
||
| 195 | case '!*': |
||
| 196 | case '!**': |
||
| 197 | $not = " not"; |
||
| 198 | case '=*': |
||
| 199 | case '=**': |
||
| 200 | if ($node["left"]["id"]!=="implements" && $this->store->is_supported("fulltext")) { |
||
| 201 | $left=$this->compile_tree($node["left"], array("no_context_join" => true)); |
||
| 202 | $right=$this->compile_tree($node["right"]); |
||
| 203 | /* fulltext search operators: =*, !*, =**, !** */ |
||
| 204 | $operator = $node["operator"]; |
||
| 205 | $query = stripslashes(substr($right,1,-1)); |
||
| 206 | if (strlen($operator)==3 && $this->store->is_supported("fulltext_boolean")) { |
||
| 207 | /* double asterisks indicate boolean mode */ |
||
| 208 | /* make sure the operators are not formatted_for_fti */ |
||
| 209 | $storeclass=get_class($this->store); |
||
| 210 | $query = preg_replace( |
||
| 211 | '%(^|\s)([-+~<>(]*)("([^"]*)"|([^ "*]*))([)*]?)%e', |
||
| 212 | "'\\1\\2'.('\\4'?'\"'.$storeclass::format_for_fti('\\4').'\"':$storeclass::format_for_fti('\\5')).'\\6'", |
||
| 213 | $query); |
||
| 214 | $boolmode = " in boolean mode"; |
||
| 215 | } else { |
||
| 216 | $boolmode = ""; |
||
| 217 | $query = $this->store->format_for_fti($query); |
||
| 218 | } |
||
| 219 | $result = "$not match ($left) against ('$query'$boolmode) "; |
||
| 220 | $this->fulltext_expr[':'.$node["right"]["record_id"]] = $result; |
||
| 221 | return $result; |
||
| 222 | } |
||
| 223 | break; |
||
| 224 | 13 | } |
|
| 225 | 13 | if ($node["left"]["id"]!=="implements") { |
|
| 226 | 13 | $left=$this->compile_tree($node["left"]); |
|
| 227 | 13 | if ($likeOp) { |
|
| 228 | $right=$this->compile_tree($node["right"], array('escape_chars' => true)); |
||
| 229 | } else { |
||
| 230 | 13 | $right=$this->compile_tree($node["right"]); |
|
| 231 | } |
||
| 232 | /* lastchanged == unixtimestamp -> lastchanged == 200201.. */ |
||
| 233 | 13 | if ($node["left"]["field"]=="lastchanged") { |
|
| 234 | $right = date("YmdHis", $right); |
||
| 235 | } |
||
| 236 | 13 | $result=" $left $operator $right "; |
|
| 237 | 13 | } else { |
|
| 238 | $type = $this->compile_tree($node["right"]); |
||
| 239 | switch ($operator) { |
||
| 240 | View Code Duplication | case '!=': |
|
| 241 | $result=" (".$this->tbl_prefix."objects.type not in (select type from ".$this->tbl_prefix."types where implements = $type )) "; |
||
| 242 | break; |
||
| 243 | View Code Duplication | default: |
|
| 244 | $result=" ( SUBSTRING_INDEX(".$this->tbl_prefix."objects.vtype, '.', 1) in (select type from ".$this->tbl_prefix."types where implements $operator $type )) "; |
||
| 245 | break; |
||
| 246 | } |
||
| 247 | } |
||
| 248 | 13 | break; |
|
| 249 | 13 | View Code Duplication | case 'group': |
| 250 | $left=$this->compile_tree($node["left"]); |
||
| 251 | if ($left) { |
||
| 252 | $result=" ( $left ) "; |
||
| 253 | } |
||
| 254 | break; |
||
| 255 | |||
| 256 | 13 | View Code Duplication | case 'orderby': |
| 257 | 12 | $result=$this->compile_tree($node["left"]); |
|
| 258 | 12 | $this->orderby_s=$this->compile_tree($node["right"]); |
|
| 259 | 12 | break; |
|
| 260 | |||
| 261 | 13 | case 'orderbyfield': |
|
| 262 | 12 | $this->in_orderby = true; |
|
| 263 | 12 | $left=$this->compile_tree($node["left"]); |
|
| 264 | 12 | if ( $node["right"]["field"] != "none" ) { |
|
| 265 | if ($node["right"]["field"] == 'AR_relevance' && $this->store->is_supported("fulltext")) { |
||
| 266 | $right = $this->fulltext_expr[':'.$node["right"]["record_id"]]; |
||
| 267 | } else { |
||
| 268 | $right=$this->compile_tree($node["right"]); |
||
| 269 | } |
||
| 270 | if ($left) { |
||
| 271 | $result=" $left , $right ".$node["type"]." "; |
||
| 272 | } else { |
||
| 273 | $result=" $right ".$node["type"]." "; |
||
| 274 | } |
||
| 275 | } else { |
||
| 276 | 12 | $result = ""; |
|
| 277 | 12 | if ($left) { |
|
| 278 | $result = " $left "; |
||
| 279 | } |
||
| 280 | 12 | $this->skipDefaultOrderBy = true; |
|
| 281 | } |
||
| 282 | 12 | break; |
|
| 283 | |||
| 284 | 13 | View Code Duplication | case 'limit': |
| 285 | 13 | $this->where_s=$this->compile_tree($node["left"]); |
|
| 286 | 13 | if ($node["limit"]) { |
|
| 287 | 1 | $this->limit_s=" limit ".(int)$node["offset"].", ".$node["limit"]." "; |
|
| 288 | 13 | } else if ($node["offset"]) { |
|
| 289 | $this->limit_s=" limit ".(int)$node["offset"]." "; |
||
| 290 | } else { |
||
| 291 | 12 | if ($this->limit) { |
|
| 292 | $offset = (int)$this->offset; |
||
| 293 | $this->limit_s=" limit $offset, ".(int)$this->limit." "; |
||
| 294 | } |
||
| 295 | } |
||
| 296 | 13 | break; |
|
| 297 | } |
||
| 298 | 13 | return $result; |
|
| 299 | } |
||
| 300 | |||
| 301 | // mysql specific compiler function |
||
| 302 | protected function priv_sql_compile($tree) { |
||
| 375 | |||
| 376 | } |
||
| 377 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)or! empty(...)instead.