DboSource::generateAssociationQuery()   F
last analyzed

Complexity

Conditions 34
Paths 1050

Size

Total Lines 153
Code Lines 115

Duplication

Lines 3
Ratio 1.96 %

Importance

Changes 0
Metric Value
cc 34
eloc 115
c 0
b 0
f 0
nc 1050
nop 8
dl 3
loc 153
rs 2

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * Dbo Source
4
 *
5
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
6
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
7
 *
8
 * Licensed under The MIT License
9
 * For full copyright and license information, please see the LICENSE.txt
10
 * Redistributions of files must retain the above copyright notice.
11
 *
12
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
13
 * @link          http://cakephp.org CakePHP(tm) Project
14
 * @package       Cake.Model.Datasource
15
 * @since         CakePHP(tm) v 0.10.0.1076
16
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
17
 */
18
19
App::uses('DataSource', 'Model/Datasource');
20
App::uses('String', 'Utility');
21
App::uses('View', 'View');
22
23
/**
24
 * DboSource
25
 *
26
 * Creates DBO-descendant objects from a given db connection configuration
27
 *
28
 * @package       Cake.Model.Datasource
29
 */
30
class DboSource extends DataSource {
31
32
/**
33
 * Description string for this Database Data Source.
34
 *
35
 * @var string
36
 */
37
	public $description = "Database Data Source";
38
39
/**
40
 * index definition, standard cake, primary, index, unique
41
 *
42
 * @var array
43
 */
44
	public $index = array('PRI' => 'primary', 'MUL' => 'index', 'UNI' => 'unique');
45
46
/**
47
 * Database keyword used to assign aliases to identifiers.
48
 *
49
 * @var string
50
 */
51
	public $alias = 'AS ';
52
53
/**
54
 * Caches result from query parsing operations. Cached results for both DboSource::name() and
55
 * DboSource::conditions() will be stored here. Method caching uses `md5()`. If you have
56
 * problems with collisions, set DboSource::$cacheMethods to false.
57
 *
58
 * @var array
59
 */
60
	public static $methodCache = array();
61
62
/**
63
 * Whether or not to cache the results of DboSource::name() and DboSource::conditions()
64
 * into the memory cache. Set to false to disable the use of the memory cache.
65
 *
66
 * @var boolean
67
 */
68
	public $cacheMethods = true;
69
70
/**
71
 * Flag to support nested transactions. If it is set to false, you will be able to use
72
 * the transaction methods (begin/commit/rollback), but just the global transaction will
73
 * be executed.
74
 *
75
 * @var boolean
76
 */
77
	public $useNestedTransactions = false;
78
79
/**
80
 * Print full query debug info?
81
 *
82
 * @var boolean
83
 */
84
	public $fullDebug = false;
85
86
/**
87
 * String to hold how many rows were affected by the last SQL operation.
88
 *
89
 * @var string
90
 */
91
	public $affected = null;
92
93
/**
94
 * Number of rows in current resultset
95
 *
96
 * @var integer
97
 */
98
	public $numRows = null;
99
100
/**
101
 * Time the last query took
102
 *
103
 * @var integer
104
 */
105
	public $took = null;
106
107
/**
108
 * Result
109
 *
110
 * @var array
111
 */
112
	protected $_result = null;
113
114
/**
115
 * Queries count.
116
 *
117
 * @var integer
118
 */
119
	protected $_queriesCnt = 0;
120
121
/**
122
 * Total duration of all queries.
123
 *
124
 * @var integer
125
 */
126
	protected $_queriesTime = null;
127
128
/**
129
 * Log of queries executed by this DataSource
130
 *
131
 * @var array
132
 */
133
	protected $_queriesLog = array();
134
135
/**
136
 * Maximum number of items in query log
137
 *
138
 * This is to prevent query log taking over too much memory.
139
 *
140
 * @var integer Maximum number of queries in the queries log.
141
 */
142
	protected $_queriesLogMax = 200;
143
144
/**
145
 * Caches serialized results of executed queries
146
 *
147
 * @var array Cache of results from executed sql queries.
148
 */
149
	protected $_queryCache = array();
150
151
/**
152
 * A reference to the physical connection of this DataSource
153
 *
154
 * @var array
155
 */
156
	protected $_connection = null;
157
158
/**
159
 * The DataSource configuration key name
160
 *
161
 * @var string
162
 */
163
	public $configKeyName = null;
164
165
/**
166
 * The starting character that this DataSource uses for quoted identifiers.
167
 *
168
 * @var string
169
 */
170
	public $startQuote = null;
171
172
/**
173
 * The ending character that this DataSource uses for quoted identifiers.
174
 *
175
 * @var string
176
 */
177
	public $endQuote = null;
178
179
/**
180
 * The set of valid SQL operations usable in a WHERE statement
181
 *
182
 * @var array
183
 */
184
	protected $_sqlOps = array('like', 'ilike', 'or', 'not', 'in', 'between', 'regexp', 'similar to');
185
186
/**
187
 * Indicates the level of nested transactions
188
 *
189
 * @var integer
190
 */
191
	protected $_transactionNesting = 0;
192
193
/**
194
 * Default fields that are used by the DBO
195
 *
196
 * @var array
197
 */
198
	protected $_queryDefaults = array(
199
		'conditions' => array(),
200
		'fields' => null,
201
		'table' => null,
202
		'alias' => null,
203
		'order' => null,
204
		'limit' => null,
205
		'joins' => array(),
206
		'group' => null,
207
		'offset' => null
208
	);
209
210
/**
211
 * Separator string for virtualField composition
212
 *
213
 * @var string
214
 */
215
	public $virtualFieldSeparator = '__';
216
217
/**
218
 * List of table engine specific parameters used on table creating
219
 *
220
 * @var array
221
 */
222
	public $tableParameters = array();
223
224
/**
225
 * List of engine specific additional field parameters used on table creating
226
 *
227
 * @var array
228
 */
229
	public $fieldParameters = array();
230
231
/**
232
 * Indicates whether there was a change on the cached results on the methods of this class
233
 * This will be used for storing in a more persistent cache
234
 *
235
 * @var boolean
236
 */
237
	protected $_methodCacheChange = false;
238
239
/**
240
 * Constructor
241
 *
242
 * @param array $config Array of configuration information for the Datasource.
243
 * @param boolean $autoConnect Whether or not the datasource should automatically connect.
244
 * @throws MissingConnectionException when a connection cannot be made.
245
 */
246
	public function __construct($config = null, $autoConnect = true) {
247
		if (!isset($config['prefix'])) {
248
			$config['prefix'] = '';
249
		}
250
		parent::__construct($config);
251
		$this->fullDebug = Configure::read('debug') > 1;
252
		if (!$this->enabled()) {
253
			throw new MissingConnectionException(array(
254
				'class' => get_class($this),
255
				'message' => __d('cake_dev', 'Selected driver is not enabled'),
256
				'enabled' => false
257
			));
258
		}
259
		if ($autoConnect) {
260
			$this->connect();
0 ignored issues
show
Bug introduced by
The method connect() does not exist on DboSource. Did you maybe mean reconnect()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
261
		}
262
	}
263
264
/**
265
 * Reconnects to database server with optional new settings
266
 *
267
 * @param array $config An array defining the new configuration settings
268
 * @return boolean True on success, false on failure
269
 */
270
	public function reconnect($config = array()) {
271
		$this->disconnect();
272
		$this->setConfig($config);
273
		$this->_sources = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $_sources.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
274
275
		return $this->connect();
0 ignored issues
show
Bug introduced by
The method connect() does not exist on DboSource. Did you maybe mean reconnect()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
276
	}
277
278
/**
279
 * Disconnects from database.
280
 *
281
 * @return boolean Always true
282
 */
283
	public function disconnect() {
284
		if ($this->_result instanceof PDOStatement) {
285
			$this->_result->closeCursor();
286
		}
287
		unset($this->_connection);
288
		$this->connected = false;
289
		return true;
290
	}
291
292
/**
293
 * Get the underlying connection object.
294
 *
295
 * @return PDO
296
 */
297
	public function getConnection() {
298
		return $this->_connection;
299
	}
300
301
/**
302
 * Gets the version string of the database server
303
 *
304
 * @return string The database version
305
 */
306
	public function getVersion() {
307
		return $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
0 ignored issues
show
Bug introduced by
The method getAttribute cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
308
	}
309
310
/**
311
 * Returns a quoted and escaped string of $data for use in an SQL statement.
312
 *
313
 * @param string $data String to be prepared for use in an SQL statement
314
 * @param string $column The column datatype into which this data will be inserted.
315
 * @return string Quoted and escaped data
316
 */
317
	public function value($data, $column = null) {
318
		if (is_array($data) && !empty($data)) {
319
			return array_map(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array_map(array(&...ount($data), $column)); (array) is incompatible with the return type documented by DboSource::value of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
320
				array(&$this, 'value'),
321
				$data, array_fill(0, count($data), $column)
322
			);
323
		} elseif (is_object($data) && isset($data->type, $data->value)) {
324
			if ($data->type === 'identifier') {
325
				return $this->name($data->value);
326
			} elseif ($data->type === 'expression') {
327
				return $data->value;
328
			}
329
		} elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {
330
			return $data;
0 ignored issues
show
Bug Compatibility introduced by
The expression return $data; of type string|array is incompatible with the return type documented by DboSource::value of type string as it can also be of type array which is not included in this return type.
Loading history...
331
		}
332
333
		if ($data === null || (is_array($data) && empty($data))) {
334
			return 'NULL';
335
		}
336
337
		if (empty($column)) {
338
			$column = $this->introspectType($data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type array; however, DboSource::introspectType() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
339
		}
340
341
		switch ($column) {
342
			case 'binary':
343
				return $this->_connection->quote($data, PDO::PARAM_LOB);
0 ignored issues
show
Bug introduced by
The method quote cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
344
			case 'boolean':
345
				return $this->_connection->quote($this->boolean($data, true), PDO::PARAM_BOOL);
0 ignored issues
show
Bug introduced by
The method quote cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
346
			case 'string':
347
			case 'text':
348
				return $this->_connection->quote($data, PDO::PARAM_STR);
0 ignored issues
show
Bug introduced by
The method quote cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
349
			default:
350
				if ($data === '') {
351
					return 'NULL';
352
				}
353
				if (is_float($data)) {
354
					return str_replace(',', '.', strval($data));
355
				}
356
				if ((is_int($data) || $data === '0') || (
357
					is_numeric($data) && strpos($data, ',') === false &&
358
					$data[0] != '0' && strpos($data, 'e') === false)
359
				) {
360
					return $data;
361
				}
362
				return $this->_connection->quote($data);
0 ignored issues
show
Bug introduced by
The method quote cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
363
		}
364
	}
365
366
/**
367
 * Returns an object to represent a database identifier in a query. Expression objects
368
 * are not sanitized or escaped.
369
 *
370
 * @param string $identifier A SQL expression to be used as an identifier
371
 * @return stdClass An object representing a database identifier to be used in a query
372
 */
373
	public function identifier($identifier) {
374
		$obj = new stdClass();
375
		$obj->type = 'identifier';
376
		$obj->value = $identifier;
377
		return $obj;
378
	}
379
380
/**
381
 * Returns an object to represent a database expression in a query. Expression objects
382
 * are not sanitized or escaped.
383
 *
384
 * @param string $expression An arbitrary SQL expression to be inserted into a query.
385
 * @return stdClass An object representing a database expression to be used in a query
386
 */
387
	public function expression($expression) {
388
		$obj = new stdClass();
389
		$obj->type = 'expression';
390
		$obj->value = $expression;
391
		return $obj;
392
	}
393
394
/**
395
 * Executes given SQL statement.
396
 *
397
 * @param string $sql SQL statement
398
 * @param array $params Additional options for the query.
399
 * @return boolean
400
 */
401
	public function rawQuery($sql, $params = array()) {
402
		$this->took = $this->numRows = false;
0 ignored issues
show
Documentation Bug introduced by
The property $numRows was declared of type integer, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
Documentation Bug introduced by
The property $took was declared of type integer, but $this->numRows = false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
403
		return $this->execute($sql, $params);
404
	}
405
406
/**
407
 * Queries the database with given SQL statement, and obtains some metadata about the result
408
 * (rows affected, timing, any errors, number of rows in resultset). The query is also logged.
409
 * If Configure::read('debug') is set, the log is shown all the time, else it is only shown on errors.
410
 *
411
 * ### Options
412
 *
413
 * - log - Whether or not the query should be logged to the memory log.
414
 *
415
 * @param string $sql SQL statement
416
 * @param array $options
417
 * @param array $params values to be bound to the query
418
 * @return mixed Resource or object representing the result set, or false on failure
419
 */
420
	public function execute($sql, $options = array(), $params = array()) {
421
		$options += array('log' => $this->fullDebug);
422
423
		$t = microtime(true);
424
		$this->_result = $this->_execute($sql, $params);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->_execute($sql, $params) of type * is incompatible with the declared type array of property $_result.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
425
426
		if ($options['log']) {
427
			$this->took = round((microtime(true) - $t) * 1000, 0);
0 ignored issues
show
Documentation Bug introduced by
The property $took was declared of type integer, but round((microtime(true) - $t) * 1000, 0) is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
428
			$this->numRows = $this->affected = $this->lastAffected();
0 ignored issues
show
Documentation Bug introduced by
The property $affected was declared of type string, but $this->lastAffected() is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
429
			$this->logQuery($sql, $params);
430
		}
431
432
		return $this->_result;
433
	}
434
435
/**
436
 * Executes given SQL statement.
437
 *
438
 * @param string $sql SQL statement
439
 * @param array $params list of params to be bound to query
440
 * @param array $prepareOptions Options to be used in the prepare statement
441
 * @return mixed PDOStatement if query executes with no problem, true as the result of a successful, false on error
442
 * query returning no rows, such as a CREATE statement, false otherwise
443
 * @throws PDOException
444
 */
445
	protected function _execute($sql, $params = array(), $prepareOptions = array()) {
446
		$sql = trim($sql);
447
		if (preg_match('/^(?:CREATE|ALTER|DROP)\s+(?:TABLE|INDEX)/i', $sql)) {
448
			$statements = array_filter(explode(';', $sql));
449
			if (count($statements) > 1) {
450
				$result = array_map(array($this, '_execute'), $statements);
451
				return array_search(false, $result) === false;
452
			}
453
		}
454
455
		try {
456
			$query = $this->_connection->prepare($sql, $prepareOptions);
0 ignored issues
show
Bug introduced by
The method prepare cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
457
			$query->setFetchMode(PDO::FETCH_LAZY);
458
			if (!$query->execute($params)) {
459
				$this->_results = $query;
460
				$query->closeCursor();
461
				return false;
462
			}
463
			if (!$query->columnCount()) {
464
				$query->closeCursor();
465
				if (!$query->rowCount()) {
466
					return true;
467
				}
468
			}
469
			return $query;
470
		} catch (PDOException $e) {
471
			if (isset($query->queryString)) {
472
				$e->queryString = $query->queryString;
473
			} else {
474
				$e->queryString = $sql;
475
			}
476
			throw $e;
477
		}
478
	}
479
480
/**
481
 * Returns a formatted error message from previous database operation.
482
 *
483
 * @param PDOStatement $query the query to extract the error from if any
484
 * @return string Error message with error number
485
 */
486
	public function lastError(PDOStatement $query = null) {
487
		if ($query) {
488
			$error = $query->errorInfo();
489
		} else {
490
			$error = $this->_connection->errorInfo();
0 ignored issues
show
Bug introduced by
The method errorInfo cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
491
		}
492
		if (empty($error[2])) {
493
			return null;
494
		}
495
		return $error[1] . ': ' . $error[2];
496
	}
497
498
/**
499
 * Returns number of affected rows in previous database operation. If no previous operation exists,
500
 * this returns false.
501
 *
502
 * @param mixed $source
503
 * @return integer Number of affected rows
504
 */
505
	public function lastAffected($source = null) {
506
		if ($this->hasResult()) {
507
			return $this->_result->rowCount();
0 ignored issues
show
Bug introduced by
The method rowCount cannot be called on $this->_result (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
508
		}
509
		return 0;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return 0; (integer) is incompatible with the return type of the parent method DataSource::lastAffected of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
510
	}
511
512
/**
513
 * Returns number of rows in previous resultset. If no previous resultset exists,
514
 * this returns false.
515
 *
516
 * @param mixed $source Not used
517
 * @return integer Number of rows in resultset
518
 */
519
	public function lastNumRows($source = null) {
520
		return $this->lastAffected();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->lastAffected(); (integer) is incompatible with the return type of the parent method DataSource::lastNumRows of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
521
	}
522
523
/**
524
 * DataSource Query abstraction
525
 *
526
 * @return resource Result resource identifier.
527
 */
528
	public function query() {
529
		$args = func_get_args();
530
		$fields = null;
531
		$order = null;
532
		$limit = null;
533
		$page = null;
534
		$recursive = null;
535
536
		if (count($args) === 1) {
537
			return $this->fetchAll($args[0]);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->fetchAll($args[0]); (boolean|array) is incompatible with the return type documented by DboSource::query of type resource.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
538
		} elseif (count($args) > 1 && (strpos($args[0], 'findBy') === 0 || strpos($args[0], 'findAllBy') === 0)) {
539
			$params = $args[1];
540
541
			if (substr($args[0], 0, 6) === 'findBy') {
542
				$all = false;
543
				$field = Inflector::underscore(substr($args[0], 6));
544
			} else {
545
				$all = true;
546
				$field = Inflector::underscore(substr($args[0], 9));
547
			}
548
549
			$or = (strpos($field, '_or_') !== false);
550
			if ($or) {
551
				$field = explode('_or_', $field);
552
			} else {
553
				$field = explode('_and_', $field);
554
			}
555
			$off = count($field) - 1;
556
557
			if (isset($params[1 + $off])) {
558
				$fields = $params[1 + $off];
559
			}
560
561
			if (isset($params[2 + $off])) {
562
				$order = $params[2 + $off];
563
			}
564
565
			if (!array_key_exists(0, $params)) {
566
				return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by DboSource::query of type resource.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
567
			}
568
569
			$c = 0;
570
			$conditions = array();
571
572
			foreach ($field as $f) {
573
				$conditions[$args[2]->alias . '.' . $f] = $params[$c++];
574
			}
575
576
			if ($or) {
577
				$conditions = array('OR' => $conditions);
578
			}
579
580
			if ($all) {
581
				if (isset($params[3 + $off])) {
582
					$limit = $params[3 + $off];
583
				}
584
585
				if (isset($params[4 + $off])) {
586
					$page = $params[4 + $off];
587
				}
588
589
				if (isset($params[5 + $off])) {
590
					$recursive = $params[5 + $off];
591
				}
592
				return $args[2]->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'));
593
			}
594
			if (isset($params[3 + $off])) {
595
				$recursive = $params[3 + $off];
596
			}
597
			return $args[2]->find('first', compact('conditions', 'fields', 'order', 'recursive'));
598
		}
599
		if (isset($args[1]) && $args[1] === true) {
600
			return $this->fetchAll($args[0], true);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->fetchAll($args[0], true); (boolean|array) is incompatible with the return type documented by DboSource::query of type resource.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
601
		} elseif (isset($args[1]) && !is_array($args[1])) {
602
			return $this->fetchAll($args[0], false);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->fetchAll($args[0], false); (boolean|array) is incompatible with the return type documented by DboSource::query of type resource.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
603
		} elseif (isset($args[1]) && is_array($args[1])) {
604
			if (isset($args[2])) {
605
				$cache = $args[2];
606
			} else {
607
				$cache = true;
608
			}
609
			return $this->fetchAll($args[0], $args[1], array('cache' => $cache));
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->fetchAll($...ay('cache' => $cache)); (boolean|array) is incompatible with the return type documented by DboSource::query of type resource.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
610
		}
611
	}
612
613
/**
614
 * Returns a row from current resultset as an array
615
 *
616
 * @param string $sql Some SQL to be executed.
617
 * @return array The fetched row as an array
618
 */
619
	public function fetchRow($sql = null) {
620
		if (is_string($sql) && strlen($sql) > 5 && !$this->execute($sql)) {
621
			return null;
622
		}
623
624
		if ($this->hasResult()) {
625
			$this->resultSet($this->_result);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class DboSource as the method resultSet() does only exist in the following sub-classes of DboSource: DboPostgresTestDb, DboSqliteTestDb, Mysql, Postgres, Sqlite, Sqlserver, SqlserverTestDb. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
626
			$resultRow = $this->fetchResult();
627
			if (isset($resultRow[0])) {
628
				$this->fetchVirtualField($resultRow);
629
			}
630
			return $resultRow;
631
		}
632
		return null;
633
	}
634
635
/**
636
 * Returns an array of all result rows for a given SQL query.
637
 * Returns false if no rows matched.
638
 *
639
 * ### Options
640
 *
641
 * - `cache` - Returns the cached version of the query, if exists and stores the result in cache.
642
 *   This is a non-persistent cache, and only lasts for a single request. This option
643
 *   defaults to true. If you are directly calling this method, you can disable caching
644
 *   by setting $options to `false`
645
 *
646
 * @param string $sql SQL statement
647
 * @param array|boolean $params Either parameters to be bound as values for the SQL statement,
648
 *  or a boolean to control query caching.
649
 * @param array $options additional options for the query.
650
 * @return boolean|array Array of resultset rows, or false if no rows matched
651
 */
652
	public function fetchAll($sql, $params = array(), $options = array()) {
653
		if (is_string($options)) {
654
			$options = array('modelName' => $options);
655
		}
656
		if (is_bool($params)) {
657
			$options['cache'] = $params;
658
			$params = array();
659
		}
660
		$options += array('cache' => true);
661
		$cache = $options['cache'];
662
		if ($cache && ($cached = $this->getQueryCache($sql, $params)) !== false) {
663
			return $cached;
664
		}
665
		$result = $this->execute($sql, array(), $params);
666
		if ($result) {
667
			$out = array();
668
669
			if ($this->hasResult()) {
670
				$first = $this->fetchRow();
671
				if ($first) {
672
					$out[] = $first;
673
				}
674
				while ($item = $this->fetchResult()) {
675
					if (isset($item[0])) {
676
						$this->fetchVirtualField($item);
677
					}
678
					$out[] = $item;
679
				}
680
			}
681
682
			if (!is_bool($result) && $cache) {
683
				$this->_writeQueryCache($sql, $out, $params);
684
			}
685
686
			if (empty($out) && is_bool($this->_result)) {
687
				return $this->_result;
688
			}
689
			return $out;
690
		}
691
		return false;
692
	}
693
694
/**
695
 * Fetches the next row from the current result set
696
 *
697
 * @return boolean
698
 */
699
	public function fetchResult() {
700
		return false;
701
	}
702
703
/**
704
 * Modifies $result array to place virtual fields in model entry where they belongs to
705
 *
706
 * @param array $result Reference to the fetched row
707
 * @return void
708
 */
709
	public function fetchVirtualField(&$result) {
710
		if (isset($result[0]) && is_array($result[0])) {
711
			foreach ($result[0] as $field => $value) {
712
				if (strpos($field, $this->virtualFieldSeparator) === false) {
713
					continue;
714
				}
715
				list($alias, $virtual) = explode($this->virtualFieldSeparator, $field);
716
717
				if (!ClassRegistry::isKeySet($alias)) {
718
					return;
719
				}
720
				$model = ClassRegistry::getObject($alias);
721
				if ($model->isVirtualField($virtual)) {
722
					$result[$alias][$virtual] = $value;
723
					unset($result[0][$field]);
724
				}
725
			}
726
			if (empty($result[0])) {
727
				unset($result[0]);
728
			}
729
		}
730
	}
731
732
/**
733
 * Returns a single field of the first of query results for a given SQL query, or false if empty.
734
 *
735
 * @param string $name Name of the field
736
 * @param string $sql SQL query
737
 * @return mixed Value of field read.
738
 */
739
	public function field($name, $sql) {
740
		$data = $this->fetchRow($sql);
741
		if (empty($data[$name])) {
742
			return false;
743
		}
744
		return $data[$name];
745
	}
746
747
/**
748
 * Empties the method caches.
749
 * These caches are used by DboSource::name() and DboSource::conditions()
750
 *
751
 * @return void
752
 */
753
	public function flushMethodCache() {
754
		$this->_methodCacheChange = true;
755
		self::$methodCache = array();
756
	}
757
758
/**
759
 * Cache a value into the methodCaches. Will respect the value of DboSource::$cacheMethods.
760
 * Will retrieve a value from the cache if $value is null.
761
 *
762
 * If caching is disabled and a write is attempted, the $value will be returned.
763
 * A read will either return the value or null.
764
 *
765
 * @param string $method Name of the method being cached.
766
 * @param string $key The key name for the cache operation.
767
 * @param mixed $value The value to cache into memory.
768
 * @return mixed Either null on failure, or the value if its set.
769
 */
770
	public function cacheMethod($method, $key, $value = null) {
771
		if ($this->cacheMethods === false) {
772
			return $value;
773
		}
774
		if (!$this->_methodCacheChange && empty(self::$methodCache)) {
775
			self::$methodCache = Cache::read('method_cache', '_cake_core_');
0 ignored issues
show
Documentation Bug introduced by
It seems like \Cache::read('method_cache', '_cake_core_') of type * is incompatible with the declared type array of property $methodCache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
776
		}
777
		if ($value === null) {
778
			return (isset(self::$methodCache[$method][$key])) ? self::$methodCache[$method][$key] : null;
779
		}
780
		$this->_methodCacheChange = true;
781
		return self::$methodCache[$method][$key] = $value;
782
	}
783
784
/**
785
 * Returns a quoted name of $data for use in an SQL statement.
786
 * Strips fields out of SQL functions before quoting.
787
 *
788
 * Results of this method are stored in a memory cache. This improves performance, but
789
 * because the method uses a hashing algorithm it can have collisions.
790
 * Setting DboSource::$cacheMethods to false will disable the memory cache.
791
 *
792
 * @param mixed $data Either a string with a column to quote. An array of columns to quote or an
793
 *   object from DboSource::expression() or DboSource::identifier()
794
 * @return string SQL field
795
 */
796
	public function name($data) {
797
		if (is_object($data) && isset($data->type)) {
798
			return $data->value;
799
		}
800
		if ($data === '*') {
801
			return '*';
802
		}
803
		if (is_array($data)) {
804
			foreach ($data as $i => $dataItem) {
805
				$data[$i] = $this->name($dataItem);
806
			}
807
			return $data;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $data; (array) is incompatible with the return type documented by DboSource::name of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
808
		}
809
		$cacheKey = md5($this->startQuote . $data . $this->endQuote);
810
		if ($return = $this->cacheMethod(__FUNCTION__, $cacheKey)) {
811
			return $return;
812
		}
813
		$data = trim($data);
814
		if (preg_match('/^[\w-]+(?:\.[^ \*]*)*$/', $data)) { // string, string.string
815
			if (strpos($data, '.') === false) { // string
816
				return $this->cacheMethod(__FUNCTION__, $cacheKey, $this->startQuote . $data . $this->endQuote);
817
			}
818
			$items = explode('.', $data);
819
			return $this->cacheMethod(__FUNCTION__, $cacheKey,
820
				$this->startQuote . implode($this->endQuote . '.' . $this->startQuote, $items) . $this->endQuote
821
			);
822
		}
823 View Code Duplication
		if (preg_match('/^[\w-]+\.\*$/', $data)) { // string.*
824
			return $this->cacheMethod(__FUNCTION__, $cacheKey,
825
				$this->startQuote . str_replace('.*', $this->endQuote . '.*', $data)
826
			);
827
		}
828
		if (preg_match('/^([\w-]+)\((.*)\)$/', $data, $matches)) { // Functions
829
			return $this->cacheMethod(__FUNCTION__, $cacheKey,
830
				$matches[1] . '(' . $this->name($matches[2]) . ')'
831
			);
832
		}
833
		if (
834
			preg_match('/^([\w-]+(\.[\w-]+|\(.*\))*)\s+' . preg_quote($this->alias) . '\s*([\w-]+)$/i', $data, $matches
835
		)) {
836
			return $this->cacheMethod(
837
				__FUNCTION__, $cacheKey,
838
				preg_replace(
839
					'/\s{2,}/', ' ', $this->name($matches[1]) . ' ' . $this->alias . ' ' . $this->name($matches[3])
840
				)
841
			);
842
		}
843 View Code Duplication
		if (preg_match('/^[\w-_\s]*[\w-_]+/', $data)) {
844
			return $this->cacheMethod(__FUNCTION__, $cacheKey, $this->startQuote . $data . $this->endQuote);
845
		}
846
		return $this->cacheMethod(__FUNCTION__, $cacheKey, $data);
847
	}
848
849
/**
850
 * Checks if the source is connected to the database.
851
 *
852
 * @return boolean True if the database is connected, else false
853
 */
854
	public function isConnected() {
855
		return $this->connected;
856
	}
857
858
/**
859
 * Checks if the result is valid
860
 *
861
 * @return boolean True if the result is valid else false
862
 */
863
	public function hasResult() {
864
		return $this->_result instanceof PDOStatement;
865
	}
866
867
/**
868
 * Get the query log as an array.
869
 *
870
 * @param boolean $sorted Get the queries sorted by time taken, defaults to false.
871
 * @param boolean $clear If True the existing log will cleared.
872
 * @return array Array of queries run as an array
873
 */
874
	public function getLog($sorted = false, $clear = true) {
875
		if ($sorted) {
876
			$log = sortByKey($this->_queriesLog, 'took', 'desc', SORT_NUMERIC);
877
		} else {
878
			$log = $this->_queriesLog;
879
		}
880
		if ($clear) {
881
			$this->_queriesLog = array();
882
		}
883
		return array('log' => $log, 'count' => $this->_queriesCnt, 'time' => $this->_queriesTime);
884
	}
885
886
/**
887
 * Outputs the contents of the queries log. If in a non-CLI environment the sql_log element
888
 * will be rendered and output. If in a CLI environment, a plain text log is generated.
889
 *
890
 * @param boolean $sorted Get the queries sorted by time taken, defaults to false.
891
 * @return void
892
 */
893
	public function showLog($sorted = false) {
894
		$log = $this->getLog($sorted, false);
895
		if (empty($log['log'])) {
896
			return;
897
		}
898
		if (PHP_SAPI !== 'cli') {
899
			$controller = null;
900
			$View = new View($controller, false);
901
			$View->set('sqlLogs', array($this->configKeyName => $log));
902
			echo $View->element('sql_dump', array('_forced_from_dbo_' => true));
0 ignored issues
show
Deprecated Code introduced by
The method View::element() has been deprecated with message: The `$options['plugin']` is deprecated and will be removed in CakePHP 3.0. Use `Plugin.element_name` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
903
		} else {
904
			foreach ($log['log'] as $k => $i) {
905
				print (($k + 1) . ". {$i['query']}\n");
906
			}
907
		}
908
	}
909
910
/**
911
 * Log given SQL query.
912
 *
913
 * @param string $sql SQL statement
914
 * @param array $params Values binded to the query (prepared statements)
915
 * @return void
916
 */
917
	public function logQuery($sql, $params = array()) {
918
		$this->_queriesCnt++;
919
		$this->_queriesTime += $this->took;
920
		$this->_queriesLog[] = array(
921
			'query' => $sql,
922
			'params' => $params,
923
			'affected' => $this->affected,
924
			'numRows' => $this->numRows,
925
			'took' => $this->took
926
		);
927
		if (count($this->_queriesLog) > $this->_queriesLogMax) {
928
			array_shift($this->_queriesLog);
929
		}
930
	}
931
932
/**
933
 * Gets full table name including prefix
934
 *
935
 * @param Model|string $model Either a Model object or a string table name.
936
 * @param boolean $quote Whether you want the table name quoted.
937
 * @param boolean $schema Whether you want the schema name included.
938
 * @return string Full quoted table name
939
 */
940
	public function fullTableName($model, $quote = true, $schema = true) {
941
		if (is_object($model)) {
942
			$schemaName = $model->schemaName;
943
			$table = $model->tablePrefix . $model->table;
944
		} elseif (!empty($this->config['prefix']) && strpos($model, $this->config['prefix']) !== 0) {
945
			$table = $this->config['prefix'] . strval($model);
946
		} else {
947
			$table = strval($model);
948
		}
949
		if ($schema && !isset($schemaName)) {
950
			$schemaName = $this->getSchemaName();
951
		}
952
953
		if ($quote) {
954 View Code Duplication
			if ($schema && !empty($schemaName)) {
955
				if (strstr($table, '.') === false) {
956
					return $this->name($schemaName) . '.' . $this->name($table);
957
				}
958
			}
959
			return $this->name($table);
960
		}
961 View Code Duplication
		if ($schema && !empty($schemaName)) {
962
			if (strstr($table, '.') === false) {
963
				return $schemaName . '.' . $table;
964
			}
965
		}
966
		return $table;
967
	}
968
969
/**
970
 * The "C" in CRUD
971
 *
972
 * Creates new records in the database.
973
 *
974
 * @param Model $model Model object that the record is for.
975
 * @param array $fields An array of field names to insert. If null, $model->data will be
976
 *   used to generate field names.
977
 * @param array $values An array of values with keys matching the fields. If null, $model->data will
978
 *   be used to generate values.
979
 * @return boolean Success
980
 */
981
	public function create(Model $model, $fields = null, $values = null) {
982
		$id = null;
983
984
		if (!$fields) {
985
			unset($fields, $values);
986
			$fields = array_keys($model->data);
987
			$values = array_values($model->data);
988
		}
989
		$count = count($fields);
990
991
		for ($i = 0; $i < $count; $i++) {
992
			$valueInsert[] = $this->value($values[$i], $model->getColumnType($fields[$i]));
0 ignored issues
show
Coding Style Comprehensibility introduced by
$valueInsert was never initialized. Although not strictly required by PHP, it is generally a good practice to add $valueInsert = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
993
			$fieldInsert[] = $this->name($fields[$i]);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$fieldInsert was never initialized. Although not strictly required by PHP, it is generally a good practice to add $fieldInsert = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
994
			if ($fields[$i] === $model->primaryKey) {
995
				$id = $values[$i];
996
			}
997
		}
998
		$query = array(
999
			'table' => $this->fullTableName($model),
1000
			'fields' => implode(', ', $fieldInsert),
0 ignored issues
show
Bug introduced by
The variable $fieldInsert does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1001
			'values' => implode(', ', $valueInsert)
0 ignored issues
show
Bug introduced by
The variable $valueInsert does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1002
		);
1003
1004
		if ($this->execute($this->renderStatement('create', $query))) {
1005
			if (empty($id)) {
1006
				$id = $this->lastInsertId($this->fullTableName($model, false, false), $model->primaryKey);
1007
			}
1008
			$model->setInsertID($id);
1009
			$model->id = $id;
1010
			return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type of the parent method DataSource::create of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1011
		}
1012
		$model->onError();
1013
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method DataSource::create of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1014
	}
1015
1016
/**
1017
 * The "R" in CRUD
1018
 *
1019
 * Reads record(s) from the database.
1020
 *
1021
 * @param Model $model A Model object that the query is for.
1022
 * @param array $queryData An array of queryData information containing keys similar to Model::find()
1023
 * @param integer $recursive Number of levels of association
1024
 * @return mixed boolean false on error/failure. An array of results on success.
1025
 */
1026
	public function read(Model $model, $queryData = array(), $recursive = null) {
1027
		$queryData = $this->_scrubQueryData($queryData);
1028
1029
		$null = null;
1030
		$array = array('callbacks' => $queryData['callbacks']);
1031
		$linkedModels = array();
1032
		$bypass = false;
1033
1034
		if ($recursive === null && isset($queryData['recursive'])) {
1035
			$recursive = $queryData['recursive'];
1036
		}
1037
1038
		if ($recursive !== null) {
1039
			$_recursive = $model->recursive;
1040
			$model->recursive = $recursive;
1041
		}
1042
1043
		if (!empty($queryData['fields'])) {
1044
			$bypass = true;
1045
			$queryData['fields'] = $this->fields($model, null, $queryData['fields']);
1046
		} else {
1047
			$queryData['fields'] = $this->fields($model);
1048
		}
1049
1050
		$_associations = $model->associations();
1051
1052
		if ($model->recursive == -1) {
1053
			$_associations = array();
1054
		} elseif ($model->recursive === 0) {
1055
			unset($_associations[2], $_associations[3]);
1056
		}
1057
1058
		foreach ($_associations as $type) {
1059
			foreach ($model->{$type} as $assoc => $assocData) {
1060
				$linkModel = $model->{$assoc};
1061
				$external = isset($assocData['external']);
1062
1063
				$linkModel->getDataSource();
1064
				if ($model->useDbConfig === $linkModel->useDbConfig) {
1065
					if ($bypass) {
1066
						$assocData['fields'] = false;
1067
					}
1068
					if ($this->generateAssociationQuery($model, $linkModel, $type, $assoc, $assocData, $queryData, $external, $null) === true) {
0 ignored issues
show
Bug introduced by
It seems like $null defined by null on line 1029 can also be of type null; however, DboSource::generateAssociationQuery() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1069
						$linkedModels[$type . '/' . $assoc] = true;
1070
					}
1071
				}
1072
			}
1073
		}
1074
1075
		$query = $this->generateAssociationQuery($model, null, null, null, null, $queryData, false, $null);
0 ignored issues
show
Bug introduced by
It seems like $null defined by null on line 1029 can also be of type null; however, DboSource::generateAssociationQuery() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1076
1077
		$resultSet = $this->fetchAll($query, $model->cacheQueries);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->fetchAll($query, $model->cacheQueries); of type boolean|array adds the type boolean to the return on line 1129 which is incompatible with the return type of the parent method DataSource::read of type array.
Loading history...
1078
1079
		if ($resultSet === false) {
1080
			$model->onError();
1081
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method DataSource::read of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1082
		}
1083
1084
		$filtered = array();
1085
1086 View Code Duplication
		if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
1087
			$filtered = $this->_filterResults($resultSet, $model);
0 ignored issues
show
Bug introduced by
It seems like $resultSet defined by $this->fetchAll($query, $model->cacheQueries) on line 1077 can also be of type boolean; however, DboSource::_filterResults() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1088
		}
1089
1090
		if ($model->recursive > -1) {
1091
			$joined = array();
1092
			if (isset($queryData['joins'][0]['alias'])) {
1093
				$joined[$model->alias] = (array)Hash::extract($queryData['joins'], '{n}.alias');
1094
			}
1095
			foreach ($_associations as $type) {
1096
				foreach ($model->{$type} as $assoc => $assocData) {
1097
					$linkModel = $model->{$assoc};
1098
1099
					if (!isset($linkedModels[$type . '/' . $assoc])) {
1100 View Code Duplication
						if ($model->useDbConfig === $linkModel->useDbConfig) {
1101
							$db = $this;
1102
						} else {
1103
							$db = ConnectionManager::getDataSource($linkModel->useDbConfig);
1104
						}
1105
					} elseif ($model->recursive > 1 && ($type === 'belongsTo' || $type === 'hasOne')) {
1106
						$db = $this;
1107
					}
1108
1109
					if (isset($db) && method_exists($db, 'queryAssociation')) {
1110
						$stack = array($assoc);
1111
						$stack['_joined'] = $joined;
1112
						$db->queryAssociation($model, $linkModel, $type, $assoc, $assocData, $array, true, $resultSet, $model->recursive - 1, $stack);
1113
						unset($db);
1114
1115
						if ($type === 'hasMany') {
1116
							$filtered[] = $assoc;
1117
						}
1118
					}
1119
				}
1120
			}
1121 View Code Duplication
			if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
1122
				$this->_filterResults($resultSet, $model, $filtered);
0 ignored issues
show
Bug introduced by
It seems like $resultSet defined by $this->fetchAll($query, $model->cacheQueries) on line 1077 can also be of type boolean; however, DboSource::_filterResults() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1123
			}
1124
		}
1125
1126
		if ($recursive !== null) {
1127
			$model->recursive = $_recursive;
0 ignored issues
show
Bug introduced by
The variable $_recursive does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1128
		}
1129
		return $resultSet;
1130
	}
1131
1132
/**
1133
 * Passes association results thru afterFind filters of corresponding model
1134
 *
1135
 * @param array $results Reference of resultset to be filtered
1136
 * @param Model $model Instance of model to operate against
1137
 * @param array $filtered List of classes already filtered, to be skipped
1138
 * @return array Array of results that have been filtered through $model->afterFind
1139
 */
1140
	protected function _filterResults(&$results, Model $model, $filtered = array()) {
1141
		$current = reset($results);
1142
		if (!is_array($current)) {
1143
			return array();
1144
		}
1145
		$keys = array_diff(array_keys($current), $filtered, array($model->alias));
1146
		$filtering = array();
1147
		foreach ($keys as $className) {
1148
			if (!isset($model->{$className}) || !is_object($model->{$className})) {
1149
				continue;
1150
			}
1151
			$linkedModel = $model->{$className};
1152
			$filtering[] = $className;
1153
			foreach ($results as $key => &$result) {
1154
				$data = $linkedModel->afterFind(array(array($className => $result[$className])), false);
1155
				if (isset($data[0][$className])) {
1156
					$result[$className] = $data[0][$className];
1157
				} else {
1158
					unset($results[$key]);
1159
				}
1160
			}
1161
		}
1162
		return $filtering;
1163
	}
1164
1165
/**
1166
 * Queries associations. Used to fetch results on recursive models.
1167
 *
1168
 * @param Model $model Primary Model object
1169
 * @param Model $linkModel Linked model that
1170
 * @param string $type Association type, one of the model association types ie. hasMany
1171
 * @param string $association
1172
 * @param array $assocData
1173
 * @param array $queryData
1174
 * @param boolean $external Whether or not the association query is on an external datasource.
1175
 * @param array $resultSet Existing results
1176
 * @param integer $recursive Number of levels of association
1177
 * @param array $stack
1178
 * @return mixed
1179
 * @throws CakeException when results cannot be created.
1180
 */
1181
	public function queryAssociation(Model $model, &$linkModel, $type, $association, $assocData, &$queryData, $external, &$resultSet, $recursive, $stack) {
1182
		if (isset($stack['_joined'])) {
1183
			$joined = $stack['_joined'];
1184
			unset($stack['_joined']);
1185
		}
1186
1187
		if ($query = $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet)) {
1188
			if (!is_array($resultSet)) {
1189
				throw new CakeException(__d('cake_dev', 'Error in Model %s', get_class($model)));
1190
			}
1191
			if ($type === 'hasMany' && empty($assocData['limit']) && !empty($assocData['foreignKey'])) {
1192
				$ins = $fetch = array();
1193 View Code Duplication
				foreach ($resultSet as &$result) {
1194
					if ($in = $this->insertQueryData('{$__cakeID__$}', $result, $association, $assocData, $model, $linkModel, $stack)) {
1195
						$ins[] = $in;
1196
					}
1197
				}
1198
1199
				if (!empty($ins)) {
1200
					$ins = array_unique($ins);
1201
					$fetch = $this->fetchAssociated($model, $query, $ins);
1202
				}
1203
1204
				if (!empty($fetch) && is_array($fetch)) {
1205
					if ($recursive > 0) {
1206
						foreach ($linkModel->associations() as $type1) {
1207
							foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
1208
								$deepModel = $linkModel->{$assoc1};
1209
								$tmpStack = $stack;
1210
								$tmpStack[] = $assoc1;
1211
1212 View Code Duplication
								if ($linkModel->useDbConfig === $deepModel->useDbConfig) {
1213
									$db = $this;
1214
								} else {
1215
									$db = ConnectionManager::getDataSource($deepModel->useDbConfig);
1216
								}
1217
								$db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
1218
							}
1219
						}
1220
					}
1221
				}
1222
				if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
1223
					$this->_filterResults($fetch, $model);
0 ignored issues
show
Bug introduced by
It seems like $fetch defined by $this->fetchAssociated($model, $query, $ins) on line 1201 can also be of type boolean; however, DboSource::_filterResults() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1224
				}
1225
				return $this->_mergeHasMany($resultSet, $fetch, $association, $model, $linkModel);
0 ignored issues
show
Bug introduced by
It seems like $fetch defined by $this->fetchAssociated($model, $query, $ins) on line 1201 can also be of type boolean; however, DboSource::_mergeHasMany() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1226
			} elseif ($type === 'hasAndBelongsToMany') {
1227
				$ins = $fetch = array();
1228 View Code Duplication
				foreach ($resultSet as &$result) {
1229
					if ($in = $this->insertQueryData('{$__cakeID__$}', $result, $association, $assocData, $model, $linkModel, $stack)) {
1230
						$ins[] = $in;
1231
					}
1232
				}
1233
				if (!empty($ins)) {
1234
					$ins = array_unique($ins);
1235
					if (count($ins) > 1) {
1236
						$query = str_replace('{$__cakeID__$}', '(' . implode(', ', $ins) . ')', $query);
1237
						$query = str_replace('= (', 'IN (', $query);
1238
					} else {
1239
						$query = str_replace('{$__cakeID__$}', $ins[0], $query);
1240
					}
1241
					$query = str_replace(' WHERE 1 = 1', '', $query);
1242
				}
1243
1244
				$foreignKey = $model->hasAndBelongsToMany[$association]['foreignKey'];
1245
				$joinKeys = array($foreignKey, $model->hasAndBelongsToMany[$association]['associationForeignKey']);
1246
				list($with, $habtmFields) = $model->joinModel($model->hasAndBelongsToMany[$association]['with'], $joinKeys);
1247
				$habtmFieldsCount = count($habtmFields);
1248
				$q = $this->insertQueryData($query, null, $association, $assocData, $model, $linkModel, $stack);
1249
1250
				if ($q !== false) {
1251
					$fetch = $this->fetchAll($q, $model->cacheQueries);
1252
				} else {
1253
					$fetch = null;
1254
				}
1255
			}
1256
1257
			$modelAlias = $model->alias;
1258
			$modelPK = $model->primaryKey;
1259
			foreach ($resultSet as &$row) {
1260
				if ($type !== 'hasAndBelongsToMany') {
1261
					$q = $this->insertQueryData($query, $row, $association, $assocData, $model, $linkModel, $stack);
1262
					$fetch = null;
1263
					if ($q !== false) {
1264
						$joinedData = array();
1265
						if (($type === 'belongsTo' || $type === 'hasOne') && isset($row[$linkModel->alias], $joined[$model->alias]) && in_array($linkModel->alias, $joined[$model->alias])) {
1266
							$joinedData = Hash::filter($row[$linkModel->alias]);
1267
							if (!empty($joinedData)) {
1268
								$fetch[0] = array($linkModel->alias => $row[$linkModel->alias]);
1269
							}
1270
						} else {
1271
							$fetch = $this->fetchAll($q, $model->cacheQueries);
1272
						}
1273
					}
1274
				}
1275
				$selfJoin = $linkModel->name === $model->name;
1276
1277
				if (!empty($fetch) && is_array($fetch)) {
1278
					if ($recursive > 0) {
1279
						foreach ($linkModel->associations() as $type1) {
1280
							foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
1281
								$deepModel = $linkModel->{$assoc1};
1282
1283
								if ($type1 === 'belongsTo' || ($deepModel->alias === $modelAlias && $type === 'belongsTo') || ($deepModel->alias !== $modelAlias)) {
1284
									$tmpStack = $stack;
1285
									$tmpStack[] = $assoc1;
1286 View Code Duplication
									if ($linkModel->useDbConfig === $deepModel->useDbConfig) {
1287
										$db = $this;
1288
									} else {
1289
										$db = ConnectionManager::getDataSource($deepModel->useDbConfig);
1290
									}
1291
									$db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
1292
								}
1293
							}
1294
						}
1295
					}
1296
					if ($type === 'hasAndBelongsToMany') {
1297
						$merge = array();
1298
1299
						foreach ($fetch as $data) {
1300
							if (isset($data[$with]) && $data[$with][$foreignKey] === $row[$modelAlias][$modelPK]) {
0 ignored issues
show
Bug introduced by
The variable $with does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $foreignKey does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1301
								if ($habtmFieldsCount <= 2) {
0 ignored issues
show
Bug introduced by
The variable $habtmFieldsCount does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1302
									unset($data[$with]);
1303
								}
1304
								$merge[] = $data;
1305
							}
1306
						}
1307
						if (empty($merge) && !isset($row[$association])) {
1308
							$row[$association] = $merge;
1309
						} else {
1310
							$this->_mergeAssociation($row, $merge, $association, $type);
1311
						}
1312
					} else {
1313
						$this->_mergeAssociation($row, $fetch, $association, $type, $selfJoin);
1314
					}
1315
					if (isset($row[$association])) {
1316
						$row[$association] = $linkModel->afterFind($row[$association], false);
1317
					}
1318
				} else {
1319
					$tempArray[0][$association] = false;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$tempArray was never initialized. Although not strictly required by PHP, it is generally a good practice to add $tempArray = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1320
					$this->_mergeAssociation($row, $tempArray, $association, $type, $selfJoin);
1321
				}
1322
			}
1323
		}
1324
	}
1325
1326
/**
1327
 * A more efficient way to fetch associations.
1328
 *
1329
 * @param Model $model Primary model object
1330
 * @param string $query Association query
1331
 * @param array $ids Array of IDs of associated records
1332
 * @return array Association results
1333
 */
1334
	public function fetchAssociated(Model $model, $query, $ids) {
1335
		$query = str_replace('{$__cakeID__$}', implode(', ', $ids), $query);
1336
		if (count($ids) > 1) {
1337
			$query = str_replace('= (', 'IN (', $query);
1338
		}
1339
		return $this->fetchAll($query, $model->cacheQueries);
1340
	}
1341
1342
/**
1343
 * Merge the results of hasMany relations.
1344
 *
1345
 * @param array $resultSet Data to merge into
1346
 * @param array $merge Data to merge
1347
 * @param string $association Name of Model being Merged
1348
 * @param Model $model Model being merged onto
1349
 * @param Model $linkModel Model being merged
1350
 * @return void
1351
 */
1352
	protected function _mergeHasMany(&$resultSet, $merge, $association, $model, $linkModel) {
0 ignored issues
show
Unused Code introduced by
The parameter $linkModel 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...
1353
		$modelAlias = $model->alias;
1354
		$modelPK = $model->primaryKey;
1355
		$modelFK = $model->hasMany[$association]['foreignKey'];
1356
		foreach ($resultSet as &$result) {
1357
			if (!isset($result[$modelAlias])) {
1358
				continue;
1359
			}
1360
			$merged = array();
1361
			foreach ($merge as $data) {
1362
				if ($result[$modelAlias][$modelPK] === $data[$association][$modelFK]) {
1363
					if (count($data) > 1) {
1364
						$data = array_merge($data[$association], $data);
1365
						unset($data[$association]);
1366
						foreach ($data as $key => $name) {
1367
							if (is_numeric($key)) {
1368
								$data[$association][] = $name;
1369
								unset($data[$key]);
1370
							}
1371
						}
1372
						$merged[] = $data;
1373
					} else {
1374
						$merged[] = $data[$association];
1375
					}
1376
				}
1377
			}
1378
			$result = Hash::mergeDiff($result, array($association => $merged));
1379
		}
1380
	}
1381
1382
/**
1383
 * Merge association of merge into data
1384
 *
1385
 * @param array $data
1386
 * @param array $merge
1387
 * @param string $association
1388
 * @param string $type
1389
 * @param boolean $selfJoin
1390
 * @return void
1391
 */
1392
	protected function _mergeAssociation(&$data, &$merge, $association, $type, $selfJoin = false) {
1393
		if (isset($merge[0]) && !isset($merge[0][$association])) {
1394
			$association = Inflector::pluralize($association);
1395
		}
1396
1397
		if ($type === 'belongsTo' || $type === 'hasOne') {
1398
			if (isset($merge[$association])) {
1399
				$data[$association] = $merge[$association][0];
1400
			} else {
1401
				if (!empty($merge[0][$association])) {
1402
					foreach ($merge[0] as $assoc => $data2) {
1403
						if ($assoc !== $association) {
1404
							$merge[0][$association][$assoc] = $data2;
1405
						}
1406
					}
1407
				}
1408
				if (!isset($data[$association])) {
1409
					$data[$association] = array();
1410
					if ($merge[0][$association]) {
1411
						$data[$association] = $merge[0][$association];
1412
					}
1413
				} else {
1414
					if (is_array($merge[0][$association])) {
1415
						foreach ($data[$association] as $k => $v) {
1416
							if (!is_array($v)) {
1417
								$dataAssocTmp[$k] = $v;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$dataAssocTmp was never initialized. Although not strictly required by PHP, it is generally a good practice to add $dataAssocTmp = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1418
							}
1419
						}
1420
1421
						foreach ($merge[0][$association] as $k => $v) {
1422
							if (!is_array($v)) {
1423
								$mergeAssocTmp[$k] = $v;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$mergeAssocTmp was never initialized. Although not strictly required by PHP, it is generally a good practice to add $mergeAssocTmp = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1424
							}
1425
						}
1426
						$dataKeys = array_keys($data);
1427
						$mergeKeys = array_keys($merge[0]);
1428
1429
						if ($mergeKeys[0] === $dataKeys[0] || $mergeKeys === $dataKeys) {
1430
							$data[$association][$association] = $merge[0][$association];
1431
						} else {
1432
							$diff = Hash::diff($dataAssocTmp, $mergeAssocTmp);
0 ignored issues
show
Bug introduced by
The variable $dataAssocTmp does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $mergeAssocTmp does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1433
							$data[$association] = array_merge($merge[0][$association], $diff);
1434
						}
1435
					} elseif ($selfJoin && array_key_exists($association, $merge[0])) {
1436
						$data[$association] = array_merge($data[$association], array($association => array()));
1437
					}
1438
				}
1439
			}
1440
		} else {
1441
			if (isset($merge[0][$association]) && $merge[0][$association] === false) {
1442
				if (!isset($data[$association])) {
1443
					$data[$association] = array();
1444
				}
1445
			} else {
1446
				foreach ($merge as $row) {
1447
					$insert = array();
1448
					if (count($row) === 1) {
1449
						$insert = $row[$association];
1450
					} elseif (isset($row[$association])) {
1451
						$insert = array_merge($row[$association], $row);
1452
						unset($insert[$association]);
1453
					}
1454
1455
					if (empty($data[$association]) || (isset($data[$association]) && !in_array($insert, $data[$association], true))) {
1456
						$data[$association][] = $insert;
1457
					}
1458
				}
1459
			}
1460
		}
1461
	}
1462
1463
/**
1464
 * Generates an array representing a query or part of a query from a single model or two associated models
1465
 *
1466
 * @param Model $model
1467
 * @param Model $linkModel
1468
 * @param string $type
1469
 * @param string $association
1470
 * @param array $assocData
1471
 * @param array $queryData
1472
 * @param boolean $external
1473
 * @param array $resultSet
1474
 * @return mixed
1475
 */
1476
	public function generateAssociationQuery(Model $model, $linkModel, $type, $association, $assocData, &$queryData, $external, &$resultSet) {
0 ignored issues
show
Unused Code introduced by
The parameter $resultSet 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...
1477
		$queryData = $this->_scrubQueryData($queryData);
1478
		$assocData = $this->_scrubQueryData($assocData);
1479
		$modelAlias = $model->alias;
1480
1481
		if (empty($queryData['fields'])) {
1482
			$queryData['fields'] = $this->fields($model, $modelAlias);
1483
		} elseif (!empty($model->hasMany) && $model->recursive > -1) {
1484
			$assocFields = $this->fields($model, $modelAlias, array("{$modelAlias}.{$model->primaryKey}"));
1485
			$passedFields = $queryData['fields'];
1486
			if (count($passedFields) === 1) {
1487
				if (strpos($passedFields[0], $assocFields[0]) === false && !preg_match('/^[a-z]+\(/i', $passedFields[0])) {
1488
					$queryData['fields'] = array_merge($passedFields, $assocFields);
1489
				} else {
1490
					$queryData['fields'] = $passedFields;
1491
				}
1492
			} else {
1493
				$queryData['fields'] = array_merge($passedFields, $assocFields);
1494
			}
1495
			unset($assocFields, $passedFields);
1496
		}
1497
1498
		if ($linkModel === null) {
1499
			return $this->buildStatement(
1500
				array(
1501
					'fields' => array_unique($queryData['fields']),
1502
					'table' => $this->fullTableName($model),
1503
					'alias' => $modelAlias,
1504
					'limit' => $queryData['limit'],
1505
					'offset' => $queryData['offset'],
1506
					'joins' => $queryData['joins'],
1507
					'conditions' => $queryData['conditions'],
1508
					'order' => $queryData['order'],
1509
					'group' => $queryData['group']
1510
				),
1511
				$model
1512
			);
1513
		}
1514
		if ($external && !empty($assocData['finderQuery'])) {
1515
			return $assocData['finderQuery'];
1516
		}
1517
1518
		$self = $model->name === $linkModel->name;
1519
		$fields = array();
1520
1521
		if ($external || (in_array($type, array('hasOne', 'belongsTo')) && $assocData['fields'] !== false)) {
1522
			$fields = $this->fields($linkModel, $association, $assocData['fields']);
1523
		}
1524 View Code Duplication
		if (empty($assocData['offset']) && !empty($assocData['page'])) {
1525
			$assocData['offset'] = ($assocData['page'] - 1) * $assocData['limit'];
1526
		}
1527
1528
		switch ($type) {
1529
			case 'hasOne':
1530
			case 'belongsTo':
1531
				$conditions = $this->_mergeConditions(
1532
					$assocData['conditions'],
1533
					$this->getConstraint($type, $model, $linkModel, $association, array_merge($assocData, compact('external', 'self')))
1534
				);
1535
1536
				if (!$self && $external) {
1537
					foreach ($conditions as $key => $condition) {
1538
						if (is_numeric($key) && strpos($condition, $modelAlias . '.') !== false) {
1539
							unset($conditions[$key]);
1540
						}
1541
					}
1542
				}
1543
1544
				if ($external) {
1545
					$query = array_merge($assocData, array(
1546
						'conditions' => $conditions,
1547
						'table' => $this->fullTableName($linkModel),
1548
						'fields' => $fields,
1549
						'alias' => $association,
1550
						'group' => null
1551
					));
1552
				} else {
1553
					$join = array(
1554
						'table' => $linkModel,
1555
						'alias' => $association,
1556
						'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT',
1557
						'conditions' => trim($this->conditions($conditions, true, false, $model))
1558
					);
1559
					$queryData['fields'] = array_merge($queryData['fields'], $fields);
1560
1561
					if (!empty($assocData['order'])) {
1562
						$queryData['order'][] = $assocData['order'];
1563
					}
1564
					if (!in_array($join, $queryData['joins'], true)) {
1565
						$queryData['joins'][] = $join;
1566
					}
1567
					return true;
1568
				}
1569
				break;
1570
			case 'hasMany':
1571
				$assocData['fields'] = $this->fields($linkModel, $association, $assocData['fields']);
1572
				if (!empty($assocData['foreignKey'])) {
1573
					$assocData['fields'] = array_merge($assocData['fields'], $this->fields($linkModel, $association, array("{$association}.{$assocData['foreignKey']}")));
1574
				}
1575
				$query = array(
1576
					'conditions' => $this->_mergeConditions($this->getConstraint('hasMany', $model, $linkModel, $association, $assocData), $assocData['conditions']),
1577
					'fields' => array_unique($assocData['fields']),
1578
					'table' => $this->fullTableName($linkModel),
1579
					'alias' => $association,
1580
					'order' => $assocData['order'],
1581
					'limit' => $assocData['limit'],
1582
					'offset' => $assocData['offset'],
1583
					'group' => null
1584
				);
1585
				break;
1586
			case 'hasAndBelongsToMany':
1587
				$joinFields = array();
1588
				$joinAssoc = null;
1589
1590
				if (isset($assocData['with']) && !empty($assocData['with'])) {
1591
					$joinKeys = array($assocData['foreignKey'], $assocData['associationForeignKey']);
1592
					list($with, $joinFields) = $model->joinModel($assocData['with'], $joinKeys);
1593
1594
					$joinTbl = $model->{$with};
1595
					$joinAlias = $joinTbl;
1596
1597
					if (is_array($joinFields) && !empty($joinFields)) {
1598
						$joinAssoc = $joinAlias = $model->{$with}->alias;
1599
						$joinFields = $this->fields($model->{$with}, $joinAlias, $joinFields);
1600
					} else {
1601
						$joinFields = array();
1602
					}
1603
				} else {
1604
					$joinTbl = $assocData['joinTable'];
1605
					$joinAlias = $this->fullTableName($assocData['joinTable']);
1606
				}
1607
				$query = array(
1608
					'conditions' => $assocData['conditions'],
1609
					'limit' => $assocData['limit'],
1610
					'offset' => $assocData['offset'],
1611
					'table' => $this->fullTableName($linkModel),
1612
					'alias' => $association,
1613
					'fields' => array_merge($this->fields($linkModel, $association, $assocData['fields']), $joinFields),
1614
					'order' => $assocData['order'],
1615
					'group' => null,
1616
					'joins' => array(array(
1617
						'table' => $joinTbl,
1618
						'alias' => $joinAssoc,
1619
						'conditions' => $this->getConstraint('hasAndBelongsToMany', $model, $linkModel, $joinAlias, $assocData, $association)
1620
					))
1621
				);
1622
				break;
1623
		}
1624
		if (isset($query)) {
1625
			return $this->buildStatement($query, $model);
1626
		}
1627
		return null;
1628
	}
1629
1630
/**
1631
 * Returns a conditions array for the constraint between two models
1632
 *
1633
 * @param string $type Association type
1634
 * @param Model $model Model object
1635
 * @param string $linkModel
1636
 * @param string $alias
1637
 * @param array $assoc
1638
 * @param string $alias2
1639
 * @return array Conditions array defining the constraint between $model and $association
1640
 */
1641
	public function getConstraint($type, $model, $linkModel, $alias, $assoc, $alias2 = null) {
1642
		$assoc += array('external' => false, 'self' => false);
1643
1644
		if (empty($assoc['foreignKey'])) {
1645
			return array();
1646
		}
1647
1648
		switch (true) {
1649
			case ($assoc['external'] && $type === 'hasOne'):
1650
				return array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}');
1651
			case ($assoc['external'] && $type === 'belongsTo'):
1652
				return array("{$alias}.{$linkModel->primaryKey}" => '{$__cakeForeignKey__$}');
1653 View Code Duplication
			case (!$assoc['external'] && $type === 'hasOne'):
1654
				return array("{$alias}.{$assoc['foreignKey']}" => $this->identifier("{$model->alias}.{$model->primaryKey}"));
1655 View Code Duplication
			case (!$assoc['external'] && $type === 'belongsTo'):
1656
				return array("{$model->alias}.{$assoc['foreignKey']}" => $this->identifier("{$alias}.{$linkModel->primaryKey}"));
1657
			case ($type === 'hasMany'):
1658
				return array("{$alias}.{$assoc['foreignKey']}" => array('{$__cakeID__$}'));
1659
			case ($type === 'hasAndBelongsToMany'):
1660
				return array(
1661
					array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}'),
1662
					array("{$alias}.{$assoc['associationForeignKey']}" => $this->identifier("{$alias2}.{$linkModel->primaryKey}"))
1663
				);
1664
		}
1665
		return array();
1666
	}
1667
1668
/**
1669
 * Builds and generates a JOIN statement from an array. Handles final clean-up before conversion.
1670
 *
1671
 * @param array $join An array defining a JOIN statement in a query
1672
 * @return string An SQL JOIN statement to be used in a query
1673
 * @see DboSource::renderJoinStatement()
1674
 * @see DboSource::buildStatement()
1675
 */
1676
	public function buildJoinStatement($join) {
1677
		$data = array_merge(array(
1678
			'type' => null,
1679
			'alias' => null,
1680
			'table' => 'join_table',
1681
			'conditions' => array()
1682
		), $join);
1683
1684
		if (!empty($data['alias'])) {
1685
			$data['alias'] = $this->alias . $this->name($data['alias']);
1686
		}
1687
		if (!empty($data['conditions'])) {
1688
			$data['conditions'] = trim($this->conditions($data['conditions'], true, false));
1689
		}
1690
		if (!empty($data['table']) && (!is_string($data['table']) || strpos($data['table'], '(') !== 0)) {
1691
			$data['table'] = $this->fullTableName($data['table']);
1692
		}
1693
		return $this->renderJoinStatement($data);
1694
	}
1695
1696
/**
1697
 * Builds and generates an SQL statement from an array. Handles final clean-up before conversion.
1698
 *
1699
 * @param array $query An array defining an SQL query
1700
 * @param Model $model The model object which initiated the query
1701
 * @return string An executable SQL statement
1702
 * @see DboSource::renderStatement()
1703
 */
1704
	public function buildStatement($query, $model) {
1705
		$query = array_merge($this->_queryDefaults, $query);
1706
		if (!empty($query['joins'])) {
1707
			$count = count($query['joins']);
1708
			for ($i = 0; $i < $count; $i++) {
1709
				if (is_array($query['joins'][$i])) {
1710
					$query['joins'][$i] = $this->buildJoinStatement($query['joins'][$i]);
1711
				}
1712
			}
1713
		}
1714
		return $this->renderStatement('select', array(
1715
			'conditions' => $this->conditions($query['conditions'], true, true, $model),
1716
			'fields' => implode(', ', $query['fields']),
1717
			'table' => $query['table'],
1718
			'alias' => $this->alias . $this->name($query['alias']),
1719
			'order' => $this->order($query['order'], 'ASC', $model),
1720
			'limit' => $this->limit($query['limit'], $query['offset']),
1721
			'joins' => implode(' ', $query['joins']),
1722
			'group' => $this->group($query['group'], $model)
1723
		));
1724
	}
1725
1726
/**
1727
 * Renders a final SQL JOIN statement
1728
 *
1729
 * @param array $data
1730
 * @return string
1731
 */
1732
	public function renderJoinStatement($data) {
1733
		if (strtoupper($data['type']) === 'CROSS') {
1734
			return "{$data['type']} JOIN {$data['table']} {$data['alias']}";
1735
		}
1736
		return trim("{$data['type']} JOIN {$data['table']} {$data['alias']} ON ({$data['conditions']})");
1737
	}
1738
1739
/**
1740
 * Renders a final SQL statement by putting together the component parts in the correct order
1741
 *
1742
 * @param string $type type of query being run. e.g select, create, update, delete, schema, alter.
1743
 * @param array $data Array of data to insert into the query.
1744
 * @return string Rendered SQL expression to be run.
1745
 */
1746
	public function renderStatement($type, $data) {
1747
		extract($data);
1748
		$aliases = null;
1749
1750
		switch (strtolower($type)) {
1751
			case 'select':
1752
				return trim("SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}");
1753
			case 'create':
1754
				return "INSERT INTO {$table} ({$fields}) VALUES ({$values})";
1755 View Code Duplication
			case 'update':
1756
				if (!empty($alias)) {
1757
					$aliases = "{$this->alias}{$alias} {$joins} ";
1758
				}
1759
				return trim("UPDATE {$table} {$aliases}SET {$fields} {$conditions}");
1760 View Code Duplication
			case 'delete':
1761
				if (!empty($alias)) {
1762
					$aliases = "{$this->alias}{$alias} {$joins} ";
1763
				}
1764
				return trim("DELETE {$alias} FROM {$table} {$aliases}{$conditions}");
1765
			case 'schema':
1766
				foreach (array('columns', 'indexes', 'tableParameters') as $var) {
1767 View Code Duplication
					if (is_array(${$var})) {
1768
						${$var} = "\t" . implode(",\n\t", array_filter(${$var}));
1769
					} else {
1770
						${$var} = '';
1771
					}
1772
				}
1773
				if (trim($indexes) !== '') {
1774
					$columns .= ',';
1775
				}
1776
				return "CREATE TABLE {$table} (\n{$columns}{$indexes}) {$tableParameters};";
1777
			case 'alter':
1778
				return;
1779
		}
1780
	}
1781
1782
/**
1783
 * Merges a mixed set of string/array conditions
1784
 *
1785
 * @param mixed $query
1786
 * @param mixed $assoc
1787
 * @return array
1788
 */
1789
	protected function _mergeConditions($query, $assoc) {
1790
		if (empty($assoc)) {
1791
			return $query;
1792
		}
1793
1794
		if (is_array($query)) {
1795
			return array_merge((array)$assoc, $query);
1796
		}
1797
1798
		if (!empty($query)) {
1799
			$query = array($query);
1800
			if (is_array($assoc)) {
1801
				$query = array_merge($query, $assoc);
1802
			} else {
1803
				$query[] = $assoc;
1804
			}
1805
			return $query;
1806
		}
1807
1808
		return $assoc;
1809
	}
1810
1811
/**
1812
 * Generates and executes an SQL UPDATE statement for given model, fields, and values.
1813
 * For databases that do not support aliases in UPDATE queries.
1814
 *
1815
 * @param Model $model
1816
 * @param array $fields
1817
 * @param array $values
1818
 * @param mixed $conditions
1819
 * @return boolean Success
1820
 */
1821
	public function update(Model $model, $fields = array(), $values = null, $conditions = null) {
1822
		if (!$values) {
1823
			$combined = $fields;
1824
		} else {
1825
			$combined = array_combine($fields, $values);
1826
		}
1827
1828
		$fields = implode(', ', $this->_prepareUpdateFields($model, $combined, empty($conditions)));
1829
1830
		$alias = $joins = null;
1831
		$table = $this->fullTableName($model);
1832
		$conditions = $this->_matchRecords($model, $conditions);
1833
1834
		if ($conditions === false) {
1835
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method DataSource::update of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1836
		}
1837
		$query = compact('table', 'alias', 'joins', 'fields', 'conditions');
1838
1839
		if (!$this->execute($this->renderStatement('update', $query))) {
1840
			$model->onError();
1841
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method DataSource::update of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1842
		}
1843
		return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type of the parent method DataSource::update of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1844
	}
1845
1846
/**
1847
 * Quotes and prepares fields and values for an SQL UPDATE statement
1848
 *
1849
 * @param Model $model
1850
 * @param array $fields
1851
 * @param boolean $quoteValues If values should be quoted, or treated as SQL snippets
1852
 * @param boolean $alias Include the model alias in the field name
1853
 * @return array Fields and values, quoted and prepared
1854
 */
1855
	protected function _prepareUpdateFields(Model $model, $fields, $quoteValues = true, $alias = false) {
1856
		$quotedAlias = $this->startQuote . $model->alias . $this->endQuote;
1857
1858
		$updates = array();
1859
		foreach ($fields as $field => $value) {
1860
			if ($alias && strpos($field, '.') === false) {
1861
				$quoted = $model->escapeField($field);
1862
			} elseif (!$alias && strpos($field, '.') !== false) {
1863
				$quoted = $this->name(str_replace($quotedAlias . '.', '', str_replace(
1864
					$model->alias . '.', '', $field
1865
				)));
1866
			} else {
1867
				$quoted = $this->name($field);
1868
			}
1869
1870
			if ($value === null) {
1871
				$updates[] = $quoted . ' = NULL';
1872
				continue;
1873
			}
1874
			$update = $quoted . ' = ';
1875
1876
			if ($quoteValues) {
1877
				$update .= $this->value($value, $model->getColumnType($field));
1878
			} elseif ($model->getColumnType($field) === 'boolean' && (is_int($value) || is_bool($value))) {
1879
				$update .= $this->boolean($value, true);
1880
			} elseif (!$alias) {
1881
				$update .= str_replace($quotedAlias . '.', '', str_replace(
1882
					$model->alias . '.', '', $value
1883
				));
1884
			} else {
1885
				$update .= $value;
1886
			}
1887
			$updates[] = $update;
1888
		}
1889
		return $updates;
1890
	}
1891
1892
/**
1893
 * Generates and executes an SQL DELETE statement.
1894
 * For databases that do not support aliases in UPDATE queries.
1895
 *
1896
 * @param Model $model
1897
 * @param mixed $conditions
1898
 * @return boolean Success
1899
 */
1900
	public function delete(Model $model, $conditions = null) {
1901
		$alias = $joins = null;
1902
		$table = $this->fullTableName($model);
1903
		$conditions = $this->_matchRecords($model, $conditions);
1904
1905
		if ($conditions === false) {
1906
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method DataSource::delete of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1907
		}
1908
1909 View Code Duplication
		if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) {
1910
			$model->onError();
1911
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method DataSource::delete of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1912
		}
1913
		return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type of the parent method DataSource::delete of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1914
	}
1915
1916
/**
1917
 * Gets a list of record IDs for the given conditions. Used for multi-record updates and deletes
1918
 * in databases that do not support aliases in UPDATE/DELETE queries.
1919
 *
1920
 * @param Model $model
1921
 * @param mixed $conditions
1922
 * @return array List of record IDs
1923
 */
1924
	protected function _matchRecords(Model $model, $conditions = null) {
1925
		if ($conditions === true) {
1926
			$conditions = $this->conditions(true);
1927
		} elseif ($conditions === null) {
1928
			$conditions = $this->conditions($this->defaultConditions($model, $conditions, false), true, true, $model);
1929
		} else {
1930
			$noJoin = true;
1931
			foreach ($conditions as $field => $value) {
1932
				$originalField = $field;
1933
				if (strpos($field, '.') !== false) {
1934
					list(, $field) = explode('.', $field);
1935
					$field = ltrim($field, $this->startQuote);
1936
					$field = rtrim($field, $this->endQuote);
1937
				}
1938
				if (!$model->hasField($field)) {
1939
					$noJoin = false;
1940
					break;
1941
				}
1942
				if ($field !== $originalField) {
1943
					$conditions[$field] = $value;
1944
					unset($conditions[$originalField]);
1945
				}
1946
			}
1947
			if ($noJoin === true) {
1948
				return $this->conditions($conditions);
1949
			}
1950
			$idList = $model->find('all', array(
1951
				'fields' => "{$model->alias}.{$model->primaryKey}",
1952
				'conditions' => $conditions
1953
			));
1954
1955
			if (empty($idList)) {
1956
				return false;
1957
			}
1958
			$conditions = $this->conditions(array(
1959
				$model->primaryKey => Hash::extract($idList, "{n}.{$model->alias}.{$model->primaryKey}")
1960
			));
1961
		}
1962
		return $conditions;
1963
	}
1964
1965
/**
1966
 * Returns an array of SQL JOIN fragments from a model's associations
1967
 *
1968
 * @param Model $model
1969
 * @return array
1970
 */
1971
	protected function _getJoins(Model $model) {
1972
		$join = array();
1973
		$joins = array_merge($model->getAssociated('hasOne'), $model->getAssociated('belongsTo'));
1974
1975
		foreach ($joins as $assoc) {
1976
			if (isset($model->{$assoc}) && $model->useDbConfig === $model->{$assoc}->useDbConfig && $model->{$assoc}->getDataSource()) {
1977
				$assocData = $model->getAssociated($assoc);
1978
				$join[] = $this->buildJoinStatement(array(
1979
					'table' => $model->{$assoc},
1980
					'alias' => $assoc,
1981
					'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT',
1982
					'conditions' => trim($this->conditions(
1983
						$this->_mergeConditions($assocData['conditions'], $this->getConstraint($assocData['association'], $model, $model->{$assoc}, $assoc, $assocData)),
1984
						true, false, $model
1985
					))
1986
				));
1987
			}
1988
		}
1989
		return $join;
1990
	}
1991
1992
/**
1993
 * Returns an SQL calculation, i.e. COUNT() or MAX()
1994
 *
1995
 * @param Model $model
1996
 * @param string $func Lowercase name of SQL function, i.e. 'count' or 'max'
1997
 * @param array $params Function parameters (any values must be quoted manually)
1998
 * @return string An SQL calculation function
1999
 */
2000
	public function calculate(Model $model, $func, $params = array()) {
2001
		$params = (array)$params;
2002
2003
		switch (strtolower($func)) {
2004
			case 'count':
2005
				if (!isset($params[0])) {
2006
					$params[0] = '*';
2007
				}
2008
				if (!isset($params[1])) {
2009
					$params[1] = 'count';
2010
				}
2011 View Code Duplication
				if (is_object($model) && $model->isVirtualField($params[0])) {
2012
					$arg = $this->_quoteFields($model->getVirtualField($params[0]));
2013
				} else {
2014
					$arg = $this->name($params[0]);
2015
				}
2016
				return 'COUNT(' . $arg . ') AS ' . $this->name($params[1]);
2017
			case 'max':
2018
			case 'min':
2019
				if (!isset($params[1])) {
2020
					$params[1] = $params[0];
2021
				}
2022 View Code Duplication
				if (is_object($model) && $model->isVirtualField($params[0])) {
2023
					$arg = $this->_quoteFields($model->getVirtualField($params[0]));
2024
				} else {
2025
					$arg = $this->name($params[0]);
2026
				}
2027
				return strtoupper($func) . '(' . $arg . ') AS ' . $this->name($params[1]);
2028
		}
2029
	}
2030
2031
/**
2032
 * Deletes all the records in a table and resets the count of the auto-incrementing
2033
 * primary key, where applicable.
2034
 *
2035
 * @param Model|string $table A string or model class representing the table to be truncated
2036
 * @return boolean SQL TRUNCATE TABLE statement, false if not applicable.
2037
 */
2038
	public function truncate($table) {
2039
		return $this->execute('TRUNCATE TABLE ' . $this->fullTableName($table));
2040
	}
2041
2042
/**
2043
 * Check if the server support nested transactions
2044
 *
2045
 * @return boolean
2046
 */
2047
	public function nestedTransactionSupported() {
2048
		return false;
2049
	}
2050
2051
/**
2052
 * Begin a transaction
2053
 *
2054
 * @return boolean True on success, false on fail
2055
 * (i.e. if the database/model does not support transactions,
2056
 * or a transaction has not started).
2057
 */
2058
	public function begin() {
2059
		if ($this->_transactionStarted) {
2060
			if ($this->nestedTransactionSupported()) {
2061
				return $this->_beginNested();
2062
			}
2063
			$this->_transactionNesting++;
2064
			return $this->_transactionStarted;
2065
		}
2066
2067
		$this->_transactionNesting = 0;
2068
		if ($this->fullDebug) {
2069
			$this->logQuery('BEGIN');
2070
		}
2071
		return $this->_transactionStarted = $this->_connection->beginTransaction();
0 ignored issues
show
Bug introduced by
The method beginTransaction cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2072
	}
2073
2074
/**
2075
 * Begin a nested transaction
2076
 *
2077
 * @return boolean
2078
 */
2079 View Code Duplication
	protected function _beginNested() {
2080
		$query = 'SAVEPOINT LEVEL' . ++$this->_transactionNesting;
2081
		if ($this->fullDebug) {
2082
			$this->logQuery($query);
2083
		}
2084
		$this->_connection->exec($query);
0 ignored issues
show
Bug introduced by
The method exec cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2085
		return true;
2086
	}
2087
2088
/**
2089
 * Commit a transaction
2090
 *
2091
 * @return boolean True on success, false on fail
2092
 * (i.e. if the database/model does not support transactions,
2093
 * or a transaction has not started).
2094
 */
2095 View Code Duplication
	public function commit() {
2096
		if (!$this->_transactionStarted) {
2097
			return false;
2098
		}
2099
2100
		if ($this->_transactionNesting === 0) {
2101
			if ($this->fullDebug) {
2102
				$this->logQuery('COMMIT');
2103
			}
2104
			$this->_transactionStarted = false;
2105
			return $this->_connection->commit();
0 ignored issues
show
Bug introduced by
The method commit cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2106
		}
2107
2108
		if ($this->nestedTransactionSupported()) {
2109
			return $this->_commitNested();
2110
		}
2111
2112
		$this->_transactionNesting--;
2113
		return true;
2114
	}
2115
2116
/**
2117
 * Commit a nested transaction
2118
 *
2119
 * @return boolean
2120
 */
2121 View Code Duplication
	protected function _commitNested() {
2122
		$query = 'RELEASE SAVEPOINT LEVEL' . $this->_transactionNesting--;
2123
		if ($this->fullDebug) {
2124
			$this->logQuery($query);
2125
		}
2126
		$this->_connection->exec($query);
0 ignored issues
show
Bug introduced by
The method exec cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2127
		return true;
2128
	}
2129
2130
/**
2131
 * Rollback a transaction
2132
 *
2133
 * @return boolean True on success, false on fail
2134
 * (i.e. if the database/model does not support transactions,
2135
 * or a transaction has not started).
2136
 */
2137 View Code Duplication
	public function rollback() {
2138
		if (!$this->_transactionStarted) {
2139
			return false;
2140
		}
2141
2142
		if ($this->_transactionNesting === 0) {
2143
			if ($this->fullDebug) {
2144
				$this->logQuery('ROLLBACK');
2145
			}
2146
			$this->_transactionStarted = false;
2147
			return $this->_connection->rollBack();
0 ignored issues
show
Bug introduced by
The method rollBack cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2148
		}
2149
2150
		if ($this->nestedTransactionSupported()) {
2151
			return $this->_rollbackNested();
2152
		}
2153
2154
		$this->_transactionNesting--;
2155
		return true;
2156
	}
2157
2158
/**
2159
 * Rollback a nested transaction
2160
 *
2161
 * @return boolean
2162
 */
2163 View Code Duplication
	protected function _rollbackNested() {
2164
		$query = 'ROLLBACK TO SAVEPOINT LEVEL' . $this->_transactionNesting--;
2165
		if ($this->fullDebug) {
2166
			$this->logQuery($query);
2167
		}
2168
		$this->_connection->exec($query);
0 ignored issues
show
Bug introduced by
The method exec cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2169
		return true;
2170
	}
2171
2172
/**
2173
 * Returns the ID generated from the previous INSERT operation.
2174
 *
2175
 * @param mixed $source
2176
 * @return mixed
2177
 */
2178
	public function lastInsertId($source = null) {
2179
		return $this->_connection->lastInsertId();
0 ignored issues
show
Bug introduced by
The method lastInsertId cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2180
	}
2181
2182
/**
2183
 * Creates a default set of conditions from the model if $conditions is null/empty.
2184
 * If conditions are supplied then they will be returned. If a model doesn't exist and no conditions
2185
 * were provided either null or false will be returned based on what was input.
2186
 *
2187
 * @param Model $model
2188
 * @param string|array|boolean $conditions Array of conditions, conditions string, null or false. If an array of conditions,
2189
 *   or string conditions those conditions will be returned. With other values the model's existence will be checked.
2190
 *   If the model doesn't exist a null or false will be returned depending on the input value.
2191
 * @param boolean $useAlias Use model aliases rather than table names when generating conditions
2192
 * @return mixed Either null, false, $conditions or an array of default conditions to use.
2193
 * @see DboSource::update()
2194
 * @see DboSource::conditions()
2195
 */
2196
	public function defaultConditions(Model $model, $conditions, $useAlias = true) {
2197
		if (!empty($conditions)) {
2198
			return $conditions;
2199
		}
2200
		$exists = $model->exists();
2201
		if (!$exists && $conditions !== null) {
2202
			return false;
2203
		} elseif (!$exists) {
2204
			return null;
2205
		}
2206
		$alias = $model->alias;
2207
2208
		if (!$useAlias) {
2209
			$alias = $this->fullTableName($model, false);
2210
		}
2211
		return array("{$alias}.{$model->primaryKey}" => $model->getID());
2212
	}
2213
2214
/**
2215
 * Returns a key formatted like a string Model.fieldname(i.e. Post.title, or Country.name)
2216
 *
2217
 * @param Model $model
2218
 * @param string $key
2219
 * @param string $assoc
2220
 * @return string
2221
 */
2222
	public function resolveKey(Model $model, $key, $assoc = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $assoc 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...
2223
		if (strpos('.', $key) !== false) {
2224
			return $this->name($model->alias) . '.' . $this->name($key);
2225
		}
2226
		return $key;
2227
	}
2228
2229
/**
2230
 * Private helper method to remove query metadata in given data array.
2231
 *
2232
 * @param array $data
2233
 * @return array
2234
 */
2235 View Code Duplication
	protected function _scrubQueryData($data) {
2236
		static $base = null;
2237
		if ($base === null) {
2238
			$base = array_fill_keys(array('conditions', 'fields', 'joins', 'order', 'limit', 'offset', 'group'), array());
2239
			$base['callbacks'] = null;
2240
		}
2241
		return (array)$data + $base;
2242
	}
2243
2244
/**
2245
 * Converts model virtual fields into sql expressions to be fetched later
2246
 *
2247
 * @param Model $model
2248
 * @param string $alias Alias table name
2249
 * @param array $fields virtual fields to be used on query
2250
 * @return array
2251
 */
2252
	protected function _constructVirtualFields(Model $model, $alias, $fields) {
2253
		$virtual = array();
2254
		foreach ($fields as $field) {
2255
			$virtualField = $this->name($alias . $this->virtualFieldSeparator . $field);
2256
			$expression = $this->_quoteFields($model->getVirtualField($field));
2257
			$virtual[] = '(' . $expression . ") {$this->alias} {$virtualField}";
2258
		}
2259
		return $virtual;
2260
	}
2261
2262
/**
2263
 * Generates the fields list of an SQL query.
2264
 *
2265
 * @param Model $model
2266
 * @param string $alias Alias table name
2267
 * @param mixed $fields
2268
 * @param boolean $quote If false, returns fields array unquoted
2269
 * @return array
2270
 */
2271
	public function fields(Model $model, $alias = null, $fields = array(), $quote = true) {
2272
		if (empty($alias)) {
2273
			$alias = $model->alias;
2274
		}
2275
		$virtualFields = $model->getVirtualField();
2276
		$cacheKey = array(
2277
			$alias,
2278
			get_class($model),
2279
			$model->alias,
2280
			$virtualFields,
2281
			$fields,
2282
			$quote,
2283
			ConnectionManager::getSourceName($this),
2284
			$model->schemaName,
2285
			$model->table
2286
		);
2287
		$cacheKey = md5(serialize($cacheKey));
2288
		if ($return = $this->cacheMethod(__FUNCTION__, $cacheKey)) {
2289
			return $return;
2290
		}
2291
		$allFields = empty($fields);
2292
		if ($allFields) {
2293
			$fields = array_keys($model->schema());
2294
		} elseif (!is_array($fields)) {
2295
			$fields = String::tokenize($fields);
2296
		}
2297
		$fields = array_values(array_filter($fields));
2298
		$allFields = $allFields || in_array('*', $fields) || in_array($model->alias . '.*', $fields);
2299
2300
		$virtual = array();
2301
		if (!empty($virtualFields)) {
2302
			$virtualKeys = array_keys($virtualFields);
2303
			foreach ($virtualKeys as $field) {
2304
				$virtualKeys[] = $model->alias . '.' . $field;
2305
			}
2306
			$virtual = ($allFields) ? $virtualKeys : array_intersect($virtualKeys, $fields);
2307
			foreach ($virtual as $i => $field) {
2308
				if (strpos($field, '.') !== false) {
2309
					$virtual[$i] = str_replace($model->alias . '.', '', $field);
2310
				}
2311
				$fields = array_diff($fields, array($field));
2312
			}
2313
			$fields = array_values($fields);
2314
		}
2315
		if (!$quote) {
2316
			if (!empty($virtual)) {
2317
				$fields = array_merge($fields, $this->_constructVirtualFields($model, $alias, $virtual));
2318
			}
2319
			return $fields;
2320
		}
2321
		$count = count($fields);
2322
2323
		if ($count >= 1 && !in_array($fields[0], array('*', 'COUNT(*)'))) {
2324
			for ($i = 0; $i < $count; $i++) {
2325
				if (is_string($fields[$i]) && in_array($fields[$i], $virtual)) {
2326
					unset($fields[$i]);
2327
					continue;
2328
				}
2329
				if (is_object($fields[$i]) && isset($fields[$i]->type) && $fields[$i]->type === 'expression') {
2330
					$fields[$i] = $fields[$i]->value;
2331
				} elseif (preg_match('/^\(.*\)\s' . $this->alias . '.*/i', $fields[$i])) {
2332
					continue;
2333
				} elseif (!preg_match('/^.+\\(.*\\)/', $fields[$i])) {
2334
					$prepend = '';
2335
2336 View Code Duplication
					if (strpos($fields[$i], 'DISTINCT') !== false) {
2337
						$prepend = 'DISTINCT ';
2338
						$fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i]));
2339
					}
2340
					$dot = strpos($fields[$i], '.');
2341
2342
					if ($dot === false) {
2343
						$prefix = !(
2344
							strpos($fields[$i], ' ') !== false ||
2345
							strpos($fields[$i], '(') !== false
2346
						);
2347
						$fields[$i] = $this->name(($prefix ? $alias . '.' : '') . $fields[$i]);
2348
					} else {
2349
						if (strpos($fields[$i], ',') === false) {
2350
							$build = explode('.', $fields[$i]);
2351
							if (!Hash::numeric($build)) {
2352
								$fields[$i] = $this->name(implode('.', $build));
2353
							}
2354
						}
2355
					}
2356
					$fields[$i] = $prepend . $fields[$i];
2357
				} elseif (preg_match('/\(([\.\w]+)\)/', $fields[$i], $field)) {
2358
					if (isset($field[1])) {
2359
						if (strpos($field[1], '.') === false) {
2360
							$field[1] = $this->name($alias . '.' . $field[1]);
2361
						} else {
2362
							$field[0] = explode('.', $field[1]);
2363
							if (!Hash::numeric($field[0])) {
2364
								$field[0] = implode('.', array_map(array(&$this, 'name'), $field[0]));
2365
								$fields[$i] = preg_replace('/\(' . $field[1] . '\)/', '(' . $field[0] . ')', $fields[$i], 1);
2366
							}
2367
						}
2368
					}
2369
				}
2370
			}
2371
		}
2372
		if (!empty($virtual)) {
2373
			$fields = array_merge($fields, $this->_constructVirtualFields($model, $alias, $virtual));
2374
		}
2375
		return $this->cacheMethod(__FUNCTION__, $cacheKey, array_unique($fields));
2376
	}
2377
2378
/**
2379
 * Creates a WHERE clause by parsing given conditions data. If an array or string
2380
 * conditions are provided those conditions will be parsed and quoted. If a boolean
2381
 * is given it will be integer cast as condition. Null will return 1 = 1.
2382
 *
2383
 * Results of this method are stored in a memory cache. This improves performance, but
2384
 * because the method uses a hashing algorithm it can have collisions.
2385
 * Setting DboSource::$cacheMethods to false will disable the memory cache.
2386
 *
2387
 * @param mixed $conditions Array or string of conditions, or any value.
2388
 * @param boolean $quoteValues If true, values should be quoted
2389
 * @param boolean $where If true, "WHERE " will be prepended to the return value
2390
 * @param Model $model A reference to the Model instance making the query
2391
 * @return string SQL fragment
2392
 */
2393
	public function conditions($conditions, $quoteValues = true, $where = true, $model = null) {
2394
		$clause = $out = '';
2395
2396
		if ($where) {
2397
			$clause = ' WHERE ';
2398
		}
2399
2400
		if (is_array($conditions) && !empty($conditions)) {
2401
			$out = $this->conditionKeysToString($conditions, $quoteValues, $model);
2402
2403
			if (empty($out)) {
2404
				return $clause . ' 1 = 1';
2405
			}
2406
			return $clause . implode(' AND ', $out);
2407
		}
2408
		if (is_bool($conditions)) {
2409
			return $clause . (int)$conditions . ' = 1';
2410
		}
2411
2412
		if (empty($conditions) || trim($conditions) === '') {
2413
			return $clause . '1 = 1';
2414
		}
2415
		$clauses = '/^WHERE\\x20|^GROUP\\x20BY\\x20|^HAVING\\x20|^ORDER\\x20BY\\x20/i';
2416
2417
		if (preg_match($clauses, $conditions)) {
2418
			$clause = '';
2419
		}
2420
		$conditions = $this->_quoteFields($conditions);
2421
		return $clause . $conditions;
2422
	}
2423
2424
/**
2425
 * Creates a WHERE clause by parsing given conditions array. Used by DboSource::conditions().
2426
 *
2427
 * @param array $conditions Array or string of conditions
2428
 * @param boolean $quoteValues If true, values should be quoted
2429
 * @param Model $model A reference to the Model instance making the query
2430
 * @return string SQL fragment
2431
 */
2432
	public function conditionKeysToString($conditions, $quoteValues = true, $model = null) {
2433
		$out = array();
2434
		$data = $columnType = null;
2435
		$bool = array('and', 'or', 'not', 'and not', 'or not', 'xor', '||', '&&');
2436
2437
		foreach ($conditions as $key => $value) {
2438
			$join = ' AND ';
2439
			$not = null;
2440
2441
			if (is_array($value)) {
2442
				$valueInsert = (
2443
					!empty($value) &&
2444
					(substr_count($key, '?') === count($value) || substr_count($key, ':') === count($value))
2445
				);
2446
			}
2447
2448
			if (is_numeric($key) && empty($value)) {
2449
				continue;
2450
			} elseif (is_numeric($key) && is_string($value)) {
2451
				$out[] = $this->_quoteFields($value);
2452
			} elseif ((is_numeric($key) && is_array($value)) || in_array(strtolower(trim($key)), $bool)) {
2453
				if (in_array(strtolower(trim($key)), $bool)) {
2454
					$join = ' ' . strtoupper($key) . ' ';
2455
				} else {
2456
					$key = $join;
2457
				}
2458
				$value = $this->conditionKeysToString($value, $quoteValues, $model);
2459
2460
				if (strpos($join, 'NOT') !== false) {
2461
					if (strtoupper(trim($key)) === 'NOT') {
2462
						$key = 'AND ' . trim($key);
2463
					}
2464
					$not = 'NOT ';
2465
				}
2466
2467
				if (empty($value)) {
2468
					continue;
2469
				}
2470
2471
				if (empty($value[1])) {
2472
					if ($not) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $not of type string|null is loosely compared to true; 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...
2473
						$out[] = $not . '(' . $value[0] . ')';
2474
					} else {
2475
						$out[] = $value[0];
2476
					}
2477
				} else {
2478
					$out[] = '(' . $not . '(' . implode(') ' . strtoupper($key) . ' (', $value) . '))';
2479
				}
2480
			} else {
2481
				if (is_object($value) && isset($value->type)) {
2482
					if ($value->type === 'identifier') {
2483
						$data .= $this->name($key) . ' = ' . $this->name($value->value);
2484
					} elseif ($value->type === 'expression') {
2485
						if (is_numeric($key)) {
2486
							$data .= $value->value;
2487
						} else {
2488
							$data .= $this->name($key) . ' = ' . $value->value;
2489
						}
2490
					}
2491
				} elseif (is_array($value) && !empty($value) && !$valueInsert) {
0 ignored issues
show
Bug introduced by
The variable $valueInsert does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2492
					$keys = array_keys($value);
2493
					if ($keys === array_values($keys)) {
2494
						$count = count($value);
2495
						if ($count === 1 && !preg_match('/\s+(?:NOT|\!=)$/', $key)) {
2496
							$data = $this->_quoteFields($key) . ' = (';
2497
							if ($quoteValues) {
2498
								if (is_object($model)) {
2499
									$columnType = $model->getColumnType($key);
2500
								}
2501
								$data .= implode(', ', $this->value($value, $columnType));
2502
							}
2503
							$data .= ')';
2504
						} else {
2505
							$data = $this->_parseKey($model, $key, $value);
0 ignored issues
show
Bug introduced by
It seems like $model defined by parameter $model on line 2432 can be null; however, DboSource::_parseKey() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
2506
						}
2507
					} else {
2508
						$ret = $this->conditionKeysToString($value, $quoteValues, $model);
2509
						if (count($ret) > 1) {
2510
							$data = '(' . implode(') AND (', $ret) . ')';
2511
						} elseif (isset($ret[0])) {
2512
							$data = $ret[0];
2513
						}
2514
					}
2515
				} elseif (is_numeric($key) && !empty($value)) {
2516
					$data = $this->_quoteFields($value);
2517
				} else {
2518
					$data = $this->_parseKey($model, trim($key), $value);
0 ignored issues
show
Bug introduced by
It seems like $model defined by parameter $model on line 2432 can be null; however, DboSource::_parseKey() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
2519
				}
2520
2521
				if ($data) {
2522
					$out[] = $data;
2523
					$data = null;
2524
				}
2525
			}
2526
		}
2527
		return $out;
2528
	}
2529
2530
/**
2531
 * Extracts a Model.field identifier and an SQL condition operator from a string, formats
2532
 * and inserts values, and composes them into an SQL snippet.
2533
 *
2534
 * @param Model $model Model object initiating the query
2535
 * @param string $key An SQL key snippet containing a field and optional SQL operator
2536
 * @param mixed $value The value(s) to be inserted in the string
2537
 * @return string
2538
 */
2539
	protected function _parseKey($model, $key, $value) {
2540
		$operatorMatch = '/^(((' . implode(')|(', $this->_sqlOps);
2541
		$operatorMatch .= ')\\x20?)|<[>=]?(?![^>]+>)\\x20?|[>=!]{1,3}(?!<)\\x20?)/is';
2542
		$bound = (strpos($key, '?') !== false || (is_array($value) && strpos($key, ':') !== false));
2543
2544
		if (strpos($key, ' ') === false) {
2545
			$operator = '=';
2546
		} else {
2547
			list($key, $operator) = explode(' ', trim($key), 2);
2548
2549
			if (!preg_match($operatorMatch, trim($operator)) && strpos($operator, ' ') !== false) {
2550
				$key = $key . ' ' . $operator;
2551
				$split = strrpos($key, ' ');
2552
				$operator = substr($key, $split);
2553
				$key = substr($key, 0, $split);
2554
			}
2555
		}
2556
2557
		$virtual = false;
2558
		if (is_object($model) && $model->isVirtualField($key)) {
2559
			$key = $this->_quoteFields($model->getVirtualField($key));
2560
			$virtual = true;
2561
		}
2562
2563
		$type = is_object($model) ? $model->getColumnType($key) : null;
2564
		$null = $value === null || (is_array($value) && empty($value));
2565
2566
		if (strtolower($operator) === 'not') {
2567
			$data = $this->conditionKeysToString(
2568
				array($operator => array($key => $value)), true, $model
2569
			);
2570
			return $data[0];
2571
		}
2572
2573
		$value = $this->value($value, $type);
2574
2575
		if (!$virtual && $key !== '?') {
2576
			$isKey = (
2577
				strpos($key, '(') !== false ||
2578
				strpos($key, ')') !== false ||
2579
				strpos($key, '|') !== false
2580
			);
2581
			$key = $isKey ? $this->_quoteFields($key) : $this->name($key);
2582
		}
2583
2584
		if ($bound) {
2585
			return String::insert($key . ' ' . trim($operator), $value);
2586
		}
2587
2588
		if (!preg_match($operatorMatch, trim($operator))) {
2589
			$operator .= is_array($value) ? ' IN' : ' =';
2590
		}
2591
		$operator = trim($operator);
2592
2593
		if (is_array($value)) {
2594
			$value = implode(', ', $value);
2595
2596
			switch ($operator) {
2597
				case '=':
2598
					$operator = 'IN';
2599
					break;
2600
				case '!=':
2601
				case '<>':
2602
					$operator = 'NOT IN';
2603
					break;
2604
			}
2605
			$value = "({$value})";
2606
		} elseif ($null || $value === 'NULL') {
2607
			switch ($operator) {
2608
				case '=':
2609
					$operator = 'IS';
2610
					break;
2611
				case '!=':
2612
				case '<>':
2613
					$operator = 'IS NOT';
2614
					break;
2615
			}
2616
		}
2617
		if ($virtual) {
2618
			return "({$key}) {$operator} {$value}";
2619
		}
2620
		return "{$key} {$operator} {$value}";
2621
	}
2622
2623
/**
2624
 * Quotes Model.fields
2625
 *
2626
 * @param string $conditions
2627
 * @return string or false if no match
2628
 */
2629
	protected function _quoteFields($conditions) {
2630
		$start = $end = null;
2631
		$original = $conditions;
2632
2633
		if (!empty($this->startQuote)) {
2634
			$start = preg_quote($this->startQuote);
2635
		}
2636
		if (!empty($this->endQuote)) {
2637
			$end = preg_quote($this->endQuote);
2638
		}
2639
		$conditions = str_replace(array($start, $end), '', $conditions);
2640
		$conditions = preg_replace_callback(
2641
			'/(?:[\'\"][^\'\"\\\]*(?:\\\.[^\'\"\\\]*)*[\'\"])|([a-z0-9_][a-z0-9\\-_]*\\.[a-z0-9_][a-z0-9_\\-]*)/i',
2642
			array(&$this, '_quoteMatchedField'),
2643
			$conditions
2644
		);
2645
		if ($conditions !== null) {
2646
			return $conditions;
2647
		}
2648
		return $original;
2649
	}
2650
2651
/**
2652
 * Auxiliary function to quote matches `Model.fields` from a preg_replace_callback call
2653
 *
2654
 * @param string $match matched string
2655
 * @return string quoted string
2656
 */
2657
	protected function _quoteMatchedField($match) {
2658
		if (is_numeric($match[0])) {
2659
			return $match[0];
2660
		}
2661
		return $this->name($match[0]);
2662
	}
2663
2664
/**
2665
 * Returns a limit statement in the correct format for the particular database.
2666
 *
2667
 * @param integer $limit Limit of results returned
2668
 * @param integer $offset Offset from which to start results
2669
 * @return string SQL limit/offset statement
2670
 */
2671 View Code Duplication
	public function limit($limit, $offset = null) {
2672
		if ($limit) {
2673
			$rt = ' LIMIT';
2674
2675
			if ($offset) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $offset of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2676
				$rt .= sprintf(' %u,', $offset);
2677
			}
2678
2679
			$rt .= sprintf(' %u', $limit);
2680
			return $rt;
2681
		}
2682
		return null;
2683
	}
2684
2685
/**
2686
 * Returns an ORDER BY clause as a string.
2687
 *
2688
 * @param array|string $keys Field reference, as a key (i.e. Post.title)
2689
 * @param string $direction Direction (ASC or DESC)
2690
 * @param Model $model model reference (used to look for virtual field)
2691
 * @return string ORDER BY clause
2692
 */
2693
	public function order($keys, $direction = 'ASC', $model = null) {
2694
		if (!is_array($keys)) {
2695
			$keys = array($keys);
2696
		}
2697
		$keys = array_filter($keys);
2698
		$result = array();
2699
		while (!empty($keys)) {
2700
			list($key, $dir) = each($keys);
2701
			array_shift($keys);
2702
2703
			if (is_numeric($key)) {
2704
				$key = $dir;
2705
				$dir = $direction;
2706
			}
2707
2708
			if (is_string($key) && strpos($key, ',') !== false && !preg_match('/\(.+\,.+\)/', $key)) {
2709
				$key = array_map('trim', explode(',', $key));
2710
			}
2711
			if (is_array($key)) {
2712
				//Flatten the array
2713
				$key = array_reverse($key, true);
2714
				foreach ($key as $k => $v) {
2715
					if (is_numeric($k)) {
2716
						array_unshift($keys, $v);
2717
					} else {
2718
						$keys = array($k => $v) + $keys;
2719
					}
2720
				}
2721
				continue;
2722
			} elseif (is_object($key) && isset($key->type) && $key->type === 'expression') {
2723
				$result[] = $key->value;
2724
				continue;
2725
			}
2726
2727
			if (preg_match('/\\x20(ASC|DESC).*/i', $key, $_dir)) {
2728
				$dir = $_dir[0];
2729
				$key = preg_replace('/\\x20(ASC|DESC).*/i', '', $key);
2730
			}
2731
2732
			$key = trim($key);
2733
2734
			if (is_object($model) && $model->isVirtualField($key)) {
2735
				$key = '(' . $this->_quoteFields($model->getVirtualField($key)) . ')';
2736
			}
2737
			list($alias) = pluginSplit($key);
2738
			if (is_object($model) && $alias !== $model->alias && is_object($model->{$alias}) && $model->{$alias}->isVirtualField($key)) {
2739
				$key = '(' . $this->_quoteFields($model->{$alias}->getVirtualField($key)) . ')';
2740
			}
2741
2742
			if (strpos($key, '.')) {
2743
				$key = preg_replace_callback('/([a-zA-Z0-9_-]{1,})\\.([a-zA-Z0-9_-]{1,})/', array(&$this, '_quoteMatchedField'), $key);
2744
			}
2745
			if (!preg_match('/\s/', $key) && strpos($key, '.') === false) {
2746
				$key = $this->name($key);
2747
			}
2748
			$key .= ' ' . trim($dir);
2749
			$result[] = $key;
2750
		}
2751
		if (!empty($result)) {
2752
			return ' ORDER BY ' . implode(', ', $result);
2753
		}
2754
		return '';
2755
	}
2756
2757
/**
2758
 * Create a GROUP BY SQL clause
2759
 *
2760
 * @param string $group Group By Condition
2761
 * @param Model $model
2762
 * @return string string condition or null
2763
 */
2764
	public function group($group, $model = null) {
2765
		if ($group) {
2766
			if (!is_array($group)) {
2767
				$group = array($group);
2768
			}
2769
			foreach ($group as $index => $key) {
2770
				if (is_object($model) && $model->isVirtualField($key)) {
2771
					$group[$index] = '(' . $model->getVirtualField($key) . ')';
2772
				}
2773
			}
2774
			$group = implode(', ', $group);
2775
			return ' GROUP BY ' . $this->_quoteFields($group);
2776
		}
2777
		return null;
2778
	}
2779
2780
/**
2781
 * Disconnects database, kills the connection and says the connection is closed.
2782
 *
2783
 * @return void
2784
 */
2785
	public function close() {
2786
		$this->disconnect();
2787
	}
2788
2789
/**
2790
 * Checks if the specified table contains any record matching specified SQL
2791
 *
2792
 * @param Model $Model Model to search
2793
 * @param string $sql SQL WHERE clause (condition only, not the "WHERE" part)
2794
 * @return boolean True if the table has a matching record, else false
2795
 */
2796
	public function hasAny(Model $Model, $sql) {
2797
		$sql = $this->conditions($sql);
2798
		$table = $this->fullTableName($Model);
2799
		$alias = $this->alias . $this->name($Model->alias);
2800
		$where = $sql ? "{$sql}" : ' WHERE 1 = 1';
2801
		$id = $Model->escapeField();
2802
2803
		$out = $this->fetchRow("SELECT COUNT({$id}) {$this->alias}count FROM {$table} {$alias}{$where}");
2804
2805
		if (is_array($out)) {
2806
			return $out[0]['count'];
2807
		}
2808
		return false;
2809
	}
2810
2811
/**
2812
 * Gets the length of a database-native column description, or null if no length
2813
 *
2814
 * @param string $real Real database-layer column type (i.e. "varchar(255)")
2815
 * @return mixed An integer or string representing the length of the column, or null for unknown length.
2816
 */
2817
	public function length($real) {
2818
		if (!preg_match_all('/([\w\s]+)(?:\((\d+)(?:,(\d+))?\))?(\sunsigned)?(\szerofill)?/', $real, $result)) {
2819
			$col = str_replace(array(')', 'unsigned'), '', $real);
2820
			$limit = null;
2821
2822 View Code Duplication
			if (strpos($col, '(') !== false) {
2823
				list($col, $limit) = explode('(', $col);
0 ignored issues
show
Unused Code introduced by
The assignment to $col is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
2824
			}
2825
			if ($limit !== null) {
2826
				return intval($limit);
2827
			}
2828
			return null;
2829
		}
2830
2831
		$types = array(
2832
			'int' => 1, 'tinyint' => 1, 'smallint' => 1, 'mediumint' => 1, 'integer' => 1, 'bigint' => 1
2833
		);
2834
2835
		list($real, $type, $length, $offset, $sign) = $result;
2836
		$typeArr = $type;
2837
		$type = $type[0];
2838
		$length = $length[0];
2839
		$offset = $offset[0];
2840
2841
		$isFloat = in_array($type, array('dec', 'decimal', 'float', 'numeric', 'double'));
2842
		if ($isFloat && $offset) {
2843
			return $length . ',' . $offset;
2844
		}
2845
2846
		if (($real[0] == $type) && (count($real) === 1)) {
2847
			return null;
2848
		}
2849
2850
		if (isset($types[$type])) {
2851
			$length += $types[$type];
2852
			if (!empty($sign)) {
2853
				$length--;
2854
			}
2855
		} elseif (in_array($type, array('enum', 'set'))) {
2856
			$length = 0;
2857
			foreach ($typeArr as $key => $enumValue) {
2858
				if ($key === 0) {
2859
					continue;
2860
				}
2861
				$tmpLength = strlen($enumValue);
2862
				if ($tmpLength > $length) {
2863
					$length = $tmpLength;
2864
				}
2865
			}
2866
		}
2867
		return intval($length);
2868
	}
2869
2870
/**
2871
 * Translates between PHP boolean values and Database (faked) boolean values
2872
 *
2873
 * @param mixed $data Value to be translated
2874
 * @param boolean $quote
2875
 * @return string|boolean Converted boolean value
2876
 */
2877
	public function boolean($data, $quote = false) {
2878
		if ($quote) {
2879
			return !empty($data) ? '1' : '0';
2880
		}
2881
		return !empty($data);
2882
	}
2883
2884
/**
2885
 * Inserts multiple values into a table
2886
 *
2887
 * @param string $table The table being inserted into.
2888
 * @param array $fields The array of field/column names being inserted.
2889
 * @param array $values The array of values to insert. The values should
2890
 *   be an array of rows. Each row should have values keyed by the column name.
2891
 *   Each row must have the values in the same order as $fields.
2892
 * @return boolean
2893
 */
2894
	public function insertMulti($table, $fields, $values) {
2895
		$table = $this->fullTableName($table);
2896
		$holder = implode(',', array_fill(0, count($fields), '?'));
2897
		$fields = implode(', ', array_map(array(&$this, 'name'), $fields));
2898
2899
		$pdoMap = array(
2900
			'integer' => PDO::PARAM_INT,
2901
			'float' => PDO::PARAM_STR,
2902
			'boolean' => PDO::PARAM_BOOL,
2903
			'string' => PDO::PARAM_STR,
2904
			'text' => PDO::PARAM_STR
2905
		);
2906
		$columnMap = array();
2907
2908
		$sql = "INSERT INTO {$table} ({$fields}) VALUES ({$holder})";
2909
		$statement = $this->_connection->prepare($sql);
0 ignored issues
show
Bug introduced by
The method prepare cannot be called on $this->_connection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
2910
		$this->begin();
2911
2912
		foreach ($values[key($values)] as $key => $val) {
2913
			$type = $this->introspectType($val);
2914
			$columnMap[$key] = $pdoMap[$type];
2915
		}
2916
2917
		foreach ($values as $value) {
2918
			$i = 1;
2919
			foreach ($value as $col => $val) {
2920
				$statement->bindValue($i, $val, $columnMap[$col]);
2921
				$i += 1;
2922
			}
2923
			$statement->execute();
2924
			$statement->closeCursor();
2925
2926
			if ($this->fullDebug) {
2927
				$this->logQuery($sql, $value);
2928
			}
2929
		}
2930
		return $this->commit();
2931
	}
2932
2933
/**
2934
 * Reset a sequence based on the MAX() value of $column. Useful
2935
 * for resetting sequences after using insertMulti().
2936
 *
2937
 * This method should be implemented by datasources that require sequences to be used.
2938
 *
2939
 * @param string $table The name of the table to update.
2940
 * @param string $column The column to use when resetting the sequence value.
2941
 * @return boolean|void success.
2942
 */
2943
	public function resetSequence($table, $column) {
2944
	}
2945
2946
/**
2947
 * Returns an array of the indexes in given datasource name.
2948
 *
2949
 * @param string $model Name of model to inspect
2950
 * @return array Fields in table. Keys are column and unique
2951
 */
2952
	public function index($model) {
2953
		return array();
2954
	}
2955
2956
/**
2957
 * Generate a database-native schema for the given Schema object
2958
 *
2959
 * @param CakeSchema $schema An instance of a subclass of CakeSchema
2960
 * @param string $tableName Optional. If specified only the table name given will be generated.
2961
 *   Otherwise, all tables defined in the schema are generated.
2962
 * @return string
2963
 */
2964
	public function createSchema($schema, $tableName = null) {
2965
		if (!$schema instanceof CakeSchema) {
2966
			trigger_error(__d('cake_dev', 'Invalid schema object'), E_USER_WARNING);
2967
			return null;
2968
		}
2969
		$out = '';
2970
2971
		foreach ($schema->tables as $curTable => $columns) {
2972
			if (!$tableName || $tableName === $curTable) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tableName 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...
2973
				$cols = $indexes = $tableParameters = array();
2974
				$primary = null;
2975
				$table = $this->fullTableName($curTable);
2976
2977
				$primaryCount = 0;
2978
				foreach ($columns as $col) {
2979
					if (isset($col['key']) && $col['key'] === 'primary') {
2980
						$primaryCount++;
2981
					}
2982
				}
2983
2984
				foreach ($columns as $name => $col) {
2985
					if (is_string($col)) {
2986
						$col = array('type' => $col);
2987
					}
2988
					$isPrimary = isset($col['key']) && $col['key'] === 'primary';
2989
					// Multi-column primary keys are not supported.
2990
					if ($isPrimary && $primaryCount > 1) {
2991
						unset($col['key']);
2992
						$isPrimary = false;
2993
					}
2994
					if ($isPrimary) {
2995
						$primary = $name;
2996
					}
2997
					if ($name !== 'indexes' && $name !== 'tableParameters') {
2998
						$col['name'] = $name;
2999
						if (!isset($col['type'])) {
3000
							$col['type'] = 'string';
3001
						}
3002
						$cols[] = $this->buildColumn($col);
3003
					} elseif ($name === 'indexes') {
3004
						$indexes = array_merge($indexes, $this->buildIndex($col, $table));
3005
					} elseif ($name === 'tableParameters') {
3006
						$tableParameters = array_merge($tableParameters, $this->buildTableParameters($col, $table));
3007
					}
3008
				}
3009
				if (!isset($columns['indexes']['PRIMARY']) && !empty($primary)) {
3010
					$col = array('PRIMARY' => array('column' => $primary, 'unique' => 1));
3011
					$indexes = array_merge($indexes, $this->buildIndex($col, $table));
3012
				}
3013
				$columns = $cols;
3014
				$out .= $this->renderStatement('schema', compact('table', 'columns', 'indexes', 'tableParameters')) . "\n\n";
3015
			}
3016
		}
3017
		return $out;
3018
	}
3019
3020
/**
3021
 * Generate a alter syntax from	CakeSchema::compare()
3022
 *
3023
 * @param mixed $compare
3024
 * @param string $table
3025
 * @return boolean
3026
 */
3027
	public function alterSchema($compare, $table = null) {
3028
		return false;
3029
	}
3030
3031
/**
3032
 * Generate a "drop table" statement for the given Schema object
3033
 *
3034
 * @param CakeSchema $schema An instance of a subclass of CakeSchema
3035
 * @param string $table Optional. If specified only the table name given will be generated.
3036
 *   Otherwise, all tables defined in the schema are generated.
3037
 * @return string
3038
 */
3039
	public function dropSchema(CakeSchema $schema, $table = null) {
3040
		$out = '';
3041
3042
		if ($table && array_key_exists($table, $schema->tables)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $table of type string|null is loosely compared to true; 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...
3043
			return $this->_dropTable($table) . "\n";
3044
		} elseif ($table) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $table of type string|null is loosely compared to true; 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...
3045
			return $out;
3046
		}
3047
3048
		foreach (array_keys($schema->tables) as $curTable) {
3049
			$out .= $this->_dropTable($curTable) . "\n";
3050
		}
3051
		return $out;
3052
	}
3053
3054
/**
3055
 * Generate a "drop table" statement for a single table
3056
 *
3057
 * @param type $table Name of the table to drop
3058
 * @return string Drop table SQL statement
3059
 */
3060
	protected function _dropTable($table) {
3061
		return 'DROP TABLE ' . $this->fullTableName($table) . ";";
3062
	}
3063
3064
/**
3065
 * Generate a database-native column schema string
3066
 *
3067
 * @param array $column An array structured like the following: array('name' => 'value', 'type' => 'value'[, options]),
3068
 *   where options can be 'default', 'length', or 'key'.
3069
 * @return string
3070
 */
3071
	public function buildColumn($column) {
3072
		$name = $type = null;
3073
		extract(array_merge(array('null' => true), $column));
3074
3075 View Code Duplication
		if (empty($name) || empty($type)) {
3076
			trigger_error(__d('cake_dev', 'Column name or type not defined in schema'), E_USER_WARNING);
3077
			return null;
3078
		}
3079
3080 View Code Duplication
		if (!isset($this->columns[$type])) {
3081
			trigger_error(__d('cake_dev', 'Column type %s does not exist', $type), E_USER_WARNING);
3082
			return null;
3083
		}
3084
3085
		$real = $this->columns[$type];
3086
		$out = $this->name($name) . ' ' . $real['name'];
3087
3088
		if (isset($column['length'])) {
3089
			$length = $column['length'];
3090
		} elseif (isset($column['limit'])) {
3091
			$length = $column['limit'];
3092
		} elseif (isset($real['length'])) {
3093
			$length = $real['length'];
3094
		} elseif (isset($real['limit'])) {
3095
			$length = $real['limit'];
3096
		}
3097
		if (isset($length)) {
3098
			$out .= '(' . $length . ')';
3099
		}
3100
3101
		if (($column['type'] === 'integer' || $column['type'] === 'float') && isset($column['default']) && $column['default'] === '') {
3102
			$column['default'] = null;
3103
		}
3104
		$out = $this->_buildFieldParameters($out, $column, 'beforeDefault');
3105
3106
		if (isset($column['key']) && $column['key'] === 'primary' && ($type === 'integer' || $type === 'biginteger')) {
3107
			$out .= ' ' . $this->columns['primary_key']['name'];
3108
		} elseif (isset($column['key']) && $column['key'] === 'primary') {
3109
			$out .= ' NOT NULL';
3110
		} elseif (isset($column['default']) && isset($column['null']) && $column['null'] === false) {
3111
			$out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' NOT NULL';
3112
		} elseif (isset($column['default'])) {
3113
			$out .= ' DEFAULT ' . $this->value($column['default'], $type);
3114
		} elseif ($type !== 'timestamp' && !empty($column['null'])) {
3115
			$out .= ' DEFAULT NULL';
3116
		} elseif ($type === 'timestamp' && !empty($column['null'])) {
3117
			$out .= ' NULL';
3118
		} elseif (isset($column['null']) && $column['null'] === false) {
3119
			$out .= ' NOT NULL';
3120
		}
3121
		if ($type === 'timestamp' && isset($column['default']) && strtolower($column['default']) === 'current_timestamp') {
3122
			$out = str_replace(array("'CURRENT_TIMESTAMP'", "'current_timestamp'"), 'CURRENT_TIMESTAMP', $out);
3123
		}
3124
		return $this->_buildFieldParameters($out, $column, 'afterDefault');
3125
	}
3126
3127
/**
3128
 * Build the field parameters, in a position
3129
 *
3130
 * @param string $columnString The partially built column string
3131
 * @param array $columnData The array of column data.
3132
 * @param string $position The position type to use. 'beforeDefault' or 'afterDefault' are common
3133
 * @return string a built column with the field parameters added.
3134
 */
3135
	protected function _buildFieldParameters($columnString, $columnData, $position) {
3136
		foreach ($this->fieldParameters as $paramName => $value) {
3137
			if (isset($columnData[$paramName]) && $value['position'] == $position) {
3138
				if (isset($value['options']) && !in_array($columnData[$paramName], $value['options'])) {
3139
					continue;
3140
				}
3141
				$val = $columnData[$paramName];
3142
				if ($value['quote']) {
3143
					$val = $this->value($val);
3144
				}
3145
				$columnString .= ' ' . $value['value'] . $value['join'] . $val;
3146
			}
3147
		}
3148
		return $columnString;
3149
	}
3150
3151
/**
3152
 * Format indexes for create table.
3153
 *
3154
 * @param array $indexes
3155
 * @param string $table
3156
 * @return array
3157
 */
3158
	public function buildIndex($indexes, $table = null) {
3159
		$join = array();
3160
		foreach ($indexes as $name => $value) {
3161
			$out = '';
3162 View Code Duplication
			if ($name === 'PRIMARY') {
3163
				$out .= 'PRIMARY ';
3164
				$name = null;
3165
			} else {
3166
				if (!empty($value['unique'])) {
3167
					$out .= 'UNIQUE ';
3168
				}
3169
				$name = $this->startQuote . $name . $this->endQuote;
3170
			}
3171 View Code Duplication
			if (is_array($value['column'])) {
3172
				$out .= 'KEY ' . $name . ' (' . implode(', ', array_map(array(&$this, 'name'), $value['column'])) . ')';
3173
			} else {
3174
				$out .= 'KEY ' . $name . ' (' . $this->name($value['column']) . ')';
3175
			}
3176
			$join[] = $out;
3177
		}
3178
		return $join;
3179
	}
3180
3181
/**
3182
 * Read additional table parameters
3183
 *
3184
 * @param string $name
3185
 * @return array
3186
 */
3187
	public function readTableParameters($name) {
3188
		$parameters = array();
3189
		if (method_exists($this, 'listDetailedSources')) {
3190
			$currentTableDetails = $this->listDetailedSources($name);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class DboSource as the method listDetailedSources() does only exist in the following sub-classes of DboSource: Mysql. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
3191
			foreach ($this->tableParameters as $paramName => $parameter) {
3192
				if (!empty($parameter['column']) && !empty($currentTableDetails[$parameter['column']])) {
3193
					$parameters[$paramName] = $currentTableDetails[$parameter['column']];
3194
				}
3195
			}
3196
		}
3197
		return $parameters;
3198
	}
3199
3200
/**
3201
 * Format parameters for create table
3202
 *
3203
 * @param array $parameters
3204
 * @param string $table
3205
 * @return array
3206
 */
3207
	public function buildTableParameters($parameters, $table = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $table 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...
3208
		$result = array();
3209
		foreach ($parameters as $name => $value) {
3210
			if (isset($this->tableParameters[$name])) {
3211
				if ($this->tableParameters[$name]['quote']) {
3212
					$value = $this->value($value);
3213
				}
3214
				$result[] = $this->tableParameters[$name]['value'] . $this->tableParameters[$name]['join'] . $value;
3215
			}
3216
		}
3217
		return $result;
3218
	}
3219
3220
/**
3221
 * Guesses the data type of an array
3222
 *
3223
 * @param string $value
3224
 * @return void
3225
 */
3226
	public function introspectType($value) {
3227
		if (!is_array($value)) {
3228
			if (is_bool($value)) {
3229
				return 'boolean';
3230
			}
3231
			if (is_float($value) && floatval($value) === $value) {
3232
				return 'float';
3233
			}
3234
			if (is_int($value) && intval($value) === $value) {
3235
				return 'integer';
3236
			}
3237
			if (is_string($value) && strlen($value) > 255) {
3238
				return 'text';
3239
			}
3240
			return 'string';
3241
		}
3242
3243
		$isAllFloat = $isAllInt = true;
3244
		$containsInt = $containsString = false;
3245
		foreach ($value as $valElement) {
3246
			$valElement = trim($valElement);
3247
			if (!is_float($valElement) && !preg_match('/^[\d]+\.[\d]+$/', $valElement)) {
3248
				$isAllFloat = false;
3249
			} else {
3250
				continue;
3251
			}
3252
			if (!is_int($valElement) && !preg_match('/^[\d]+$/', $valElement)) {
3253
				$isAllInt = false;
3254
			} else {
3255
				$containsInt = true;
3256
				continue;
3257
			}
3258
			$containsString = true;
3259
		}
3260
3261
		if ($isAllFloat) {
3262
			return 'float';
3263
		}
3264
		if ($isAllInt) {
3265
			return 'integer';
3266
		}
3267
3268
		if ($containsInt && !$containsString) {
3269
			return 'integer';
3270
		}
3271
		return 'string';
3272
	}
3273
3274
/**
3275
 * Writes a new key for the in memory sql query cache
3276
 *
3277
 * @param string $sql SQL query
3278
 * @param mixed $data result of $sql query
3279
 * @param array $params query params bound as values
3280
 * @return void
3281
 */
3282
	protected function _writeQueryCache($sql, $data, $params = array()) {
3283
		if (preg_match('/^\s*select/i', $sql)) {
3284
			$this->_queryCache[$sql][serialize($params)] = $data;
3285
		}
3286
	}
3287
3288
/**
3289
 * Returns the result for a sql query if it is already cached
3290
 *
3291
 * @param string $sql SQL query
3292
 * @param array $params query params bound as values
3293
 * @return mixed results for query if it is cached, false otherwise
3294
 */
3295
	public function getQueryCache($sql, $params = array()) {
3296
		if (isset($this->_queryCache[$sql]) && preg_match('/^\s*select/i', $sql)) {
3297
			$serialized = serialize($params);
3298
			if (isset($this->_queryCache[$sql][$serialized])) {
3299
				return $this->_queryCache[$sql][$serialized];
3300
			}
3301
		}
3302
		return false;
3303
	}
3304
3305
/**
3306
 * Used for storing in cache the results of the in-memory methodCache
3307
 */
3308
	public function __destruct() {
3309
		if ($this->_methodCacheChange) {
3310
			Cache::write('method_cache', self::$methodCache, '_cake_core_');
3311
		}
3312
	}
3313
3314
}
3315