Completed
Push — php7.2-travis ( 2bd3b2 )
by
unknown
15:40 queued 09:23
created

mysql_compiler   D

Complexity

Total Complexity 86

Size/Duplication

Total Lines 373
Duplicated Lines 36.73 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 38.85%

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 137
loc 373
ccs 122
cts 314
cp 0.3885
rs 4.8717
wmc 86
lcom 1
cbo 1

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
F priv_sql_compile() 16 73 13
F compile_tree() 121 273 72

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 4 and the first side effect is on line 2.

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.

Loading history...
2
  include_once($this->code."stores/modules/sql_compiler.php");
3
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 20
	public function __construct (&$store, $tbl_prefix="") {
22 20
		debug("mysql_compiler($tbl_prefix)", "store");
23 20
		$this->tbl_prefix=$tbl_prefix;
24 20
		$this->store=$store;
25 20
	}
26
27 18
	protected function compile_tree(&$node, $arguments=null) {
28 18
		if ($arguments) {
29
			extract($arguments);
30
		}
31 18
		switch ((string)$node["id"]) {
32 18
			case 'property':
33 14
				$table=$this->tbl_prefix.$node["table"];
34 14
				$field=$node["field"];
35 14
				$record_id=$node["record_id"];
36 14
				if (!$record_id) {
37 14 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 14
						$this->select_tables[$table]=$table;
52 14
						$this->used_tables[$table]=$table;
53 14
						$result=" $table.$field ";
54
					}
55 14
				} 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 14
			break;
72 18 View Code Duplication
			case 'ident':
73 2
				$table=$this->tbl_prefix.$node["table"];
74 2
				$field=$node["field"];
75 2
				$this->used_tables[$table]=$table;
76 2
				$result=" $table.$field ";
77 2
			break;
78 18 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 18
			case 'string':
128 14
				$result = "'".addSlashes($node["value"])."'";
129 14
				if ($escape_chars) {
130
					$result = preg_replace('/([^\\\])_/', '\\1\_', $result);
131
				}
132 14
				return $result;
133
			break;
134 18
			case 'float':
135 18
			case 'int':
136 1
				$result=$node["value"];
137 1
			break;
138 18 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 18 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 18
			case 'cmp':
158 15
				$not="";
159 15
				switch ($node["operator"]) {
160 15
					case '=':
161 15
					case '==':
162 15
						$operator="=";
163 15
					break;
164
					case '!=':
165
					case '<=':
166
					case '>=':
167
					case '<':
168
					case '>':
169
						$operator=$node["operator"];
170
					break;
171
					case '!~':
172
					case '!~~':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
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 '!//':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
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 '!**':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
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 15
				}
225 15
				if ($node["left"]["id"]!=="implements") {
226 15
					$left=$this->compile_tree($node["left"]);
227 15
					if ($likeOp) {
228
						$right=$this->compile_tree($node["right"], array('escape_chars' => true));
229
					} else {
230 15
						$right=$this->compile_tree($node["right"]);
231
					}
232
					/* lastchanged == unixtimestamp -> lastchanged == 200201.. */
233 15
					if ($node["left"]["field"]=="lastchanged") {
234
						$right = date("YmdHis", $right);
235
					}
236 15
					$result=" $left $operator $right ";
237 15
				} 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 15
			break;
249 18 View Code Duplication
			case 'group':
250
				$left=$this->compile_tree($node["left"]);
251
				if ($left) {
252
					$result=" ( $left ) ";
253
				}
254
			break;
255
256 18 View Code Duplication
			case 'orderby':
257 14
				$result=$this->compile_tree($node["left"]);
258 14
				$this->orderby_s=$this->compile_tree($node["right"]);
259 14
			break;
260
261 18
			case 'orderbyfield':
262 14
				$this->in_orderby = true;
263 14
				$left=$this->compile_tree($node["left"]);
264 14
				if ( $node["right"]["field"] != "none" ) {
265 1
					if ($node["right"]["field"] == 'AR_relevance' && $this->store->is_supported("fulltext")) {
266
						$right = $this->fulltext_expr[':'.$node["right"]["record_id"]];
267
					} else {
268 1
						$right=$this->compile_tree($node["right"]);
269
					}
270 1
					if ($left) {
271
						$result=" $left ,  $right ".$node["type"]." ";
272
					} else {
273 1
						$result=" $right ".$node["type"]." ";
274
					}
275 1
				} else {
276 14
					$result = "";
277 14
					if ($left) {
278
						$result = " $left ";
279
					}
280 14
					$this->skipDefaultOrderBy = true;
281
				}
282 14
			break;
283
284 18 View Code Duplication
			case 'limit':
285 18
				$this->where_s=$this->compile_tree($node["left"]);
286 18
				if ($node["limit"]) {
287 6
					$this->limit_s=" limit ".(int)$node["offset"].", ".$node["limit"]." ";
288 18
				} 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 18
			break;
297
		}
298 18
		return $result;
299
	}
300
301
	// mysql specific compiler function
302 18
	protected function priv_sql_compile($tree) {
303 18
		$this->custom_ref = 0;
304 18
		$this->custom_id = 0;
305 18
		$this->used_tables = array();
306 18
		$this->compile_tree($tree);
307
308 18
		$prop_dep = '';
309 18
		$query = '';
310 18
		$orderby = '';
311
312 18
		if ( $this->error ) {
313
			return null;
314
		}
315
316 18
		$nodes=$this->tbl_prefix."nodes";
317 18
		$objects=$this->tbl_prefix."objects";
318 18
		$properties=$this->tbl_prefix."prop_";
319 18
		$this->used_tables[$nodes]=$nodes;
320 18
		$this->used_tables[$objects]=$objects;
321 18 View Code Duplication
		if ($this->join_target_properties) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->join_target_properties of type array<*,string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
322
			$this->used_tables[$properties."references as target_reference"] = $properties."references as target_reference";
323
			$this->used_tables["$nodes as target"] = "$nodes as target";
324
		}
325
326 18
		$tables = implode(', ',array_keys($this->used_tables));
327 18
		foreach ( $this->used_tables as $key => $val){
328 18 View Code Duplication
			if ($this->select_tables[$key]) {
329 14
				if ($this->join_target_properties[$key]) {
330
					$prop_dep.=" and $val.object=target.object ";
331
				} else {
332 14
					$prop_dep.=" and $val.object=$objects.id ";
333
				}
334 14
			}
335 18
		}
336
337 18
		$join = "";
338 18
		if (is_array($this->nls_join)) {
339
			$join = implode("", $this->nls_join);
340
		}
341
342 18
		$query.=" where $nodes.object=$objects.id $prop_dep";
343 18
		$query.=" and $nodes.path like '".str_replace('_','\\_',AddSlashes($this->path))."%' ";
344 18
		if ($this->where_s) {
345 15
			$query.=" and ( $this->where_s ) ";
346 15
		}
347 18
		if ($this->where_s_ext) {
348
			$query .= " and ($this->where_s_ext) ";
349
		}
350
		/* do target join */
351 18
		if ($this->join_target_properties) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->join_target_properties of type array<*,string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
352
			$query .= " and $objects.id = target_reference.object ";
353
			$query .= " and target.path = target_reference.AR_path ";
354
		}
355
356 18
		if ($this->orderby_s) {
357 1 View Code Duplication
			if ($this->skipDefaultOrderBy) {
358 1
				$orderby = " order by $this->orderby_s ";
359 1
			} else {
360
				$orderby = " order by $this->orderby_s, $nodes.parent ASC, $nodes.priority DESC, $nodes.path ASC ";
361
			}
362 18
		} else if (!$this->skipDefaultOrderBy) {
363 4
			$orderby = " order by $nodes.parent ASC, $nodes.priority DESC, $nodes.path ASC ";
364 4
		}
365
366 18
		$select_query = "select distinct($nodes.path), $objects.id, $nodes.parent, $nodes.priority, $objects.type, ".
367 18
		                " UNIX_TIMESTAMP($objects.lastchanged) as lastchanged, $objects.vtype ";
368 18
		$select_query .= "from ($tables) $join $query ";
369
370 18
		$select_query .= $orderby . " $this->limit_s ";
371 18
		$count_query = "select count(distinct($nodes.path)) as count from $tables ".$query;
372
373 18
		return array("select_query" => $select_query, "count_query" => $count_query);
374
	}
375
376
  }
377