Passed
Push — master ( 596a36...36fff2 )
by Fabio
05:01
created

TActiveRecordGateway::findRecordsBySql()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
1
<?php
2
/**
3
 * TActiveRecordGateway class file.
4
 *
5
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 * @package Prado\Data\ActiveRecord
9
 */
10
11
namespace Prado\Data\ActiveRecord;
12
13
use Prado\Data\ActiveRecord\Exceptions\TActiveRecordException;
14
use Prado\Data\ActiveRecord\Relations\TActiveRecordRelationContext;
15
use Prado\Data\Common\TDbMetaData;
16
use Prado\Data\Common\TDbTableColumn;
17
use Prado\Data\DataGateway\TDataGatewayCommand;
18
use Prado\Data\DataGateway\TSqlCriteria;
19
use Prado\Data\TDbConnection;
20
use Prado\Prado;
21
use ReflectionClass;
22
23
/**
24
 * TActiveRecordGateway excutes the SQL command queries and returns the data
25
 * record as arrays (for most finder methods).
26
 *
27
 * @author Wei Zhuo <weizho[at]gmail[dot]com>
28
 * @package Prado\Data\ActiveRecord
29
 * @since 3.1
30
 */
31
class TActiveRecordGateway extends \Prado\TComponent
32
{
33
	const DEFAULT_DATA_GATEWAY_CLASS = '\Prado\Data\DataGateway\TDataGatewayCommand';
34
35
	/**
36
	 * Defaults to {@link TActiveRecordGateway::DEFAULT_GATEWAY_CLASS DEFAULT_GATEWAY_CLASS}
37
	 * @var string
38
	 */
39
	private $_dataGatewayClass = self::DEFAULT_DATA_GATEWAY_CLASS;
40
	private $_manager;
41
	private $_tables = []; //table cache
42
	private $_meta = []; //meta data cache.
43
	private $_commandBuilders = [];
44
	private $_currentRecord;
45
46
	/**
47
	 * Constant name for specifying optional table name in TActiveRecord.
48
	 */
49
	const TABLE_CONST = 'TABLE';
50
	/**
51
	 * Method name for returning optional table name in in TActiveRecord
52
	 */
53
	const TABLE_METHOD = 'table';
54
55
	/**
56
	 * Record gateway constructor.
57
	 * @param TActiveRecordManager $manager
58
	 */
59 1
	public function __construct(TActiveRecordManager $manager)
60
	{
61 1
		$this->_manager = $manager;
62 1
	}
63
64
	/**
65
	 * @return TActiveRecordManager record manager.
66
	 */
67 8
	protected function getManager()
68
	{
69 8
		return $this->_manager;
70
	}
71
72
	/**
73
	 * Gets the table name from the 'TABLE' constant of the active record
74
	 * class if defined, otherwise use the class name as table name.
75
	 * @param TActiveRecord $record active record instance
76
	 * @return string table name for the given record class.
77
	 */
78 39
	protected function getRecordTableName(TActiveRecord $record)
79
	{
80 39
		$class = new ReflectionClass($record);
81 39
		if ($class->hasConstant(self::TABLE_CONST)) {
82 29
			$value = $class->getConstant(self::TABLE_CONST);
83 29
			if (empty($value)) {
84
				throw new TActiveRecordException(
85
					'ar_invalid_tablename_property',
86
					get_class($record),
87
					self::TABLE_CONST
88
				);
89
			}
90 29
			return $value;
91 10
		} elseif ($class->hasMethod(self::TABLE_METHOD)) {
92
			$value = $record->{self::TABLE_METHOD}();
93
			if (empty($value)) {
94
				throw new TActiveRecordException(
95
					'ar_invalid_tablename_method',
96
					get_class($record),
97
					self::TABLE_METHOD
98
				);
99
			}
100
			return $value;
101
		} else {
102 10
			return strtolower(get_class($record));
103
		}
104
	}
105
106
	/**
107
	 * Returns table information, trys the application cache first.
108
	 * @param TActiveRecord $record
109
	 * @return TDbTableInfo table information.
0 ignored issues
show
Bug introduced by
The type Prado\Data\ActiveRecord\TDbTableInfo was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
110
	 */
111 39
	public function getRecordTableInfo(TActiveRecord $record)
112
	{
113 39
		$tableName = $this->getRecordTableName($record);
114 39
		return $this->getTableInfo($record->getDbConnection(), $tableName);
115
	}
116
117
	/**
118
	 * Returns table information for table in the database connection.
119
	 * @param TDbConnection $connection database connection
120
	 * @param string $tableName table name
121
	 * @return TDbTableInfo table details.
122
	 */
123 39
	public function getTableInfo(TDbConnection $connection, $tableName)
124
	{
125 39
		$connStr = $connection->getConnectionString();
126 39
		$key = $connStr . $tableName;
127 39
		if (!isset($this->_tables[$key])) {
128
			//call this first to ensure that unserializing the cache
129
			//will find the correct driver dependent classes.
130 8
			if (!isset($this->_meta[$connStr])) {
131 3
				$this->_meta[$connStr] = TDbMetaData::getInstance($connection);
132
			}
133
134 8
			$tableInfo = null;
135 8
			if (($cache = $this->getManager()->getCache()) !== null) {
136
				$tableInfo = $cache->get($key);
137
			}
138 8
			if (empty($tableInfo)) {
139 8
				$tableInfo = $this->_meta[$connStr]->getTableInfo($tableName);
140 8
				if ($cache !== null) {
141
					$cache->set($key, $tableInfo);
142
				}
143
			}
144 8
			$this->_tables[$key] = $tableInfo;
145
		}
146 39
		return $this->_tables[$key];
147
	}
148
149
	/**
150
	 * @param TActiveRecord $record
151
	 * @return TDataGatewayCommand
152
	 */
153 39
	public function getCommand(TActiveRecord $record)
154
	{
155 39
		$conn = $record->getDbConnection();
156 39
		$connStr = $conn->getConnectionString();
157 39
		$tableInfo = $this->getRecordTableInfo($record);
158 39
		if (!isset($this->_commandBuilders[$connStr])) {
159 3
			$builder = $tableInfo->createCommandBuilder($record->getDbConnection());
160 3
			$command = Prado::createComponent($this->getDataGatewayClass(), $builder);
161 3
			$command->OnCreateCommand[] = [$this, 'onCreateCommand'];
0 ignored issues
show
Bug Best Practice introduced by
The property OnCreateCommand does not exist on Prado\TComponent. Since you implemented __get, consider adding a @property annotation.
Loading history...
162 3
			$command->OnExecuteCommand[] = [$this, 'onExecuteCommand'];
0 ignored issues
show
Bug Best Practice introduced by
The property OnExecuteCommand does not exist on Prado\TComponent. Since you implemented __get, consider adding a @property annotation.
Loading history...
163 3
			$this->_commandBuilders[$connStr] = $command;
164
		}
165 39
		$this->_commandBuilders[$connStr]->getBuilder()->setTableInfo($tableInfo);
166 39
		$this->_currentRecord = $record;
167 39
		return $this->_commandBuilders[$connStr];
168
	}
169
170
	/**
171
	 * Set implementation class of DataGatewayCommand
172
	 * @param string $value
173
	 */
174
	public function setDataGatewayClass($value)
175
	{
176
		$this->_dataGatewayClass = (string) $value;
177
	}
178
179
	/**
180
	 * @return string the implementation class of DataGatewayCommand. Defaults to {@link TActiveRecordGateway::DEFAULT_DATA_GATEWAY_CLASS DEFAULT_DATA_GATEWAY_CLASS}
181
	 */
182 3
	public function getDataGatewayClass()
183
	{
184 3
		return $this->_dataGatewayClass;
185
	}
186
187
	/**
188
	 * Raised when a command is prepared and parameter binding is completed.
189
	 * The parameter object is TDataGatewayEventParameter of which the
190
	 * {@link TDataGatewayEventParameter::getCommand Command} property can be
191
	 * inspected to obtain the sql query to be executed.
192
	 * This method also raises the OnCreateCommand event on the ActiveRecord
193
	 * object calling this gateway.
194
	 * @param TDataGatewayCommand $sender originator
195
	 * @param TDataGatewayEventParameter $param
0 ignored issues
show
Bug introduced by
The type Prado\Data\ActiveRecord\TDataGatewayEventParameter was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
196
	 */
197 37
	public function onCreateCommand($sender, $param)
198
	{
199 37
		$this->raiseEvent('OnCreateCommand', $this, $param);
200 37
		if ($this->_currentRecord !== null) {
201 37
			$this->_currentRecord->onCreateCommand($param);
202
		}
203 37
	}
204
205
	/**
206
	 * Raised when a command is executed and the result from the database was returned.
207
	 * The parameter object is TDataGatewayResultEventParameter of which the
208
	 * {@link TDataGatewayEventParameter::getResult Result} property contains
209
	 * the data return from the database. The data returned can be changed
210
	 * by setting the {@link TDataGatewayEventParameter::setResult Result} property.
211
	 * This method also raises the OnCreateCommand event on the ActiveRecord
212
	 * object calling this gateway.
213
	 * @param TDataGatewayCommand $sender originator
214
	 * @param TDataGatewayResultEventParameter $param
0 ignored issues
show
Bug introduced by
The type Prado\Data\ActiveRecord\...wayResultEventParameter was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
215
	 */
216 35
	public function onExecuteCommand($sender, $param)
217
	{
218 35
		$this->raiseEvent('OnExecuteCommand', $this, $param);
219 35
		if ($this->_currentRecord !== null) {
220 35
			$this->_currentRecord->onExecuteCommand($param);
221
		}
222 35
	}
223
224
	/**
225
	 * Returns record data matching the given primary key(s). If the table uses
226
	 * composite key, specify the name value pairs as an array.
227
	 * @param TActiveRecord $record active record instance.
228
	 * @param array $keys primary name value pairs
229
	 * @return array record data
230
	 */
231 9
	public function findRecordByPK(TActiveRecord $record, $keys)
232
	{
233 9
		$command = $this->getCommand($record);
234 9
		return $command->findByPk($keys);
235
	}
236
237
	/**
238
	 * Returns records matching the list of given primary keys.
239
	 * @param TActiveRecord $record active record instance.
240
	 * @param array $keys list of primary name value pairs
241
	 * @return array matching data.
242
	 */
243 3
	public function findRecordsByPks(TActiveRecord $record, $keys)
244
	{
245 3
		return $this->getCommand($record)->findAllByPk($keys);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getCommand...rd)->findAllByPk($keys) returns the type Prado\Data\DataGateway\TDbDataReader which is incompatible with the documented return type array.
Loading history...
246
	}
247
248
249
	/**
250
	 * Returns record data matching the given critera. If $iterator is true, it will
251
	 * return multiple rows as TDbDataReader otherwise it returns the <b>first</b> row data.
252
	 * @param TActiveRecord $record active record finder instance.
253
	 * @param TActiveRecordCriteria $criteria search criteria.
254
	 * @param bool $iterator true to return multiple rows as iterator, false returns first row.
255
	 * @return mixed matching data.
256
	 */
257 19
	public function findRecordsByCriteria(TActiveRecord $record, $criteria, $iterator = false)
258
	{
259 19
		$command = $this->getCommand($record);
260 19
		return $iterator ? $command->findAll($criteria) : $command->find($criteria);
261
	}
262
263
	/**
264
	 * Return record data from sql query.
265
	 * @param TActiveRecord $record active record finder instance.
266
	 * @param TActiveRecordCriteria $criteria sql query
267
	 * @return array result.
268
	 */
269 1
	public function findRecordBySql(TActiveRecord $record, $criteria)
270
	{
271 1
		return $this->getCommand($record)->findBySql($criteria);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getCommand...)->findBySql($criteria) returns the type Prado\Data\DataGateway\TDbDataReader which is incompatible with the documented return type array.
Loading history...
272
	}
273
274
	/**
275
	 * Return record data from sql query.
276
	 * @param TActiveRecord $record active record finder instance.
277
	 * @param TActiveRecordCriteria $criteria sql query
278
	 * @return TDbDataReader result iterator.
0 ignored issues
show
Bug introduced by
The type Prado\Data\ActiveRecord\TDbDataReader was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
279
	 */
280
	public function findRecordsBySql(TActiveRecord $record, $criteria)
281
	{
282
		return $this->getCommand($record)->findAllBySql($criteria);
283
	}
284
285 7
	public function findRecordsByIndex(TActiveRecord $record, $criteria, $fields, $values)
286
	{
287 7
		return $this->getCommand($record)->findAllByIndex($criteria, $fields, $values);
288
	}
289
290
	/**
291
	 * Returns the number of records that match the given criteria.
292
	 * @param TActiveRecord $record active record finder instance.
293
	 * @param TActiveRecordCriteria $criteria search criteria
294
	 * @return int number of records.
295
	 */
296 3
	public function countRecords(TActiveRecord $record, $criteria)
297
	{
298 3
		return $this->getCommand($record)->count($criteria);
299
	}
300
301
	/**
302
	 * Insert a new record.
303
	 * @param TActiveRecord $record new record.
304
	 * @return int number of rows affected.
305
	 */
306 2
	public function insert(TActiveRecord $record)
307
	{
308
		//$this->updateAssociatedRecords($record,true);
309 2
		$result = $this->getCommand($record)->insert($this->getInsertValues($record));
310 2
		if ($result) {
311 2
			$this->updatePostInsert($record);
312
		}
313
		//$this->updateAssociatedRecords($record);
314 2
		return $result;
315
	}
316
317
	/**
318
	 * Sets the last insert ID to the corresponding property of the record if available.
319
	 * @param TActiveRecord $record record for insertion
320
	 */
321 2
	protected function updatePostInsert($record)
322
	{
323 2
		$command = $this->getCommand($record);
324 2
		$tableInfo = $command->getTableInfo();
325 2
		foreach ($tableInfo->getColumns() as $name => $column) {
326 2
			if ($column->hasSequence()) {
327 2
				$record->setColumnValue($name, $command->getLastInsertID($column->getSequenceName()));
0 ignored issues
show
Unused Code introduced by
The call to Prado\Data\DataGateway\T...mand::getLastInsertID() has too many arguments starting with $column->getSequenceName(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

327
				$record->setColumnValue($name, $command->/** @scrutinizer ignore-call */ getLastInsertID($column->getSequenceName()));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
328
			}
329
		}
330 2
	}
331
332
	/**
333
	 * @param TActiveRecord $record
334
	 * @return array insert values.
335
	 */
336 2
	protected function getInsertValues(TActiveRecord $record)
337
	{
338 2
		$values = [];
339 2
		$tableInfo = $this->getCommand($record)->getTableInfo();
340 2
		foreach ($tableInfo->getColumns() as $name => $column) {
341 2
			if ($column->getIsExcluded()) {
342
				continue;
343
			}
344 2
			$value = $record->getColumnValue($name);
345 2
			if (!$column->getAllowNull() && $value === null && !$column->hasSequence() && ($column->getDefaultValue() === TDbTableColumn::UNDEFINED_VALUE)) {
346
				throw new TActiveRecordException(
347
					'ar_value_must_not_be_null',
348
					get_class($record),
349
					$tableInfo->getTableFullName(),
350
					$name
351
				);
352
			}
353 2
			if ($value !== null) {
354 2
				$values[$name] = $value;
355
			}
356
		}
357 2
		return $values;
358
	}
359
360
	/**
361
	 * Update the record.
362
	 * @param TActiveRecord $record dirty record.
363
	 * @return int number of rows affected.
364
	 */
365 2
	public function update(TActiveRecord $record)
366
	{
367
		//$this->updateAssociatedRecords($record,true);
368 2
		[$data, $keys] = $this->getUpdateValues($record);
369 2
		$result = $this->getCommand($record)->updateByPk($data, $keys);
370
		//$this->updateAssociatedRecords($record);
371 2
		return $result;
372
	}
373
374 2
	protected function getUpdateValues(TActiveRecord $record)
375
	{
376 2
		$values = [];
377 2
		$tableInfo = $this->getCommand($record)->getTableInfo();
378 2
		$primary = [];
379 2
		foreach ($tableInfo->getColumns() as $name => $column) {
380 2
			if ($column->getIsExcluded()) {
381
				continue;
382
			}
383 2
			$value = $record->getColumnValue($name);
384 2
			if (!$column->getAllowNull() && $value === null && ($column->getDefaultValue() === TDbTableColumn::UNDEFINED_VALUE)) {
385
				throw new TActiveRecordException(
386
					'ar_value_must_not_be_null',
387
					get_class($record),
388
					$tableInfo->getTableFullName(),
389
					$name
390
				);
391
			}
392 2
			if ($column->getIsPrimaryKey()) {
393 2
				$primary[$name] = $value;
394
			} else {
395 2
				$values[$name] = $value;
396
			}
397
		}
398 2
		return [$values, $primary];
399
	}
400
401
	protected function updateAssociatedRecords(TActiveRecord $record, $updateBelongsTo = false)
402
	{
403
		$context = new TActiveRecordRelationContext($record);
404
		return $context->updateAssociatedRecords($updateBelongsTo);
405
	}
406
407
	/**
408
	 * Delete the record.
409
	 * @param TActiveRecord $record record to be deleted.
410
	 * @return int number of rows affected.
411
	 */
412 2
	public function delete(TActiveRecord $record)
413
	{
414 2
		return $this->getCommand($record)->deleteByPk($this->getPrimaryKeyValues($record));
415
	}
416
417 2
	protected function getPrimaryKeyValues(TActiveRecord $record)
418
	{
419 2
		$tableInfo = $this->getCommand($record)->getTableInfo();
420 2
		$primary = [];
421 2
		foreach ($tableInfo->getColumns() as $name => $column) {
422 2
			if ($column->getIsPrimaryKey()) {
423 2
				$primary[$name] = $record->getColumnValue($name);
424
			}
425
		}
426 2
		return $primary;
427
	}
428
429
	/**
430
	 * Delete multiple records using primary keys.
431
	 * @param TActiveRecord $record finder instance.
432
	 * @param mixed $keys
433
	 * @return int number of rows deleted.
434
	 */
435 2
	public function deleteRecordsByPk(TActiveRecord $record, $keys)
436
	{
437 2
		return $this->getCommand($record)->deleteByPk($keys);
438
	}
439
440
	/**
441
	 * Delete multiple records by criteria.
442
	 * @param TActiveRecord $record active record finder instance.
443
	 * @param TActiveRecordCriteria $criteria search criteria
444
	 * @return int number of records.
445
	 */
446 1
	public function deleteRecordsByCriteria(TActiveRecord $record, $criteria)
447
	{
448 1
		return $this->getCommand($record)->delete($criteria);
449
	}
450
}
451