Completed
Push — hash-nonce ( 07e2e8 )
by Sam
08:52
created

SQLConditionalExpression   F

Complexity

Total Complexity 86

Size/Duplication

Total Lines 709
Duplicated Lines 8.6 %

Coupling/Cohesion

Components 4
Dependencies 6

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 61
loc 709
rs 1.0434
c 1
b 1
f 0
wmc 86
lcom 4
cbo 6

32 Methods

Rating   Name   Duplication   Size   Complexity  
A filtersOnID() 9 9 3
A __construct() 0 4 1
A setFrom() 0 4 1
A addFrom() 9 9 3
A setConnective() 0 3 1
A getConnective() 0 3 1
A useDisjunction() 0 3 1
A useConjunction() 0 3 1
A addLeftJoin() 13 13 2
A addInnerJoin() 11 11 2
A addFilterToJoin() 0 4 1
A setJoinFilter() 0 4 1
A isJoinedTo() 0 3 1
B queriedTables() 0 20 6
A getFrom() 0 3 1
D getJoins() 0 43 9
B getOrderedJoins() 0 19 5
C mergesort() 0 42 8
A setWhere() 0 5 2
A addWhere() 8 8 1
A setWhereAny() 0 6 2
A addWhereAny() 0 8 1
A getWhere() 0 3 1
A getWhereParameterised() 0 4 1
C parsePredicate() 0 40 11
B normalisePredicates() 0 15 7
B splitQueryParameters() 0 20 5
A filtersOnFK() 10 10 3
A isEmpty() 0 3 1
A toDelete() 0 5 1
A toSelect() 0 5 1
A toUpdate() 0 5 1

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 SQLConditionalExpression 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 SQLConditionalExpression, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Represents a SQL query for an expression which interacts with existing rows
5
 * (SELECT / DELETE / UPDATE) with a WHERE clause
6
 *
7
 * @package framework
8
 * @subpackage model
9
 */
10
abstract class SQLConditionalExpression extends SQLExpression {
11
12
	/**
13
	 * An array of WHERE clauses.
14
	 *
15
	 * Each item in this array will be in the form of a single-length array
16
	 * in the format array('predicate' => array($parameters))
17
	 *
18
	 * @var array
19
	 */
20
	protected $where = array();
21
22
	/**
23
	 * The logical connective used to join WHERE clauses. Defaults to AND.
24
	 *
25
	 * @var string
26
	 */
27
	protected $connective = 'AND';
28
29
	/**
30
	 * An array of tables. The first one is just the table name.
31
	 * Used as the FROM in DELETE/SELECT statements, the INTO in INSERT statements,
32
	 * and the target table in UPDATE statements
33
	 *
34
	 * The keys of this array are the aliases of the tables (unquoted), where the
35
	 * values are either the literal table names, or an array with join details.
36
	 *
37
	 * @see SQLConditionalExpression::addLeftJoin()
38
	 *
39
	 * @var array
40
	 */
41
	protected $from = array();
42
43
	/**
44
	 * Construct a new SQLInteractExpression.
45
	 *
46
	 * @param array|string $from An array of Tables (FROM clauses). The first one should be just the table name.
47
	 * @param array $where An array of WHERE clauses.
48
	 */
49
	function __construct($from = array(), $where = array()) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
50
		$this->setFrom($from);
51
		$this->setWhere($where);
52
	}
53
54
	/**
55
	 * Sets the list of tables to query from or update
56
	 *
57
	 * @example $query->setFrom('"MyTable"'); // SELECT * FROM "MyTable"
58
	 *
59
	 * @param string|array $from Single, or list of, ANSI quoted table names
60
	 * @return self
61
	 */
62
	public function setFrom($from) {
63
		$this->from = array();
64
		return $this->addFrom($from);
65
	}
66
67
	/**
68
	 * Add a table to include in the query or update
69
	 *
70
	 * @example $query->addFrom('"MyTable"'); // SELECT * FROM "MyTable"
71
	 *
72
	 * @param string|array $from Single, or list of, ANSI quoted table names
73
	 * @return self Self reference
74
	 */
75 View Code Duplication
	public function addFrom($from) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
76
		if(is_array($from)) {
77
			$this->from = array_merge($this->from, $from);
78
		} elseif(!empty($from)) {
79
			$this->from[str_replace(array('"','`'), '', $from)] = $from;
80
		}
81
82
		return $this;
83
	}
84
85
	/**
86
	 * Set the connective property.
87
	 *
88
	 * @param string $value either 'AND' or 'OR'
89
	 */
90
	public function setConnective($value) {
91
		$this->connective = $value;
92
	}
93
94
	/**
95
	 * Get the connective property.
96
	 *
97
	 * @return string 'AND' or 'OR'
98
	 */
99
	public function getConnective() {
100
		return $this->connective;
101
	}
102
103
	/**
104
	 * Use the disjunctive operator 'OR' to join filter expressions in the WHERE clause.
105
	 */
106
	public function useDisjunction() {
107
		$this->setConnective('OR');
108
	}
109
110
	/**
111
	 * Use the conjunctive operator 'AND' to join filter expressions in the WHERE clause.
112
	 */
113
	public function useConjunction() {
114
		$this->setConnective('AND');
115
	}
116
117
	/**
118
	 * Add a LEFT JOIN criteria to the tables list.
119
	 *
120
	 * @param string $table Unquoted table name
121
	 * @param string $onPredicate The "ON" SQL fragment in a "LEFT JOIN ... AS ... ON ..." statement, Needs to be valid
122
	 *                            (quoted) SQL.
123
	 * @param string $tableAlias Optional alias which makes it easier to identify and replace joins later on
124
	 * @param int $order A numerical index to control the order that joins are added to the query; lower order values
125
	 *                   will cause the query to appear first. The default is 20, and joins created automatically by the
126
	 *                   ORM have a value of 10.
127
	 * @param array $parameters Any additional parameters if the join is a parameterised subquery
128
	 * @return self Self reference
129
	 */
130 View Code Duplication
	public function addLeftJoin($table, $onPredicate, $tableAlias = '', $order = 20, $parameters = array()) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
131
		if(!$tableAlias) {
132
			$tableAlias = $table;
133
		}
134
		$this->from[$tableAlias] = array(
135
			'type' => 'LEFT',
136
			'table' => $table,
137
			'filter' => array($onPredicate),
138
			'order' => $order,
139
			'parameters' => $parameters
140
		);
141
		return $this;
142
	}
143
144
	/**
145
	 * Add an INNER JOIN criteria
146
	 *
147
	 * @param string $table Unquoted table name
148
	 * @param string $onPredicate The "ON" SQL fragment in an "INNER JOIN ... AS ... ON ..." statement. Needs to be
149
	 * valid (quoted) SQL.
150
	 * @param string $tableAlias Optional alias which makes it easier to identify and replace joins later on
151
	 * @param int $order A numerical index to control the order that joins are added to the query; lower order
152
	 * values will cause the query to appear first. The default is 20, and joins created automatically by the
153
	 * ORM have a value of 10.
154
	 * @param array $parameters Any additional parameters if the join is a parameterised subquery
155
	 * @return self Self reference
156
	 */
157 View Code Duplication
	public function addInnerJoin($table, $onPredicate, $tableAlias = null, $order = 20, $parameters = array()) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
158
		if(!$tableAlias) $tableAlias = $table;
0 ignored issues
show
Bug Best Practice introduced by
The expression $tableAlias of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
159
		$this->from[$tableAlias] = array(
160
			'type' => 'INNER',
161
			'table' => $table,
162
			'filter' => array($onPredicate),
163
			'order' => $order,
164
			'parameters' => $parameters
165
		);
166
		return $this;
167
	}
168
169
	/**
170
	 * Add an additional filter (part of the ON clause) on a join.
171
	 *
172
	 * @param string $table Table to join on from the original join (unquoted)
173
	 * @param string $filter The "ON" SQL fragment (escaped)
174
	 * @return self Self reference
175
	 */
176
	public function addFilterToJoin($table, $filter) {
177
		$this->from[$table]['filter'][] = $filter;
178
		return $this;
179
	}
180
181
	/**
182
	 * Set the filter (part of the ON clause) on a join.
183
	 *
184
	 * @param string $table Table to join on from the original join (unquoted)
185
	 * @param string $filter The "ON" SQL fragment (escaped)
186
	 * @return self Self reference
187
	 */
188
	public function setJoinFilter($table, $filter) {
189
		$this->from[$table]['filter'] = array($filter);
190
		return $this;
191
	}
192
193
	/**
194
	 * Returns true if we are already joining to the given table alias
195
	 *
196
	 * @param string $tableAlias Table name
197
	 * @return boolean
198
	 */
199
	public function isJoinedTo($tableAlias) {
200
		return isset($this->from[$tableAlias]);
201
	}
202
203
	/**
204
	 * Return a list of tables that this query is selecting from.
205
	 *
206
	 * @return array Unquoted table names
207
	 */
208
	public function queriedTables() {
209
		$tables = array();
210
211
		foreach($this->from as $key => $tableClause) {
212
			if(is_array($tableClause)) {
213
				$table = '"'.$tableClause['table'].'"';
214
			} else if(is_string($tableClause) && preg_match('/JOIN +("[^"]+") +(AS|ON) +/i', $tableClause, $matches)) {
215
				$table = $matches[1];
216
			} else {
217
				$table = $tableClause;
218
			}
219
220
			// Handle string replacements
221
			if($this->replacementsOld) $table = str_replace($this->replacementsOld, $this->replacementsNew, $table);
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->replacementsOld of type array 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...
222
223
			$tables[] = preg_replace('/^"|"$/','',$table);
224
		}
225
226
		return $tables;
227
	}
228
229
	/**
230
	 * Return a list of tables queried
231
	 *
232
	 * @return array
233
	 */
234
	public function getFrom() {
235
		return $this->from;
236
	}
237
238
	/**
239
	 * Retrieves the finalised list of joins
240
	 *
241
	 * @todo This part of the code could be simplified
242
	 *
243
	 * @param array $parameters Out variable for parameters required for this query
244
	 * @return array List of joins as a mapping from array('Alias' => 'Join Expression')
245
	 */
246
	public function getJoins(&$parameters = array()) {
247
		if(func_num_args() == 0) {
248
			Deprecation::notice(
249
				'4.0',
250
				'SQLConditionalExpression::getJoins() now may produce parameters which are necessary to
251
				execute this query'
252
			);
253
		}
254
255
		// Sort the joins
256
		$parameters = array();
257
		$joins = $this->getOrderedJoins($this->from);
258
259
		// Build from clauses
260
		foreach($joins as $alias => $join) {
261
			// $join can be something like this array structure
262
			// array('type' => 'inner', 'table' => 'SiteTree', 'filter' => array("SiteTree.ID = 1",
263
			// "Status = 'approved'", 'order' => 20))
264
			if(!is_array($join)) continue;
265
266
			if(is_string($join['filter'])) {
267
				$filter = $join['filter'];
268
			} elseif(sizeof($join['filter']) == 1) {
269
				$filter = $join['filter'][0];
270
			} else {
271
				$filter = "(" . implode(") AND (", $join['filter']) . ")";
272
			}
273
274
			// Ensure tables are quoted, unless the table is actually a sub-select
275
			$table = preg_match('/\bSELECT\b/i', $join['table'])
276
				? $join['table']
277
				: "\"{$join['table']}\"";
278
			$aliasClause = ($alias != $join['table'])
279
				? " AS \"{$alias}\""
280
				: "";
281
			$joins[$alias] = strtoupper($join['type']) . " JOIN " . $table . "$aliasClause ON $filter";
282
			if(!empty($join['parameters'])) {
283
				$parameters = array_merge($parameters, $join['parameters']);
284
			}
285
		}
286
287
		return $joins;
288
	}
289
290
	/**
291
	 * Ensure that framework "auto-generated" table JOINs are first in the finalised SQL query.
292
	 * This prevents issues where developer-initiated JOINs attempt to JOIN using relations that haven't actually
293
	 * yet been scaffolded by the framework. Demonstrated by PostGres in errors like:
294
	 *"...ERROR: missing FROM-clause..."
295
	 *
296
	 * @param $from array - in the format of $this->from
297
	 * @return array - and reorderded list of selects
298
	 */
299
	protected function getOrderedJoins($from) {
300
		// shift the first FROM table out from so we only deal with the JOINs
301
		$baseFrom = array_shift($from);
302
		$this->mergesort($from, function($firstJoin, $secondJoin) {
303
			if(
304
				!is_array($firstJoin)
305
				|| !is_array($secondJoin)
306
				|| $firstJoin['order'] == $secondJoin['order']
307
			) {
308
				return 0;
309
			} else {
310
				return ($firstJoin['order'] < $secondJoin['order']) ?  -1 : 1;
311
			}
312
		});
313
314
		// Put the first FROM table back into the results
315
		array_unshift($from, $baseFrom);
316
		return $from;
317
	}
318
319
	/**
320
	 * Since uasort don't preserve the order of an array if the comparison is equal
321
	 * we have to resort to a merge sort. It's quick and stable: O(n*log(n)).
322
	 *
323
	 * @see http://stackoverflow.com/q/4353739/139301
324
	 *
325
	 * @param array &$array - the array to sort
326
	 * @param callable $cmpFunction - the function to use for comparison
327
	 */
328
	protected function mergesort(&$array, $cmpFunction = 'strcmp') {
329
		// Arrays of size < 2 require no action.
330
		if (count($array) < 2) {
331
			return;
332
		}
333
		// Split the array in half
334
		$halfway = count($array) / 2;
335
		$array1 = array_slice($array, 0, $halfway);
336
		$array2 = array_slice($array, $halfway);
337
		// Recurse to sort the two halves
338
		$this->mergesort($array1, $cmpFunction);
339
		$this->mergesort($array2, $cmpFunction);
340
		// If all of $array1 is <= all of $array2, just append them.
341
		if(call_user_func($cmpFunction, end($array1), reset($array2)) < 1) {
342
			$array = array_merge($array1, $array2);
343
			return;
344
		}
345
		// Merge the two sorted arrays into a single sorted array
346
		$array = array();
347
		$val1 = reset($array1);
348
		$val2 = reset($array2);
349
		do {
350
			if (call_user_func($cmpFunction, $val1, $val2) < 1) {
351
				$array[key($array1)] = $val1;
352
				$val1 = next($array1);
353
			} else {
354
				$array[key($array2)] = $val2;
355
				$val2 = next($array2);
356
			}
357
		} while($val1 && $val2);
358
359
		// Merge the remainder
360
		while($val1) {
361
			$array[key($array1)] = $val1;
362
			$val1 = next($array1);
363
		}
364
		while($val2) {
365
			$array[key($array2)] = $val2;
366
			$val2 = next($array2);
367
		}
368
		return;
369
	}
370
371
	/**
372
	 * Set a WHERE clause.
373
	 *
374
	 * @see SQLConditionalExpression::addWhere() for syntax examples
375
	 *
376
	 * @param mixed $where Predicate(s) to set, as escaped SQL statements or paramaterised queries
377
	 * @param mixed $where,... Unlimited additional predicates
0 ignored issues
show
Bug introduced by
There is no parameter named $where,.... Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
378
	 * @return self Self reference
379
	 */
380
	public function setWhere($where) {
381
		$where = func_num_args() > 1 ? func_get_args() : $where;
382
		$this->where = array();
383
		return $this->addWhere($where);
384
	}
385
386
	/**
387
	 * Adds a WHERE clause.
388
	 *
389
	 * Note that the database will execute any parameterised queries using
390
	 * prepared statements whenever available.
391
	 *
392
	 * There are several different ways of doing this.
393
	 *
394
	 * <code>
395
	 *  // the entire predicate as a single string
396
	 *  $query->addWhere("\"Column\" = 'Value'");
397
	 *
398
	 *  // multiple predicates as an array
399
	 *  $query->addWhere(array("\"Column\" = 'Value'", "\"Column\" != 'Value'"));
400
	 *
401
	 *  // Shorthand for the above using argument expansion
402
	 *  $query->addWhere("\"Column\" = 'Value'", "\"Column\" != 'Value'");
403
	 *
404
	 *  // multiple predicates with parameters
405
	 *  $query->addWhere(array('"Column" = ?' => $column, '"Name" = ?' => $value)));
406
	 *
407
	 *  // Shorthand for simple column comparison (as above), omitting the '?'
408
	 *  $query->addWhere(array('"Column"' => $column, '"Name"' => $value));
409
	 *
410
	 *  // Multiple predicates, each with multiple parameters.
411
	 *  $query->addWhere(array(
412
	 *		'"ColumnOne" = ? OR "ColumnTwo" != ?' => array(1, 4),
413
	 *		'"ID" != ?' => $value
414
	 *	));
415
	 *
416
	 *	// Using a dynamically generated condition (any object that implements SQLConditionGroup)
417
	 *	$condition = new ObjectThatImplements_SQLConditionGroup();
418
	 *	$query->addWhere($condition);
419
	 *
420
	 * </code>
421
	 *
422
	 * Note that if giving multiple parameters for a single predicate the array
423
	 * of values must be given as an indexed array, not an associative array.
424
	 *
425
	 * Also should be noted is that any null values for parameters may give unexpected
426
	 * behaviour. array('Column' => NULL) is shorthand for array('Column = ?', NULL), and
427
	 * will not match null values for that column, as 'Column IS NULL' is the correct syntax.
428
	 *
429
	 * Additionally, be careful of key conflicts. Adding two predicates with the same
430
	 * condition but different parameters can cause a key conflict if added in the same array.
431
	 * This can be solved by wrapping each individual condition in an array. E.g.
432
	 *
433
	 * <code>
434
	 * // Multiple predicates with duplicate conditions
435
	 *  $query->addWhere(array(
436
	 *		array('ID != ?' => 5),
437
	 *		array('ID != ?' => 6)
438
	 *	));
439
	 *
440
	 * // Alternatively this can be added in two separate calls to addWhere
441
	 * $query->addWhere(array('ID != ?' => 5));
442
	 * $query->addWhere(array('ID != ?' => 6));
443
	 *
444
	 * // Or simply omit the outer array
445
	 * $query->addWhere(array('ID != ?' => 5), array('ID != ?' => 6));
446
	 * </code>
447
	 *
448
	 * If it's necessary to force the parameter to be considered as a specific data type
449
	 * by the database connector's prepared query processor any parameter can be cast
450
	 * to that type by using the following format.
451
	 *
452
	 * <code>
453
	 *  // Treat this value as a double type, regardless of its type within PHP
454
	 *  $query->addWhere(array(
455
	 *		'Column' => array(
456
	 *			'value' => $variable,
457
	 *			'type' => 'double'
458
	 *		)
459
	 *	));
460
	 * </code>
461
	 *
462
	 * @param mixed $where Predicate(s) to set, as escaped SQL statements or paramaterised queries
463
	 * @param mixed $where,... Unlimited additional predicates
0 ignored issues
show
Bug introduced by
There is no parameter named $where,.... Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
464
	 * @return self Self reference
465
	 */
466 View Code Duplication
	public function addWhere($where) {
0 ignored issues
show
Unused Code introduced by
The parameter $where is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
467
		$where = $this->normalisePredicates(func_get_args());
468
469
		// If the function is called with an array of items
470
		$this->where = array_merge($this->where, $where);
471
472
		return $this;
473
	}
474
475
	/**
476
	 * @see SQLConditionalExpression::addWhere()
477
	 *
478
	 * @param mixed $filters Predicate(s) to set, as escaped SQL statements or paramaterised queries
479
	 * @param mixed $filters,... Unlimited additional predicates
0 ignored issues
show
Documentation introduced by
There is no parameter named $filters,.... Did you maybe mean $filters?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
480
	 * @return self Self reference
481
	 */
482
	public function setWhereAny($filters) {
483
		$filters = func_num_args() > 1 ? func_get_args() : $filters;
484
		return $this
485
			->setWhere(array())
486
			->addWhereAny($filters);
487
	}
488
489
	/**
490
	 * @see SQLConditionalExpression::addWhere()
491
	 *
492
	 * @param mixed $filters Predicate(s) to set, as escaped SQL statements or paramaterised queries
493
	 * @param mixed $filters,... Unlimited additional predicates
0 ignored issues
show
Documentation introduced by
There is no parameter named $filters,.... Did you maybe mean $filters?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
494
	 * @return self Self reference
495
	 */
496
	public function addWhereAny($filters) {
0 ignored issues
show
Unused Code introduced by
The parameter $filters is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
497
		// Parse and split predicates along with any parameters
498
		$filters = $this->normalisePredicates(func_get_args());
499
		$this->splitQueryParameters($filters, $predicates, $parameters);
500
501
		$clause = "(".implode(") OR (", $predicates).")";
502
		return $this->addWhere(array($clause => $parameters));
503
	}
504
505
	/**
506
	 * Return a list of WHERE clauses used internally.
507
	 *
508
	 * @return array
509
	 */
510
	public function getWhere() {
511
		return $this->where;
512
	}
513
514
	/**
515
	 * Return a list of WHERE clauses used internally.
516
	 *
517
	 * @param array $parameters Out variable for parameters required for this query
518
	 * @return array
519
	 */
520
	public function getWhereParameterised(&$parameters) {
521
		$this->splitQueryParameters($this->where, $predicates, $parameters);
522
		return $predicates;
523
	}
524
525
	/**
526
	 * Given a key / value pair, extract the predicate and any potential paramaters
527
	 * in a format suitable for storing internally as a list of paramaterised conditions.
528
	 *
529
	 * @param string|integer $key The left hand (key index) of this condition.
530
	 * Could be the predicate or an integer index.
531
	 * @param mixed $value The The right hand (array value) of this condition.
532
	 * Could be the predicate (if non-paramaterised), or the parameter(s). Could also be
533
	 * an array containing a nested condition in the similar format this function outputs.
534
	 * @return array|SQLConditionGroup A single item array in the format
535
	 * array($predicate => array($parameters)), unless it's a SQLConditionGroup
536
	 */
537
	protected function parsePredicate($key, $value) {
538
		// If a string key is given then presume this is a paramaterised condition
539
		if($value instanceof SQLConditionGroup) {
540
			return $value;
541
		} elseif(is_string($key)) {
542
543
			// Extract the parameter(s) from the value
544
			if(!is_array($value) || isset($value['type'])) {
545
				$parameters = array($value);
546
			} else {
547
				$parameters = array_values($value);
548
			}
549
550
			// Append '= ?' if not present, parameters are given, and we have exactly one parameter
551
			if(strpos($key, '?') === FALSE) {
552
				$parameterCount = count($parameters);
553
				if($parameterCount === 1) {
554
					$key .= " = ?";
555
				} elseif($parameterCount > 1) {
556
					user_error("Incorrect number of '?' in predicate $key. Expected $parameterCount but none given.",
557
						E_USER_ERROR);
558
				}
559
			}
560
			return array($key => $parameters);
561
		} elseif(is_array($value)) {
562
563
			// If predicates are nested one per array (as per the internal format)
564
			// then run a quick check over the contents and recursively parse
565
			if(count($value) != 1) {
566
				user_error('Nested predicates should be given as a single item array in '
567
						.  'array($predicate => array($prameters)) format)', E_USER_ERROR);
568
			}
569
			foreach($value as $key => $value) {
570
				return $this->parsePredicate($key, $value);
571
			}
572
		} else {
573
			// Non-paramaterised condition
574
			return array($value => array());
575
		}
576
	}
577
578
	/**
579
	 * Given a list of conditions in any user-acceptable format, convert this
580
	 * to an array of paramaterised predicates suitable for merging with $this->where.
581
	 *
582
	 * Normalised predicates are in the below format, in order to avoid key collisions.
583
	 *
584
	 * <code>
585
	 * array(
586
	 *	array('Condition != ?' => array('parameter')),
587
	 *	array('Condition != ?' => array('otherparameter')),
588
	 *	array('Condition = 3' => array()),
589
	 *	array('Condition = ? OR Condition = ?' => array('parameter1', 'parameter2))
590
	 * )
591
	 * </code>
592
	 *
593
	 * @param array $predicates List of predicates. These should be wrapped in an array
594
	 * one level more than for addWhere, as query expansion is not supported here.
595
	 * @return array List of normalised predicates
596
	 */
597
	protected function normalisePredicates(array $predicates) {
598
		// Since this function is called with func_get_args we should un-nest the single first parameter
599
		if(count($predicates) == 1) $predicates = array_shift($predicates);
600
601
		// Ensure single predicates are iterable
602
		if(!is_array($predicates)) $predicates = array($predicates);
603
604
		$normalised = array();
605
		foreach($predicates as $key => $value) {
606
			if(empty($value) && (empty($key) || is_numeric($key))) continue; // Ignore empty conditions
607
			$normalised[] = $this->parsePredicate($key, $value);
608
		}
609
610
		return $normalised;
611
	}
612
613
	/**
614
	 * Given a list of conditions as per the format of $this->where, split
615
	 * this into an array of predicates, and a separate array of ordered parameters
616
	 *
617
	 * Note, that any SQLConditionGroup objects will be evaluated here.
618
	 * @see SQLConditionGroup
619
	 *
620
	 * @param array $conditions List of Conditions including parameters
621
	 * @param array $predicates Out parameter for the list of string predicates
622
	 * @param array $parameters Out parameter for the list of parameters
623
	 */
624
	public function splitQueryParameters($conditions, &$predicates, &$parameters) {
625
		// Merge all filters with paramaterised queries
626
		$predicates = array();
627
		$parameters = array();
628
		foreach($conditions as $condition) {
629
			// Evaluate the result of SQLConditionGroup here
630
			if($condition instanceof SQLConditionGroup) {
631
				$conditionSQL = $condition->conditionSQL($conditionParameters);
632
				if(!empty($conditionSQL)) {
633
					$predicates[] = $conditionSQL;
634
					$parameters = array_merge($parameters, $conditionParameters);
635
				}
636
			} else {
637
				foreach($condition as $key => $value) {
638
					$predicates[] = $key;
639
					$parameters = array_merge($parameters, $value);
640
				}
641
			}
642
		}
643
	}
644
645
	/**
646
	 * Checks whether this query is for a specific ID in a table
647
	 *
648
	 * @todo Doesn't work with combined statements (e.g. "Foo='bar' AND ID=5")
649
	 *
650
	 * @return boolean
651
	 */
652 View Code Duplication
	public function filtersOnID() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
653
		$regexp = '/^(.*\.)?("|`)?ID("|`)?\s?(=|IN)/';
654
655
		foreach($this->getWhereParameterised($parameters) as $predicate) {
656
			if(preg_match($regexp, $predicate)) return true;
657
		}
658
659
		return false;
660
	}
661
662
	/**
663
	 * Checks whether this query is filtering on a foreign key, ie finding a has_many relationship
664
	 *
665
	 * @todo Doesn't work with combined statements (e.g. "Foo='bar' AND ParentID=5")
666
	 *
667
	 * @return boolean
668
	 */
669 View Code Duplication
	public function filtersOnFK() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
670
		$regexp = '/^(.*\.)?("|`)?[a-zA-Z]+ID("|`)?\s?(=|IN)/';
671
672
		// @todo - Test this works with paramaterised queries
673
		foreach($this->getWhereParameterised($parameters) as $predicate) {
674
			if(preg_match($regexp, $predicate)) return true;
675
		}
676
677
		return false;
678
	}
679
680
	public function isEmpty() {
681
		return empty($this->from);
682
	}
683
684
	/**
685
	 * Generates an SQLDelete object using the currently specified parameters
686
	 *
687
	 * @return SQLDelete
688
	 */
689
	public function toDelete() {
690
		$delete = new SQLDelete();
691
		$this->copyTo($delete);
692
		return $delete;
693
	}
694
695
	/**
696
	 * Generates an SQLSelect object using the currently specified parameters.
697
	 *
698
	 * @return SQLSelect
699
	 */
700
	public function toSelect() {
701
		$select = new SQLSelect();
702
		$this->copyTo($select);
703
		return $select;
704
	}
705
706
	/**
707
	 * Generates an SQLUpdate object using the currently specified parameters.
708
	 * No fields will have any assigned values for the newly generated SQLUpdate
709
	 * object.
710
	 *
711
	 * @return SQLUpdate
712
	 */
713
	public function toUpdate() {
714
		$update = new SQLUpdate();
715
		$this->copyTo($update);
716
		return $update;
717
	}
718
}
719