Completed
Branch master (099915)
by Fabio
08:02
created

TMappedStatement::runQueryForMap()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 27
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 19
c 1
b 0
f 0
nc 6
nop 6
dl 0
loc 27
ccs 19
cts 19
cp 1
crap 6
rs 9.0111
1
<?php
2
/**
3
 * TMappedStatement and related classes.
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\SqlMap\Statements
9
 */
10
11
namespace Prado\Data\SqlMap\Statements;
12
13
use Prado\Collections\TList;
14
use Prado\Data\ActiveRecord\TActiveRecord;
15
use Prado\Data\SqlMap\Configuration\TResultProperty;
16
use Prado\Data\SqlMap\Configuration\TSqlMapInsert;
17
use Prado\Data\SqlMap\Configuration\TSqlMapStatement;
18
use Prado\Data\SqlMap\DataMapper\TLazyLoadList;
19
use Prado\Data\SqlMap\DataMapper\TPropertyAccess;
20
use Prado\Data\SqlMap\DataMapper\TSqlMapExecutionException;
21
use Prado\Data\SqlMap\TSqlMapManager;
22
use Prado\Exceptions\TInvalidDataValueException;
23
24
/**
25
 * TMappedStatement class executes SQL mapped statements. Mapped Statements can
26
 * hold any SQL statement and use Parameter Maps and Result Maps for input and output.
27
 *
28
 * This class is usualy instantiated during SQLMap configuration by TSqlDomBuilder.
29
 *
30
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
31
 * @package Prado\Data\SqlMap\Statements
32
 * @since 3.0
33
 */
34
class TMappedStatement extends \Prado\TComponent implements IMappedStatement
35
{
36
	/**
37
	 * @var TSqlMapStatement current SQL statement.
38
	 */
39
	private $_statement;
40
41
	/**
42
	 * @var TPreparedCommand SQL command prepareer
43
	 */
44
	private $_command;
45
46
	/**
47
	 * @var TSqlMapper sqlmap used by this mapper.
0 ignored issues
show
Bug introduced by
The type Prado\Data\SqlMap\Statements\TSqlMapper 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...
48
	 */
49
	private $_manager;
50
51
	/**
52
	 * @var TPostSelectBinding[] post select statement queue.
53
	 */
54
	private $_selectQueue = [];
55
56
	/**
57
	 * @var bool true when data is mapped to a particular row.
58
	 */
59
	private $_IsRowDataFound = false;
60
61
	/**
62
	 * @var TSQLMapObjectCollectionTree group by object collection tree
63
	 */
64
	private $_groupBy;
65
66
	/**
67
	 * @var Post select is to query for list.
68
	 */
69
	const QUERY_FOR_LIST = 0;
70
71
	/**
72
	 * @var Post select is to query for list.
73
	 */
74
	const QUERY_FOR_ARRAY = 1;
75
76
	/**
77
	 * @var Post select is to query for object.
78
	 */
79
	const QUERY_FOR_OBJECT = 2;
80
81
	/**
82
	 * @return string Name used to identify the TMappedStatement amongst the others.
83
	 * This the name of the SQL statement by default.
84
	 */
85 2
	public function getID()
86
	{
87 2
		return $this->_statement->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property ID does not exist on Prado\Data\SqlMap\Configuration\TSqlMapStatement. Since you implemented __get, consider adding a @property annotation.
Loading history...
88
	}
89
90
	/**
91
	 * @return TSqlMapStatement The SQL statment used by this MappedStatement
92
	 */
93 9
	public function getStatement()
94
	{
95 9
		return $this->_statement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_statement returns the type Prado\Data\SqlMap\Configuration\TSqlMapStatement which is incompatible with the return type mandated by Prado\Data\SqlMap\Statem...atement::getStatement() of Prado\Data\SqlMap\Statements\TSqlMapStatement.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
96
	}
97
98
	/**
99
	 * @return TSqlMapManager The SqlMap used by this MappedStatement
100
	 */
101 118
	public function getManager()
102
	{
103 118
		return $this->_manager;
104
	}
105
106
	/**
107
	 * @return TPreparedCommand command to prepare SQL statements.
108
	 */
109 3
	public function getCommand()
110
	{
111 3
		return $this->_command;
112
	}
113
114
	/**
115
	 * Empty the group by results cache.
116
	 */
117 3
	protected function initialGroupByResults()
118
	{
119 3
		$this->_groupBy = new TSqlMapObjectCollectionTree();
120 3
	}
121
122
	/**
123
	 * Creates a new mapped statement.
124
	 * @param TSqlMapManager $sqlMap an sqlmap.
125
	 * @param TSqlMapStatement $statement An SQL statement.
126
	 */
127 2
	public function __construct(TSqlMapManager $sqlMap, TSqlMapStatement $statement)
128
	{
129 2
		$this->_manager = $sqlMap;
0 ignored issues
show
Documentation Bug introduced by
It seems like $sqlMap of type Prado\Data\SqlMap\TSqlMapManager is incompatible with the declared type Prado\Data\SqlMap\Statements\TSqlMapper of property $_manager.

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...
130 2
		$this->_statement = $statement;
131 2
		$this->_command = new TPreparedCommand();
132 2
		$this->initialGroupByResults();
133 2
	}
134
135 1
	public function getSqlString()
136
	{
137 1
		return $this->getStatement()->getSqlText()->getPreparedStatement()->getPreparedSql();
138
	}
139
140
	/**
141
	 * Execute SQL Query.
142
	 * @param IDbConnection $connection database connection
143
	 * @param array $sql SQL statement and parameters.
144
	 * @param mixed $command
145
	 * @param mixed $max
146
	 * @param mixed $skip
147
	 * @throws TSqlMapExecutionException if execution error or false record set.
148
	 * @throws TSqlMapQueryExecutionException if any execution error
149
	 * @return mixed record set if applicable.
150
	 */
151
	/*	protected function executeSQLQuery($connection, $sql)
152
		{
153
			try
154
			{
155
				if(!($recordSet = $connection->execute($sql['sql'],$sql['parameters'])))
156
				{
157
					throw new TSqlMapExecutionException(
158
						'sqlmap_execution_error_no_record', $this->getID(),
159
						$connection->ErrorMsg());
160
				}
161
				return $recordSet;
162
			}
163
			catch (Exception $e)
164
			{
165
				throw new TSqlMapQueryExecutionException($this->getStatement(), $e);
166
			}
167
		}*/
168
169
	/**
170
	 * Execute SQL Query with limits.
171
	 * @param IDbConnection $connection database connection
0 ignored issues
show
Bug introduced by
The type Prado\Data\SqlMap\Statements\IDbConnection 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...
172
	 * @param $command
173
	 * @param int $max The maximum number of rows to return.
174
	 * @param int $skip The number of rows to skip over.
175
	 * @throws TSqlMapExecutionException if execution error or false record set.
176
	 * @throws TSqlMapQueryExecutionException if any execution error
177
	 * @return mixed record set if applicable.
178
	 */
179
	protected function executeSQLQueryLimit($connection, $command, $max, $skip)
180
	{
181
		if ($max > -1 || $skip > -1) {
182
			$maxStr = $max > 0 ? ' LIMIT ' . $max : '';
183
			$skipStr = $skip > 0 ? ' OFFSET ' . $skip : '';
184
			$command->setText($command->getText() . $maxStr . $skipStr);
185
		}
186
		$connection->setActive(true);
187
		return $command->query();
188
189
		/*//var_dump($command);
190
		try
191
		{
192
			$recordSet = $connection->selectLimit($sql['sql'],$max,$skip,$sql['parameters']);
193
			if(!$recordSet)
194
			{
195
				throw new TSqlMapExecutionException(
196
							'sqlmap_execution_error_query_for_list',
197
							$connection->ErrorMsg());
198
			}
199
			return $recordSet;
200
		}
201
		catch (Exception $e)
202
		{
203
			throw new TSqlMapQueryExecutionException($this->getStatement(), $e);
204
		}*/
205
	}
206
207
	/**
208
	 * Executes the SQL and retuns a List of result objects.
209
	 * @param IDbConnection $connection database connection
210
	 * @param mixed $parameter The object used to set the parameters in the SQL.
211
	 * @param null|object $result result collection object.
212
	 * @param int $skip The number of rows to skip over.
213
	 * @param int $max The maximum number of rows to return.
214
	 * @param null|callable $delegate row delegate handler
215
	 * @return array a list of result objects
216
	 * @see executeQueryForList()
217
	 */
218 32
	public function executeQueryForList($connection, $parameter, $result = null, $skip = -1, $max = -1, $delegate = null)
219
	{
220 32
		$sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter, $skip, $max);
221 32
		return $this->runQueryForList($connection, $parameter, $sql, $result, $delegate);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->runQueryFo...ql, $result, $delegate) returns the type array which is incompatible with the return type mandated by Prado\Data\SqlMap\Statem...::executeQueryForList() of Prado\Data\SqlMap\Statements\TList.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
222
	}
223
224
	/**
225
	 * Executes the SQL and retuns a List of result objects.
226
	 *
227
	 * This method should only be called by internal developers, consider using
228
	 * <tt>executeQueryForList()</tt> first.
229
	 *
230
	 * @param IDbConnection $connection database connection
231
	 * @param mixed $parameter The object used to set the parameters in the SQL.
232
	 * @param array $sql SQL string and subsititution parameters.
233
	 * @param object $result result collection object.
234
	 * @param null|callable $delegate row delegate handler
235
	 * @return array a list of result objects
236
	 * @see executeQueryForList()
237
	 */
238 35
	public function runQueryForList($connection, $parameter, $sql, $result, $delegate = null)
239
	{
240 35
		$registry = $this->getManager()->getTypeHandlers();
241 35
		$list = $result instanceof \ArrayAccess ? $result :
242 35
							$this->_statement->createInstanceOfListClass($registry);
243 35
		$connection->setActive(true);
244 35
		$reader = $sql->query();
245
		//$reader = $this->executeSQLQueryLimit($connection, $sql, $max, $skip);
246 35
		if ($delegate !== null) {
247 1
			foreach ($reader as $row) {
248 1
				$obj = $this->applyResultMap($row);
249 1
				$param = new TResultSetListItemParameter($obj, $parameter, $list);
250 1
				$this->raiseRowDelegate($delegate, $param);
251
			}
252
		} else {
253
			//var_dump($sql,$parameter);
254 34
			foreach ($reader as $row) {
255
//				var_dump($row);
256 33
				$list[] = $this->applyResultMap($row);
257
			}
258
		}
259
260 35
		if (!$this->_groupBy->isEmpty()) {
261 1
			$list = $this->_groupBy->collect();
262 1
			$this->initialGroupByResults();
263
		}
264
265 35
		$this->executePostSelect($connection);
266 35
		$this->onExecuteQuery($sql);
267
268 35
		return $list;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $list also could return the type ArrayAccess which is incompatible with the documented return type array.
Loading history...
269
	}
270
271
	/**
272
	 * Executes the SQL and retuns all rows selected in a map that is keyed on
273
	 * the property named in the keyProperty parameter.  The value at each key
274
	 * will be the value of the property specified in the valueProperty parameter.
275
	 * If valueProperty is null, the entire result object will be entered.
276
	 * @param IDbConnection $connection database connection
277
	 * @param mixed $parameter The object used to set the parameters in the SQL.
278
	 * @param string $keyProperty The property of the result object to be used as the key.
279
	 * @param null|string $valueProperty The property of the result object to be used as the value (or null).
280
	 * @param int $skip The number of rows to skip over.
281
	 * @param int $max The maximum number of rows to return.
282
	 * @param null|callable $delegate row delegate handler
283
	 * @return array An array of object containing the rows keyed by keyProperty.
284
	 */
285 4
	public function executeQueryForMap($connection, $parameter, $keyProperty, $valueProperty = null, $skip = -1, $max = -1, $delegate = null)
286
	{
287 4
		$sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter, $skip, $max);
288 4
		return $this->runQueryForMap($connection, $parameter, $sql, $keyProperty, $valueProperty, $delegate);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->runQueryFo...lueProperty, $delegate) returns the type array which is incompatible with the return type mandated by Prado\Data\SqlMap\Statem...t::executeQueryForMap() of Prado\Data\SqlMap\Statements\TMap.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
289
	}
290
291
	/**
292
	 * Executes the SQL and retuns all rows selected in a map that is keyed on
293
	 * the property named in the keyProperty parameter.  The value at each key
294
	 * will be the value of the property specified in the valueProperty parameter.
295
	 * If valueProperty is null, the entire result object will be entered.
296
	 *
297
	 * This method should only be called by internal developers, consider using
298
	 * <tt>executeQueryForMap()</tt> first.
299
	 *
300
	 * @param IDbConnection $connection database connection
301
	 * @param mixed $parameter The object used to set the parameters in the SQL.
302
	 * @param mixed $command
303
	 * @param string $keyProperty The property of the result object to be used as the key.
304
	 * @param null|string $valueProperty The property of the result object to be used as the value (or null).
305
	 * @param null|callable $delegate row delegate, a callback function
306
	 * @return array An array of object containing the rows keyed by keyProperty.
307
	 * @see executeQueryForMap()
308
	 */
309 4
	public function runQueryForMap($connection, $parameter, $command, $keyProperty, $valueProperty = null, $delegate = null)
310
	{
311 4
		$map = [];
312
		//$recordSet = $this->executeSQLQuery($connection, $sql);
313 4
		$connection->setActive(true);
314 4
		$reader = $command->query();
315 4
		if ($delegate !== null) {
316
			//while($row = $recordSet->fetchRow())
317 1
			foreach ($reader as $row) {
318 1
				$obj = $this->applyResultMap($row);
319 1
				$key = TPropertyAccess::get($obj, $keyProperty);
320 1
				$value = ($valueProperty === null) ? $obj :
321 1
							TPropertyAccess::get($obj, $valueProperty);
322 1
				$param = new TResultSetMapItemParameter($key, $value, $parameter, $map);
323 1
				$this->raiseRowDelegate($delegate, $param);
324
			}
325
		} else {
326
			//while($row = $recordSet->fetchRow())
327 3
			foreach ($reader as $row) {
328 3
				$obj = $this->applyResultMap($row);
329 3
				$key = TPropertyAccess::get($obj, $keyProperty);
330 3
				$map[$key] = ($valueProperty === null) ? $obj :
331 3
								TPropertyAccess::get($obj, $valueProperty);
332
			}
333
		}
334 4
		$this->onExecuteQuery($command);
335 4
		return $map;
336
	}
337
338
	/**
339
	 * Raises delegate handler.
340
	 * This method is invoked for each new list item. It is the responsibility
341
	 * of the handler to add the item to the list.
342
	 * @param callable $handler to be executed
343
	 * @param mixed $param event parameter
344
	 */
345 2
	protected function raiseRowDelegate($handler, $param)
346
	{
347 2
		if (is_string($handler)) {
348
			call_user_func($handler, $this, $param);
349 2
		} elseif (is_callable($handler, true)) {
350
			// an array: 0 - object, 1 - method name/path
351 2
			[$object, $method] = $handler;
352 2
			if (is_string($object)) {	// static method call
353
				call_user_func($handler, $this, $param);
354
			} else {
355 2
				if (($pos = strrpos($method, '.')) !== false) {
356
					$object = $this->getSubProperty(substr($method, 0, $pos));
357
					$method = substr($method, $pos + 1);
358
				}
359 2
				$object->$method($this, $param);
360
			}
361
		} else {
362
			throw new TInvalidDataValueException('sqlmap_invalid_delegate', $this->getID(), $handler);
363
		}
364 2
	}
365
366
	/**
367
	 * Executes an SQL statement that returns a single row as an object of the
368
	 * type of the <tt>$result</tt> passed in as a parameter.
369
	 * @param IDbConnection $connection database connection
370
	 * @param mixed $parameter The parameter data (object, arrary, primitive) used to set the parameters in the SQL
371
	 * @param null|mixed $result The result object.
372
	 * @return object the object.
373
	 */
374 86
	public function executeQueryForObject($connection, $parameter, $result = null)
375
	{
376 86
		$sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter);
377 86
		return $this->runQueryForObject($connection, $sql, $result);
378
	}
379
380
	/**
381
	 * Executes an SQL statement that returns a single row as an object of the
382
	 * type of the <tt>$result</tt> passed in as a parameter.
383
	 *
384
	 * This method should only be called by internal developers, consider using
385
	 * <tt>executeQueryForObject()</tt> first.
386
	 *
387
	 * @param IDbConnection $connection database connection
388
	 * @param $command
389
	 * @param object &$result The result object.
390
	 * @return object the object.
391
	 * @see executeQueryForObject()
392
	 */
393 86
	public function runQueryForObject($connection, $command, &$result)
394
	{
395 86
		$object = null;
396 86
		$connection->setActive(true);
397 86
		foreach ($command->query() as $row) {
398 86
			$object = $this->applyResultMap($row, $result);
399
		}
400
401 86
		if (!$this->_groupBy->isEmpty()) {
402
			$list = $this->_groupBy->collect();
403
			$this->initialGroupByResults();
404
			$object = $list[0];
405
		}
406
407 86
		$this->executePostSelect($connection);
408 86
		$this->onExecuteQuery($command);
409
410 86
		return $object;
411
	}
412
413
	/**
414
	 * Execute an insert statement. Fill the parameter object with the ouput
415
	 * parameters if any, also could return the insert generated key.
416
	 * @param IDbConnection $connection database connection
417
	 * @param mixed $parameter The parameter object used to fill the statement.
418
	 * @return string the insert generated key.
419
	 */
420 20
	public function executeInsert($connection, $parameter)
421
	{
422 20
		$generatedKey = $this->getPreGeneratedSelectKey($connection, $parameter);
423
424 20
		$command = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter);
425
//		var_dump($command,$parameter);
426 20
		$result = $command->execute();
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
427
428 20
		if ($generatedKey === null) {
0 ignored issues
show
introduced by
The condition $generatedKey === null is always false.
Loading history...
429 19
			$generatedKey = $this->getPostGeneratedSelectKey($connection, $parameter);
430
		}
431
432 20
		$this->executePostSelect($connection);
433 20
		$this->onExecuteQuery($command);
434 20
		return $generatedKey;
435
	}
436
437
	/**
438
	 * Gets the insert generated ID before executing an insert statement.
439
	 * @param IDbConnection $connection database connection
440
	 * @param mixed $parameter insert statement parameter.
441
	 * @return string new insert ID if pre-select key statement was executed, null otherwise.
442
	 */
443 20
	protected function getPreGeneratedSelectKey($connection, $parameter)
444
	{
445 20
		if ($this->_statement instanceof TSqlMapInsert) {
446 16
			$selectKey = $this->_statement->getSelectKey();
447 16
			if (($selectKey !== null) && !$selectKey->getIsAfter()) {
448 1
				return $this->executeSelectKey($connection, $parameter, $selectKey);
449
			}
450
		}
451 19
	}
452
453
	/**
454
	 * Gets the inserted row ID after executing an insert statement.
455
	 * @param IDbConnection $connection database connection
456
	 * @param mixed $parameter insert statement parameter.
457
	 * @return string last insert ID, null otherwise.
458
	 */
459 19
	protected function getPostGeneratedSelectKey($connection, $parameter)
460
	{
461 19
		if ($this->_statement instanceof TSqlMapInsert) {
462 15
			$selectKey = $this->_statement->getSelectKey();
463 15
			if (($selectKey !== null) && $selectKey->getIsAfter()) {
464 1
				return $this->executeSelectKey($connection, $parameter, $selectKey);
465
			}
466
		}
467 18
	}
468
469
	/**
470
	 * Execute the select key statement, used to obtain last insert ID.
471
	 * @param IDbConnection $connection database connection
472
	 * @param mixed $parameter insert statement parameter
473
	 * @param TSqlMapSelectKey $selectKey select key statement
0 ignored issues
show
Bug introduced by
The type Prado\Data\SqlMap\Statements\TSqlMapSelectKey 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...
474
	 * @return string last insert ID.
475
	 */
476 2
	protected function executeSelectKey($connection, $parameter, $selectKey)
477
	{
478 2
		$mappedStatement = $this->getManager()->getMappedStatement($selectKey->getID());
479 2
		$generatedKey = $mappedStatement->executeQueryForObject(
480 2
			$connection,
481 2
			$parameter,
482 2
			null
483
		);
484 2
		if (strlen($prop = $selectKey->getProperty()) > 0) {
485 2
			TPropertyAccess::set($parameter, $prop, $generatedKey);
486
		}
487 2
		return $generatedKey;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $generatedKey returns the type object which is incompatible with the documented return type string.
Loading history...
488
	}
489
490
	/**
491
	 * Execute an update statement. Also used for delete statement.
492
	 * Return the number of rows effected.
493
	 * @param IDbConnection $connection database connection
494
	 * @param mixed $parameter The object used to set the parameters in the SQL.
495
	 * @return int The number of rows effected.
496
	 */
497 8
	public function executeUpdate($connection, $parameter)
498
	{
499 8
		$sql = $this->_command->create($this->getManager(), $connection, $this->_statement, $parameter);
500 8
		$affectedRows = $sql->execute();
501
		//$this->executeSQLQuery($connection, $sql);
502 8
		$this->executePostSelect($connection);
503 8
		$this->onExecuteQuery($sql);
504 8
		return $affectedRows;
505
	}
506
507
	/**
508
	 * Process 'select' result properties
509
	 * @param IDbConnection $connection database connection
510
	 */
511 117
	protected function executePostSelect($connection)
512
	{
513 117
		while (count($this->_selectQueue)) {
514 6
			$postSelect = array_shift($this->_selectQueue);
515 6
			$method = $postSelect->getMethod();
516 6
			$statement = $postSelect->getStatement();
517 6
			$property = $postSelect->getResultProperty()->getProperty();
518 6
			$keys = $postSelect->getKeys();
519 6
			$resultObject = $postSelect->getResultObject();
520
521 6
			if ($method == self::QUERY_FOR_LIST || $method == self::QUERY_FOR_ARRAY) {
522 4
				$values = $statement->executeQueryForList($connection, $keys, null);
523
524 4
				if ($method == self::QUERY_FOR_ARRAY) {
525 1
					$values = $values->toArray();
526
				}
527 4
				TPropertyAccess::set($resultObject, $property, $values);
528 2
			} elseif ($method == self::QUERY_FOR_OBJECT) {
529 2
				$value = $statement->executeQueryForObject($connection, $keys, null);
530 2
				TPropertyAccess::set($resultObject, $property, $value);
531
			}
532
		}
533 117
	}
534
535
	/**
536
	 * Raise the execute query event.
537
	 * @param array $sql prepared SQL statement and subsititution parameters
538
	 */
539 120
	public function onExecuteQuery($sql)
540
	{
541 120
		$this->raiseEvent('OnExecuteQuery', $this, $sql);
0 ignored issues
show
Bug introduced by
$sql of type array is incompatible with the type Prado\TEventParameter expected by parameter $param of Prado\TComponent::raiseEvent(). ( Ignorable by Annotation )

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

541
		$this->raiseEvent('OnExecuteQuery', $this, /** @scrutinizer ignore-type */ $sql);
Loading history...
542 120
	}
543
544
	/**
545
	 * Apply result mapping.
546
	 * @param array $row a result set row retrieved from the database
547
	 * @param null|&object $resultObject the result object, will create if necessary.
0 ignored issues
show
Documentation Bug introduced by
The doc comment null|&object at position 2 could not be parsed: Unknown type name '&' at position 2 in null|&object.
Loading history...
548
	 * @return object the result filled with data, null if not filled.
549
	 */
550 115
	protected function applyResultMap($row, &$resultObject = null)
551
	{
552 115
		if ($row === false) {
553
			return null;
554
		}
555
556 115
		$resultMapName = $this->_statement->getResultMap();
557 115
		$resultClass = $this->_statement->getResultClass();
558
559 115
		$obj = null;
560 115
		if ($this->getManager()->getResultMaps()->contains($resultMapName)) {
561 69
			$obj = $this->fillResultMap($resultMapName, $row, null, $resultObject);
562 55
		} elseif (strlen($resultClass) > 0) {
563 44
			$obj = $this->fillResultClass($resultClass, $row, $resultObject);
564
		} else {
565 11
			$obj = $this->fillDefaultResultMap(null, $row, $resultObject);
566
		}
567 115
		if (class_exists('TActiveRecord', false) && $obj instanceof TActiveRecord) {
568
			//Create a new clean active record.
569
			$obj = TActiveRecord::createRecord(get_class($obj), $obj);
0 ignored issues
show
Bug introduced by
$obj of type Prado\Data\ActiveRecord\TActiveRecord is incompatible with the type array expected by parameter $data of Prado\Data\ActiveRecord\...eRecord::createRecord(). ( Ignorable by Annotation )

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

569
			$obj = TActiveRecord::createRecord(get_class($obj), /** @scrutinizer ignore-type */ $obj);
Loading history...
570
		}
571 115
		return $obj;
572
	}
573
574
	/**
575
	 * Fill the result using ResultClass, will creates new result object if required.
576
	 * @param string $resultClass result object class name
577
	 * @param array $row a result set row retrieved from the database
578
	 * @param object $resultObject the result object, will create if necessary.
579
	 * @return object result object filled with data
580
	 */
581 44
	protected function fillResultClass($resultClass, $row, $resultObject)
582
	{
583 44
		if ($resultObject === null) {
584 44
			$registry = $this->getManager()->getTypeHandlers();
585 44
			$resultObject = $this->_statement->createInstanceOfResultClass($registry, $row);
586
		}
587
588 44
		if ($resultObject instanceof \ArrayAccess) {
589 2
			return $this->fillResultArrayList($row, $resultObject);
590 42
		} elseif (is_object($resultObject)) {
591 23
			return $this->fillResultObjectProperty($row, $resultObject);
592
		} else {
593 21
			return $this->fillDefaultResultMap(null, $row, $resultObject);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->fillDefaul...l, $row, $resultObject) returns the type array which is incompatible with the documented return type object.
Loading history...
594
		}
595
	}
596
597
	/**
598
	 * Apply the result to a TList or an array.
599
	 * @param array $row a result set row retrieved from the database
600
	 * @param object $resultObject result object, array or list
601
	 * @return object result filled with data.
602
	 */
603 2
	protected function fillResultArrayList($row, $resultObject)
604
	{
605 2
		if ($resultObject instanceof TList) {
606 2
			foreach ($row as $v) {
607 2
				$resultObject[] = $v;
608
			}
609
		} else {
610
			foreach ($row as $k => $v) {
611
				$resultObject[$k] = $v;
612
			}
613
		}
614 2
		return $resultObject;
615
	}
616
617
	/**
618
	 * Apply the result to an object.
619
	 * @param array $row a result set row retrieved from the database
620
	 * @param object $resultObject result object, array or list
621
	 * @return object result filled with data.
622
	 */
623 23
	protected function fillResultObjectProperty($row, $resultObject)
624
	{
625 23
		$index = 0;
626 23
		$registry = $this->getManager()->getTypeHandlers();
627 23
		foreach ($row as $k => $v) {
628 23
			$property = new TResultProperty;
629 23
			if (is_string($k) && strlen($k) > 0) {
630 23
				$property->setColumn($k);
631
			}
632 23
			$property->setColumnIndex(++$index);
633 23
			$type = gettype(TPropertyAccess::get($resultObject, $k));
634 23
			$property->setType($type);
635 23
			$value = $property->getPropertyValue($registry, $row);
636 23
			TPropertyAccess::set($resultObject, $k, $value);
637
		}
638 23
		return $resultObject;
639
	}
640
641
	/**
642
	 * Fills the result object according to result mappings.
643
	 * @param string $resultMapName result map name.
644
	 * @param array $row a result set row retrieved from the database
645
	 * @param null|mixed $parentGroup
646
	 * @param null|&object $resultObject result object to fill, will create new instances if required.
0 ignored issues
show
Documentation Bug introduced by
The doc comment null|&object at position 2 could not be parsed: Unknown type name '&' at position 2 in null|&object.
Loading history...
647
	 * @return object result object filled with data.
648
	 */
649 69
	protected function fillResultMap($resultMapName, $row, $parentGroup = null, &$resultObject = null)
650
	{
651 69
		$resultMap = $this->getManager()->getResultMap($resultMapName);
652 69
		$registry = $this->getManager()->getTypeHandlers();
653 69
		$resultMap = $resultMap->resolveSubMap($registry, $row);
654
655 69
		if ($resultObject === null) {
656 68
			$resultObject = $resultMap->createInstanceOfResult($registry);
657
		}
658
659 69
		if (is_object($resultObject)) {
660 61
			if (strlen($resultMap->getGroupBy()) > 0) {
661 1
				return $this->addResultMapGroupBy($resultMap, $row, $parentGroup, $resultObject);
662
			} else {
663 61
				foreach ($resultMap->getColumns() as $property) {
664 61
					$this->setObjectProperty($resultMap, $property, $row, $resultObject);
665
				}
666
			}
667
		} else {
668 8
			$resultObject = $this->fillDefaultResultMap($resultMap, $row, $resultObject);
669
		}
670 69
		return $resultObject;
671
	}
672
673
	/**
674
	 * ResultMap with GroupBy property. Save object collection graph in a tree
675
	 * and collect the result later.
676
	 * @param TResultMap $resultMap result mapping details.
0 ignored issues
show
Bug introduced by
The type Prado\Data\SqlMap\Statements\TResultMap 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...
677
	 * @param array $row a result set row retrieved from the database
678
	 * @param mixed $parent
679
	 * @param object &$resultObject the result object
680
	 * @return object result object.
681
	 */
682 1
	protected function addResultMapGroupBy($resultMap, $row, $parent, &$resultObject)
683
	{
684 1
		$group = $this->getResultMapGroupKey($resultMap, $row);
685
686 1
		if (empty($parent)) {
687 1
			$rootObject = ['object' => $resultObject, 'property' => null];
688 1
			$this->_groupBy->add(null, $group, $rootObject);
689
		}
690
691 1
		foreach ($resultMap->getColumns() as $property) {
692
			//set properties.
693 1
			$this->setObjectProperty($resultMap, $property, $row, $resultObject);
694 1
			$nested = $property->getResultMapping();
695
696
			//nested property
697 1
			if ($this->getManager()->getResultMaps()->contains($nested)) {
698 1
				$nestedMap = $this->getManager()->getResultMap($nested);
699 1
				$groupKey = $this->getResultMapGroupKey($nestedMap, $row);
700
701
				//add the node reference first
702 1
				if (empty($parent)) {
703 1
					$this->_groupBy->add($group, $groupKey, '');
704
				}
705
706
				//get the nested result mapping value
707 1
				$value = $this->fillResultMap($nested, $row, $groupKey);
708
709
				//add it to the object tree graph
710 1
				$groupObject = ['object' => $value, 'property' => $property->getProperty()];
711 1
				if (empty($parent)) {
712 1
					$this->_groupBy->add($group, $groupKey, $groupObject);
713
				} else {
714 1
					$this->_groupBy->add($parent, $groupKey, $groupObject);
715
				}
716
			}
717
		}
718 1
		return $resultObject;
719
	}
720
721
	/**
722
	 * Gets the result 'group by' groupping key for each row.
723
	 * @param TResultMap $resultMap result mapping details.
724
	 * @param array $row a result set row retrieved from the database
725
	 * @return string groupping key.
726
	 */
727 1
	protected function getResultMapGroupKey($resultMap, $row)
728
	{
729 1
		$groupBy = $resultMap->getGroupBy();
730 1
		if (isset($row[$groupBy])) {
731 1
			return $resultMap->getID() . $row[$groupBy];
732
		} else {
733 1
			return $resultMap->getID() . crc32(serialize($row));
734
		}
735
	}
736
737
	/**
738
	 * Fill the result map using default settings. If <tt>$resultMap</tt> is null
739
	 * the result object returned will be guessed from <tt>$resultObject</tt>.
740
	 * @param TResultMap $resultMap result mapping details.
741
	 * @param array $row a result set row retrieved from the database
742
	 * @param object $resultObject the result object
743
	 * @return mixed the result object filled with data.
744
	 */
745 40
	protected function fillDefaultResultMap($resultMap, $row, $resultObject)
746
	{
747 40
		if ($resultObject === null) {
748 11
			$resultObject = '';
749
		}
750
751 40
		if ($resultMap !== null) {
752 8
			$result = $this->fillArrayResultMap($resultMap, $row, $resultObject);
0 ignored issues
show
Bug introduced by
It seems like $resultObject can also be of type string; however, parameter $resultObject of Prado\Data\SqlMap\Statem...t::fillArrayResultMap() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

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

752
			$result = $this->fillArrayResultMap($resultMap, $row, /** @scrutinizer ignore-type */ $resultObject);
Loading history...
753
		} else {
754 32
			$result = $row;
755
		}
756
757
		//if scalar result types
758 40
		if (count($result) == 1 && ($type = gettype($resultObject)) != 'array') {
759 34
			return $this->getScalarResult($result, $type);
760
		} else {
761 6
			return $result;
762
		}
763
	}
764
765
	/**
766
	 * Retrieve the result map as an array.
767
	 * @param TResultMap $resultMap result mapping details.
768
	 * @param array $row a result set row retrieved from the database
769
	 * @param object $resultObject the result object
770
	 * @return array array list of result objects.
771
	 */
772 8
	protected function fillArrayResultMap($resultMap, $row, $resultObject)
773
	{
774 8
		$result = [];
775 8
		$registry = $this->getManager()->getTypeHandlers();
776 8
		foreach ($resultMap->getColumns() as $column) {
777 8
			if (($column->getType() === null)
778 8
				&& ($resultObject !== null) && !is_object($resultObject)) {
779 7
				$column->setType(gettype($resultObject));
780
			}
781 8
			$result[$column->getProperty()] = $column->getPropertyValue($registry, $row);
782
		}
783 8
		return $result;
784
	}
785
786
	/**
787
	 * Converts the first array value to scalar value of given type.
788
	 * @param array $result list of results
789
	 * @param string $type scalar type.
790
	 * @return mixed scalar value.
791
	 */
792 34
	protected function getScalarResult($result, $type)
793
	{
794 34
		$scalar = array_shift($result);
795 34
		settype($scalar, $type);
796 34
		return $scalar;
797
	}
798
799
	/**
800
	 * Set a property of the result object with appropriate value.
801
	 * @param TResultMap $resultMap result mapping details.
802
	 * @param TResultProperty $property the result property to fill.
803
	 * @param array $row a result set row retrieved from the database
804
	 * @param object &$resultObject the result object
805
	 */
806 61
	protected function setObjectProperty($resultMap, $property, $row, &$resultObject)
807
	{
808 61
		$select = $property->getSelect();
809 61
		$key = $property->getProperty();
810 61
		$nested = $property->getNestedResultMap();
811 61
		$registry = $this->getManager()->getTypeHandlers();
812 61
		if ($key === '') {
813
			$resultObject = $property->getPropertyValue($registry, $row);
814 61
		} elseif (strlen($select) == 0 && ($nested === null)) {
815 61
			$value = $property->getPropertyValue($registry, $row);
816
817 61
			$this->_IsRowDataFound = $this->_IsRowDataFound || ($value != null);
818 61
			if (is_array($resultObject) || is_object($resultObject)) {
819 61
				TPropertyAccess::set($resultObject, $key, $value);
820
			} else {
821 61
				$resultObject = $value;
822
			}
823 14
		} elseif ($nested !== null) {
824 4
			if ($property->instanceOfListType($resultObject) || $property->instanceOfArrayType($resultObject)) {
825 1
				if (strlen($resultMap->getGroupBy()) <= 0) {
826
					throw new TSqlMapExecutionException(
827
						'sqlmap_non_groupby_array_list_type',
828
						$resultMap->getID(),
829
						get_class($resultObject),
830 1
						$key
831
					);
832
				}
833
			} else {
834 3
				$obj = $nested->createInstanceOfResult($this->getManager()->getTypeHandlers());
835 3
				if ($this->fillPropertyWithResultMap($nested, $row, $obj) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
836 2
					$obj = null;
837
				}
838 4
				TPropertyAccess::set($resultObject, $key, $obj);
839
			}
840
		} else { //'select' ResultProperty
841 10
			$this->enquequePostSelect($select, $resultMap, $property, $row, $resultObject);
842
		}
843 61
	}
844
845
	/**
846
	 * Add nested result property to post select queue.
847
	 * @param string $select post select statement ID
848
	 * @param TResultMap $resultMap current result mapping details.
849
	 * @param TResultProperty $property current result property.
850
	 * @param array $row a result set row retrieved from the database
851
	 * @param object $resultObject the result object
852
	 */
853 10
	protected function enquequePostSelect($select, $resultMap, $property, $row, $resultObject)
854
	{
855 10
		$statement = $this->getManager()->getMappedStatement($select);
856 10
		$key = $this->getPostSelectKeys($resultMap, $property, $row);
857 10
		$postSelect = new TPostSelectBinding;
858 10
		$postSelect->setStatement($statement);
859 10
		$postSelect->setResultObject($resultObject);
860 10
		$postSelect->setResultProperty($property);
861 10
		$postSelect->setKeys($key);
862
863 10
		if ($property->instanceOfListType($resultObject)) {
864 7
			$values = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $values is dead and can be removed.
Loading history...
865 7
			if ($property->getLazyLoad()) {
866 4
				$values = TLazyLoadList::newInstance(
867 4
					$statement,
868 4
					$key,
869 4
					$resultObject,
870 4
					$property->getProperty()
871
				);
872 4
				TPropertyAccess::set($resultObject, $property->getProperty(), $values);
873
			} else {
874 7
				$postSelect->setMethod(self::QUERY_FOR_LIST);
875
			}
876 3
		} elseif ($property->instanceOfArrayType($resultObject)) {
877 1
			$postSelect->setMethod(self::QUERY_FOR_ARRAY);
878
		} else {
879 2
			$postSelect->setMethod(self::QUERY_FOR_OBJECT);
880
		}
881
882 10
		if (!$property->getLazyLoad()) {
883 6
			$this->_selectQueue[] = $postSelect;
884
		}
885 10
	}
886
887
	/**
888
	 * Finds in the post select property the SQL statement primary selection keys.
889
	 * @param TResultMap $resultMap result mapping details
890
	 * @param TResultProperty $property result property
891
	 * @param array $row current row data.
892
	 * @return array list of primary key values.
893
	 */
894 10
	protected function getPostSelectKeys($resultMap, $property, $row)
895
	{
896 10
		$value = $property->getColumn();
897 10
		if (is_int(strpos($value, ',', 0)) || is_int(strpos($value, '=', 0))) {
0 ignored issues
show
introduced by
The condition is_int(strpos($value, ',', 0)) is always true.
Loading history...
898 1
			$keys = [];
899 1
			foreach (explode(',', $value) as $entry) {
900 1
				$pair = explode('=', $entry);
901 1
				$keys[trim($pair[0])] = $row[trim($pair[1])];
902
			}
903 1
			return $keys;
904
		} else {
905 9
			$registry = $this->getManager()->getTypeHandlers();
906 9
			return $property->getPropertyValue($registry, $row);
907
		}
908
	}
909
910
	/**
911
	 * Fills the property with result mapping results.
912
	 * @param TResultMap $resultMap nested result mapping details.
913
	 * @param array $row a result set row retrieved from the database
914
	 * @param object &$resultObject the result object
915
	 * @return bool true if the data was found, false otherwise.
916
	 */
917 3
	protected function fillPropertyWithResultMap($resultMap, $row, &$resultObject)
918
	{
919 3
		$dataFound = false;
920 3
		foreach ($resultMap->getColumns() as $property) {
921 3
			$this->_IsRowDataFound = false;
922 3
			$this->setObjectProperty($resultMap, $property, $row, $resultObject);
923 3
			$dataFound = $dataFound || $this->_IsRowDataFound;
924
		}
925 3
		$this->_IsRowDataFound = $dataFound;
926 3
		return $dataFound;
927
	}
928
929
	public function __wakeup()
930
	{
931
		if (null === $this->_selectQueue) {
932
			$this->_selectQueue = [];
933
		}
934
	}
935
936
	public function __sleep()
937
	{
938
		$exprops = [];
939
		$cn = __CLASS__;
940
		if (!count($this->_selectQueue)) {
941
			$exprops[] = "\0$cn\0_selectQueue";
942
		}
943
		if (null === $this->_groupBy) {
944
			$exprops[] = "\0$cn\0_groupBy";
945
		}
946
		if (!$this->_IsRowDataFound) {
947
			$exprops[] = "\0$cn\0_IsRowDataFound";
948
		}
949
		return array_diff(parent::__sleep(), $exprops);
950
	}
951
}
952