TMappedStatement   F
last analyzed

Complexity

Total Complexity 130

Size/Duplication

Total Lines 866
Duplicated Lines 0 %

Test Coverage

Coverage 92.33%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 325
c 1
b 0
f 0
dl 0
loc 866
ccs 289
cts 313
cp 0.9233
rs 2
wmc 130

38 Methods

Rating   Name   Duplication   Size   Complexity  
A runQueryForMap() 0 24 6
A getManager() 0 3 1
A getCommand() 0 3 1
A getStatement() 0 3 1
A getID() 0 3 1
A runQueryForObject() 0 18 3
A executeQueryForMap() 0 4 1
A raiseRowDelegate() 0 18 5
A initialGroupByResults() 0 3 1
A runQueryForList() 0 28 6
A executeQueryForList() 0 4 1
A executeQueryForObject() 0 4 1
A executeSQLQueryLimit() 0 9 5
A fillResultArrayList() 0 12 4
A executeUpdate() 0 7 1
A fillResultObjectProperty() 0 16 4
A executePostSelect() 0 20 6
A executeSelectKey() 0 12 2
A fillResultClass() 0 13 4
A getSqlString() 0 3 1
A onExecuteQuery() 0 3 1
A __construct() 0 7 1
A __sleep() 0 14 4
A getScalarResult() 0 5 1
A getPreGeneratedSelectKey() 0 9 4
A getResultMapGroupKey() 0 7 2
A getPostSelectKeys() 0 13 4
B applyResultMap() 0 22 7
A fillResultMap() 0 23 6
A getPostGeneratedSelectKey() 0 9 4
A enquequePostSelect() 0 31 5
C setObjectProperty() 0 36 13
A fillPropertyWithResultMap() 0 10 3
A fillArrayResultMap() 0 12 5
A addResultMapGroupBy() 0 37 6
A fillDefaultResultMap() 0 17 5
A __wakeup() 0 6 2
A executeInsert() 0 14 2

How to fix   Complexity   

Complex Class

Complex classes like TMappedStatement often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TMappedStatement, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * TMappedStatement and related classes.
5
 *
6
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
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\TResultMap;
16
use Prado\Data\SqlMap\Configuration\TResultProperty;
17
use Prado\Data\SqlMap\Configuration\TSqlMapInsert;
18
use Prado\Data\SqlMap\Configuration\TSqlMapStatement;
19
use Prado\Data\SqlMap\Configuration\TSqlMapSelectKey;
20
use Prado\Data\SqlMap\DataMapper\TLazyLoadList;
21
use Prado\Data\SqlMap\DataMapper\TPropertyAccess;
22
use Prado\Data\SqlMap\DataMapper\TSqlMapExecutionException;
23
use Prado\Data\SqlMap\TSqlMapManager;
24
use Prado\Exceptions\TInvalidDataValueException;
25
26
/**
27
 * TMappedStatement class executes SQL mapped statements. Mapped Statements can
28
 * hold any SQL statement and use Parameter Maps and Result Maps for input and output.
29
 *
30
 * This class is usualy instantiated during SQLMap configuration by TSqlDomBuilder.
31
 *
32
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
33
 * @since 3.0
34
 */
35
class TMappedStatement extends \Prado\TComponent implements IMappedStatement
36
{
37
	/**
38
	 * @var TSqlMapStatement current SQL statement.
39
	 */
40
	private $_statement;
41
42
	/**
43
	 * @var TPreparedCommand SQL command prepareer
44
	 */
45
	private $_command;
46
47
	/**
48
	 * @var TSqlMapManager sqlmap used by this mapper.
49
	 */
50
	private $_manager;
51
52
	/**
53
	 * @var TPostSelectBinding[] post select statement queue.
54
	 */
55
	private $_selectQueue = [];
56
57
	/**
58
	 * @var bool true when data is mapped to a particular row.
59
	 */
60
	private $_IsRowDataFound = false;
61
62
	/**
63
	 * @var TSqlMapObjectCollectionTree group by object collection tree
64
	 */
65
	private $_groupBy;
66
67
	/**
68
	 * @var int select is to query for list.
69
	 */
70
	public const QUERY_FOR_LIST = 0;
71
72
	/**
73
	 * @var int select is to query for array.
74
	 */
75
	public const QUERY_FOR_ARRAY = 1;
76
77
	/**
78
	 * @var int select is to query for object.
79
	 */
80
	public const QUERY_FOR_OBJECT = 2;
81
82
	/**
83
	 * @return string Name used to identify the TMappedStatement amongst the others.
84
	 * This the name of the SQL statement by default.
85 2
	 */
86
	public function getID()
87 2
	{
88
		return $this->_statement->getID();
89
	}
90
91
	/**
92
	 * @return TSqlMapStatement The SQL statment used by this MappedStatement
93 9
	 */
94
	public function getStatement()
95 9
	{
96
		return $this->_statement;
97
	}
98
99
	/**
100
	 * @return TSqlMapManager The SqlMap used by this MappedStatement
101 118
	 */
102
	public function getManager()
103 118
	{
104
		return $this->_manager;
105
	}
106
107
	/**
108
	 * @return TPreparedCommand command to prepare SQL statements.
109 3
	 */
110
	public function getCommand()
111 3
	{
112
		return $this->_command;
113
	}
114
115
	/**
116
	 * Empty the group by results cache.
117 3
	 */
118
	protected function initialGroupByResults()
119 3
	{
120 3
		$this->_groupBy = new TSqlMapObjectCollectionTree();
121
	}
122
123
	/**
124
	 * Creates a new mapped statement.
125
	 * @param TSqlMapManager $sqlMap an sqlmap.
126
	 * @param TSqlMapStatement $statement An SQL statement.
127 2
	 */
128
	public function __construct(TSqlMapManager $sqlMap, TSqlMapStatement $statement)
129 2
	{
130 2
		$this->_manager = $sqlMap;
131 2
		$this->_statement = $statement;
132 2
		$this->_command = new TPreparedCommand();
133 2
		$this->initialGroupByResults();
134
		parent::__construct();
135 1
	}
136
137 1
	public function getSqlString()
138
	{
139
		return $this->getStatement()->getSqlText()->getPreparedStatement()->getPreparedSql();
140
	}
141
142
	/**
143
	 * Execute SQL Query with limits.
144
	 * @param \Prado\Data\TDbConnection $connection database connection
145
	 * @param \Prado\Data\TDbCommand $command
146
	 * @param int $max The maximum number of rows to return.
147
	 * @param int $skip The number of rows to skip over.
148
	 * @throws TSqlMapExecutionException if execution error or false record set.
149
	 * @return mixed record set if applicable.
150
	 */
151
	protected function executeSQLQueryLimit($connection, $command, $max, $skip)
152
	{
153
		if ($max > -1 || $skip > -1) {
154
			$maxStr = $max > 0 ? ' LIMIT ' . $max : '';
155
			$skipStr = $skip > 0 ? ' OFFSET ' . $skip : '';
156
			$command->setText($command->getText() . $maxStr . $skipStr);
157
		}
158
		$connection->setActive(true);
159
		return $command->query();
160
	}
161
162
	/**
163
	 * Executes the SQL and retuns a List of result objects.
164
	 * @param \Prado\Data\TDbConnection $connection database connection
165
	 * @param mixed $parameter The object used to set the parameters in the SQL.
166
	 * @param null|object $result result collection object.
167
	 * @param int $skip The number of rows to skip over.
168
	 * @param int $max The maximum number of rows to return.
169
	 * @param null|callable $delegate row delegate handler
170
	 * @return array a list of result objects
171
	 * @see executeQueryForList()
172
	 */
173
	public function executeQueryForList($connection, $parameter, $result = null, $skip = -1, $max = -1, $delegate = null)
174
	{
175
		$sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter, $skip, $max);
176
		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\Collections\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...
177
	}
178
179
	/**
180
	 * Executes the SQL and retuns a List of result objects.
181
	 *
182
	 * This method should only be called by internal developers, consider using
183
	 * <tt>executeQueryForList()</tt> first.
184
	 *
185
	 * @param \Prado\Data\TDbConnection $connection database connection
186
	 * @param mixed $parameter The object used to set the parameters in the SQL.
187
	 * @param \Prado\Data\TDbCommand $sql SQL string and subsititution parameters.
188
	 * @param object $result result collection object.
189
	 * @param null|callable $delegate row delegate handler
190
	 * @return array a list of result objects
191
	 * @see executeQueryForList()
192
	 */
193
	public function runQueryForList($connection, $parameter, $sql, $result, $delegate = null)
194
	{
195
		$registry = $this->getManager()->getTypeHandlers();
196
		$list = $result instanceof \ArrayAccess ? $result :
197
							$this->_statement->createInstanceOfListClass($registry);
198
		$connection->setActive(true);
199
		$reader = $sql->query();
200
		if ($delegate !== null) {
201
			foreach ($reader as $row) {
202
				$obj = $this->applyResultMap($row);
203
				$param = new TResultSetListItemParameter($obj, $parameter, $list);
204
				$this->raiseRowDelegate($delegate, $param);
205
			}
206
		} else {
207
			foreach ($reader as $row) {
208
				$list[] = $this->applyResultMap($row);
209
			}
210
		}
211
212
		if (!$this->_groupBy->isEmpty()) {
213
			$list = $this->_groupBy->collect();
214
			$this->initialGroupByResults();
215
		}
216
217
		$this->executePostSelect($connection);
218 32
		$this->onExecuteQuery($sql);
0 ignored issues
show
Bug introduced by
$sql of type Prado\Data\TDbCommand is incompatible with the type array expected by parameter $sql of Prado\Data\SqlMap\Statem...ement::onExecuteQuery(). ( Ignorable by Annotation )

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

218
		$this->onExecuteQuery(/** @scrutinizer ignore-type */ $sql);
Loading history...
219
220 32
		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...
221 32
	}
222
223
	/**
224
	 * Executes the SQL and retuns all rows selected in a map that is keyed on
225
	 * the property named in the keyProperty parameter.  The value at each key
226
	 * will be the value of the property specified in the valueProperty parameter.
227
	 * If valueProperty is null, the entire result object will be entered.
228
	 * @param \Prado\Data\TDbConnection $connection database connection
229
	 * @param mixed $parameter The object used to set the parameters in the SQL.
230
	 * @param string $keyProperty The property of the result object to be used as the key.
231
	 * @param null|string $valueProperty The property of the result object to be used as the value (or null).
232
	 * @param int $skip The number of rows to skip over.
233
	 * @param int $max The maximum number of rows to return.
234
	 * @param null|callable $delegate row delegate handler
235
	 * @return array An array of object containing the rows keyed by keyProperty.
236
	 */
237
	public function executeQueryForMap($connection, $parameter, $keyProperty, $valueProperty = null, $skip = -1, $max = -1, $delegate = null)
238 35
	{
239
		$sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter, $skip, $max);
240 35
		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\Collections\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...
241 35
	}
242 35
243 35
	/**
244 35
	 * Executes the SQL and retuns all rows selected in a map that is keyed on
245
	 * the property named in the keyProperty parameter.  The value at each key
246 35
	 * will be the value of the property specified in the valueProperty parameter.
247 1
	 * If valueProperty is null, the entire result object will be entered.
248 1
	 *
249 1
	 * This method should only be called by internal developers, consider using
250 1
	 * <tt>executeQueryForMap()</tt> first.
251
	 *
252
	 * @param \Prado\Data\TDbConnection $connection database connection
253
	 * @param mixed $parameter The object used to set the parameters in the SQL.
254 34
	 * @param mixed $command
255
	 * @param string $keyProperty The property of the result object to be used as the key.
256 33
	 * @param null|string $valueProperty The property of the result object to be used as the value (or null).
257
	 * @param null|callable $delegate row delegate, a callback function
258
	 * @return array An array of object containing the rows keyed by keyProperty.
259
	 * @see executeQueryForMap()
260 35
	 */
261 1
	public function runQueryForMap($connection, $parameter, $command, $keyProperty, $valueProperty = null, $delegate = null)
262 1
	{
263
		$map = [];
264
		$connection->setActive(true);
265 35
		$reader = $command->query();
266 35
		if ($delegate !== null) {
267
			foreach ($reader as $row) {
268 35
				$obj = $this->applyResultMap($row);
269
				$key = TPropertyAccess::get($obj, $keyProperty);
270
				$value = ($valueProperty === null) ? $obj :
271
							TPropertyAccess::get($obj, $valueProperty);
272
				$param = new TResultSetMapItemParameter($key, $value, $parameter, $map);
273
				$this->raiseRowDelegate($delegate, $param);
274
			}
275
		} else {
276
			foreach ($reader as $row) {
277
				$obj = $this->applyResultMap($row);
278
				$key = TPropertyAccess::get($obj, $keyProperty);
279
				$map[$key] = ($valueProperty === null) ? $obj :
280
								TPropertyAccess::get($obj, $valueProperty);
281
			}
282
		}
283
		$this->onExecuteQuery($command);
284
		return $map;
285 4
	}
286
287 4
	/**
288 4
	 * Raises delegate handler.
289
	 * This method is invoked for each new list item. It is the responsibility
290
	 * of the handler to add the item to the list.
291
	 * @param callable $handler to be executed
292
	 * @param mixed $param event parameter
293
	 */
294
	protected function raiseRowDelegate($handler, $param)
295
	{
296
		if (is_string($handler)) {
297
			call_user_func($handler, $this, $param);
298
		} elseif (is_callable($handler, true)) {
299
			// an array: 0 - object, 1 - method name/path
300
			[$object, $method] = $handler;
301
			if (is_string($object)) {	// static method call
302
				call_user_func($handler, $this, $param);
303
			} else {
304
				if (($pos = strrpos($method, '.')) !== false) {
305
					$object = $this->getSubProperty(substr($method, 0, $pos));
306
					$method = substr($method, $pos + 1);
307
				}
308
				$object->$method($this, $param);
309 4
			}
310
		} else {
311 4
			throw new TInvalidDataValueException('sqlmap_invalid_delegate', $this->getID(), $handler);
312
		}
313 4
	}
314 4
315 4
	/**
316
	 * Executes an SQL statement that returns a single row as an object of the
317 1
	 * type of the <tt>$result</tt> passed in as a parameter.
318 1
	 * @param \Prado\Data\TDbConnection $connection database connection
319 1
	 * @param mixed $parameter The parameter data (object, arrary, primitive) used to set the parameters in the SQL
320 1
	 * @param null|mixed $result The result object.
321 1
	 * @return object the object.
322 1
	 */
323 1
	public function executeQueryForObject($connection, $parameter, $result = null)
324
	{
325
		$sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter);
326
		return $this->runQueryForObject($connection, $sql, $result);
327 3
	}
328 3
329 3
	/**
330 3
	 * Executes an SQL statement that returns a single row as an object of the
331 3
	 * type of the <tt>$result</tt> passed in as a parameter.
332
	 *
333
	 * This method should only be called by internal developers, consider using
334 4
	 * <tt>executeQueryForObject()</tt> first.
335 4
	 *
336
	 * @param \Prado\Data\TDbConnection $connection database connection
337
	 * @param \Prado\Data\TDbCommand $command
338
	 * @param object $result The result object.
339
	 * @return object the object.
340
	 * @see executeQueryForObject()
341
	 */
342
	public function runQueryForObject($connection, $command, &$result)
343
	{
344
		$object = null;
345 2
		$connection->setActive(true);
346
		foreach ($command->query() as $row) {
347 2
			$object = $this->applyResultMap($row, $result);
348
		}
349 2
350
		if (!$this->_groupBy->isEmpty()) {
351 2
			$list = $this->_groupBy->collect();
352 2
			$this->initialGroupByResults();
353
			$object = $list[0];
354
		}
355 2
356
		$this->executePostSelect($connection);
357
		$this->onExecuteQuery($command);
0 ignored issues
show
Bug introduced by
$command of type Prado\Data\TDbCommand is incompatible with the type array expected by parameter $sql of Prado\Data\SqlMap\Statem...ement::onExecuteQuery(). ( Ignorable by Annotation )

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

357
		$this->onExecuteQuery(/** @scrutinizer ignore-type */ $command);
Loading history...
358
359 2
		return $object;
360
	}
361
362
	/**
363
	 * Execute an insert statement. Fill the parameter object with the ouput
364 2
	 * parameters if any, also could return the insert generated key.
365
	 * @param \Prado\Data\TDbConnection $connection database connection
366
	 * @param mixed $parameter The parameter object used to fill the statement.
367
	 * @return string the insert generated key.
368
	 */
369
	public function executeInsert($connection, $parameter)
370
	{
371
		$generatedKey = $this->getPreGeneratedSelectKey($connection, $parameter);
372
373
		$command = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter);
374 86
		$result = $command->execute();
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
375
376 86
		if ($generatedKey === null) {
377 86
			$generatedKey = $this->getPostGeneratedSelectKey($connection, $parameter);
378
		}
379
380
		$this->executePostSelect($connection);
381
		$this->onExecuteQuery($command);
0 ignored issues
show
Bug introduced by
$command of type Prado\Data\TDbCommand is incompatible with the type array expected by parameter $sql of Prado\Data\SqlMap\Statem...ement::onExecuteQuery(). ( Ignorable by Annotation )

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

381
		$this->onExecuteQuery(/** @scrutinizer ignore-type */ $command);
Loading history...
382
		return $generatedKey;
383
	}
384
385
	/**
386
	 * Gets the insert generated ID before executing an insert statement.
387
	 * @param \Prado\Data\TDbConnection $connection database connection
388
	 * @param mixed $parameter insert statement parameter.
389
	 * @return null|string new insert ID if pre-select key statement was executed, null otherwise.
390
	 */
391
	protected function getPreGeneratedSelectKey($connection, $parameter)
392
	{
393 86
		if ($this->_statement instanceof TSqlMapInsert) {
394
			$selectKey = $this->_statement->getSelectKey();
395 86
			if (($selectKey !== null) && !$selectKey->getIsAfter()) {
396 86
				return $this->executeSelectKey($connection, $parameter, $selectKey);
397 86
			}
398 86
		}
399
		return null;
400
	}
401 86
402
	/**
403
	 * Gets the inserted row ID after executing an insert statement.
404
	 * @param \Prado\Data\TDbConnection $connection database connection
405
	 * @param mixed $parameter insert statement parameter.
406
	 * @return null|string last insert ID, null otherwise.
407 86
	 */
408 86
	protected function getPostGeneratedSelectKey($connection, $parameter)
409
	{
410 86
		if ($this->_statement instanceof TSqlMapInsert) {
411
			$selectKey = $this->_statement->getSelectKey();
412
			if (($selectKey !== null) && $selectKey->getIsAfter()) {
413
				return $this->executeSelectKey($connection, $parameter, $selectKey);
414
			}
415
		}
416
		return null;
417
	}
418
419
	/**
420 20
	 * Execute the select key statement, used to obtain last insert ID.
421
	 * @param \Prado\Data\TDbConnection $connection database connection
422 20
	 * @param mixed $parameter insert statement parameter
423
	 * @param TSqlMapSelectKey $selectKey select key statement
424 20
	 * @return string last insert ID.
425
	 */
426 20
	protected function executeSelectKey($connection, $parameter, $selectKey)
427
	{
428 20
		$mappedStatement = $this->getManager()->getMappedStatement($selectKey->getID());
429 19
		$generatedKey = $mappedStatement->executeQueryForObject(
430
			$connection,
431
			$parameter,
432 20
			null
433 20
		);
434 20
		if (strlen($prop = $selectKey->getProperty()) > 0) {
435
			TPropertyAccess::set($parameter, $prop, $generatedKey);
436
		}
437
		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...
438
	}
439
440
	/**
441
	 * Execute an update statement. Also used for delete statement.
442
	 * Return the number of rows effected.
443 20
	 * @param \Prado\Data\TDbConnection $connection database connection
444
	 * @param mixed $parameter The object used to set the parameters in the SQL.
445 20
	 * @return int The number of rows effected.
446 16
	 */
447 16
	public function executeUpdate($connection, $parameter)
448 1
	{
449
		$sql = $this->_command->create($this->getManager(), $connection, $this->_statement, $parameter);
450
		$affectedRows = $sql->execute();
451 19
		$this->executePostSelect($connection);
452
		$this->onExecuteQuery($sql);
0 ignored issues
show
Bug introduced by
$sql of type Prado\Data\TDbCommand is incompatible with the type array expected by parameter $sql of Prado\Data\SqlMap\Statem...ement::onExecuteQuery(). ( Ignorable by Annotation )

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

452
		$this->onExecuteQuery(/** @scrutinizer ignore-type */ $sql);
Loading history...
453
		return $affectedRows;
454
	}
455
456
	/**
457
	 * Process 'select' result properties
458
	 * @param \Prado\Data\TDbConnection $connection database connection
459 19
	 */
460
	protected function executePostSelect($connection)
461 19
	{
462 15
		while (count($this->_selectQueue)) {
463 15
			$postSelect = array_shift($this->_selectQueue);
464 1
			$method = $postSelect->getMethod();
465
			$statement = $postSelect->getStatement();
466
			$property = $postSelect->getResultProperty()->getProperty();
467 18
			$keys = $postSelect->getKeys();
468
			$resultObject = $postSelect->getResultObject();
469
470
			if ($method == self::QUERY_FOR_LIST || $method == self::QUERY_FOR_ARRAY) {
471
				$values = $statement->executeQueryForList($connection, $keys, null);
472
473
				if ($method == self::QUERY_FOR_ARRAY) {
474
					$values = $values->toArray();
475
				}
476 2
				TPropertyAccess::set($resultObject, $property, $values);
477
			} elseif ($method == self::QUERY_FOR_OBJECT) {
478 2
				$value = $statement->executeQueryForObject($connection, $keys, null);
479 2
				TPropertyAccess::set($resultObject, $property, $value);
480 2
			}
481 2
		}
482 2
	}
483
484 2
	/**
485 2
	 * Raise the execute query event.
486
	 * @param array $sql prepared SQL statement and subsititution parameters
487 2
	 */
488
	public function onExecuteQuery($sql)
489
	{
490
		$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

490
		$this->raiseEvent('OnExecuteQuery', $this, /** @scrutinizer ignore-type */ $sql);
Loading history...
491
	}
492
493
	/**
494
	 * Apply result mapping.
495
	 * @param array $row a result set row retrieved from the database
496
	 * @param null|object $resultObject the result object, will create if necessary.
497 8
	 * @return object the result filled with data, null if not filled.
498
	 */
499 8
	protected function applyResultMap($row, &$resultObject = null)
500 8
	{
501
		if ($row === false) {
0 ignored issues
show
introduced by
The condition $row === false is always false.
Loading history...
502 8
			return null;
503 8
		}
504 8
505
		$resultMapName = $this->_statement->getResultMap();
506
		$resultClass = $this->_statement->getResultClass();
507
508
		$obj = null;
509
		if ($this->getManager()->getResultMaps()->contains($resultMapName)) {
510
			$obj = $this->fillResultMap($resultMapName, $row, null, $resultObject);
511 117
		} elseif ($resultClass !== null && strlen($resultClass) > 0) {
512
			$obj = $this->fillResultClass($resultClass, $row, $resultObject);
513 117
		} else {
514 6
			$obj = $this->fillDefaultResultMap(null, $row, $resultObject);
515 6
		}
516 6
		if (class_exists('\Prado\Data\ActiveRecord\TActiveRecord', false) && $obj instanceof TActiveRecord) {
517 6
			//Create a new clean active record.
518 6
			$obj = TActiveRecord::createRecord($obj::class, $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

518
			$obj = TActiveRecord::createRecord($obj::class, /** @scrutinizer ignore-type */ $obj);
Loading history...
519 6
		}
520
		return $obj;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $obj also could return the type array which is incompatible with the documented return type object.
Loading history...
521 6
	}
522 4
523
	/**
524 4
	 * Fill the result using ResultClass, will creates new result object if required.
525 1
	 * @param string $resultClass result object class name
526
	 * @param array $row a result set row retrieved from the database
527 4
	 * @param object $resultObject the result object, will create if necessary.
528 2
	 * @return object result object filled with data
529 2
	 */
530 2
	protected function fillResultClass($resultClass, $row, $resultObject)
531
	{
532
		if ($resultObject === null) {
533 117
			$registry = $this->getManager()->getTypeHandlers();
534
			$resultObject = $this->_statement->createInstanceOfResultClass($registry, $row);
535
		}
536
537
		if ($resultObject instanceof \ArrayAccess) {
538
			return $this->fillResultArrayList($row, $resultObject);
539 120
		} elseif (is_object($resultObject)) {
540
			return $this->fillResultObjectProperty($row, $resultObject);
541 120
		} else {
542 120
			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...
543
		}
544
	}
545
546
	/**
547
	 * Apply the result to a TList or an array.
548
	 * @param array $row a result set row retrieved from the database
549
	 * @param object $resultObject result object, array or list
550 115
	 * @return object result filled with data.
551
	 */
552 115
	protected function fillResultArrayList($row, $resultObject)
553
	{
554
		if ($resultObject instanceof TList) {
555
			foreach ($row as $v) {
556 115
				$resultObject[] = $v;
557 115
			}
558
		} else {
559 115
			foreach ($row as $k => $v) {
560 115
				$resultObject[$k] = $v;
561 69
			}
562 55
		}
563 44
		return $resultObject;
564
	}
565 11
566
	/**
567 115
	 * Apply the result to an object.
568
	 * @param array $row a result set row retrieved from the database
569
	 * @param object $resultObject result object, array or list
570
	 * @return object result filled with data.
571 115
	 */
572
	protected function fillResultObjectProperty($row, $resultObject)
573
	{
574
		$index = 0;
575
		$registry = $this->getManager()->getTypeHandlers();
576
		foreach ($row as $k => $v) {
577
			$property = new TResultProperty();
578
			if (is_string($k) && strlen($k) > 0) {
579
				$property->setColumn($k);
580
			}
581 44
			$property->setColumnIndex(++$index);
582
			$type = gettype(TPropertyAccess::get($resultObject, $k));
583 44
			$property->setType($type);
584 44
			$value = $property->getPropertyValue($registry, $row);
585 44
			TPropertyAccess::set($resultObject, $k, $value);
586
		}
587
		return $resultObject;
588 44
	}
589 2
590 42
	/**
591 23
	 * Fills the result object according to result mappings.
592
	 * @param string $resultMapName result map name.
593 21
	 * @param array $row a result set row retrieved from the database
594
	 * @param null|mixed $parentGroup
595
	 * @param null|object $resultObject result object to fill, will create new instances if required.
596
	 * @return object result object filled with data.
597
	 */
598
	protected function fillResultMap($resultMapName, $row, $parentGroup = null, &$resultObject = null)
599
	{
600
		$resultMap = $this->getManager()->getResultMap($resultMapName);
601
		$registry = $this->getManager()->getTypeHandlers();
602
		$resultMap = $resultMap->resolveSubMap($registry, $row);
603 2
604
		if ($resultObject === null) {
605 2
			$resultObject = $resultMap->createInstanceOfResult($registry);
606 2
		}
607 2
608
		if (is_object($resultObject)) {
609
			$gb = $resultMap->getGroupBy();
610
			if ($gb !== null && strlen($gb) > 0) {
611
				return $this->addResultMapGroupBy($resultMap, $row, $parentGroup, $resultObject);
612
			} else {
613
				foreach ($resultMap->getColumns() as $property) {
614 2
					$this->setObjectProperty($resultMap, $property, $row, $resultObject);
615
				}
616
			}
617
		} else {
618
			$resultObject = $this->fillDefaultResultMap($resultMap, $row, $resultObject);
619
		}
620
		return $resultObject;
621
	}
622
623 23
	/**
624
	 * ResultMap with GroupBy property. Save object collection graph in a tree
625 23
	 * and collect the result later.
626 23
	 * @param TResultMap $resultMap result mapping details.
627 23
	 * @param array $row a result set row retrieved from the database
628 23
	 * @param mixed $parent
629 23
	 * @param object $resultObject the result object
630 23
	 * @return object result object.
631
	 */
632 23
	protected function addResultMapGroupBy($resultMap, $row, $parent, &$resultObject)
633 23
	{
634 23
		$group = $this->getResultMapGroupKey($resultMap, $row);
635 23
636 23
		if (empty($parent)) {
637
			$rootObject = ['object' => $resultObject, 'property' => null];
638 23
			$this->_groupBy->add(null, $group, $rootObject);
639
		}
640
641
		foreach ($resultMap->getColumns() as $property) {
642
			//set properties.
643
			$this->setObjectProperty($resultMap, $property, $row, $resultObject);
644
			$nested = $property->getResultMapping();
645
646
			//nested property
647
			if ($this->getManager()->getResultMaps()->contains($nested)) {
648
				$nestedMap = $this->getManager()->getResultMap($nested);
649 69
				$groupKey = $this->getResultMapGroupKey($nestedMap, $row);
650
651 69
				//add the node reference first
652 69
				if (empty($parent)) {
653 69
					$this->_groupBy->add($group, $groupKey, '');
654
				}
655 69
656 68
				//get the nested result mapping value
657
				$value = $this->fillResultMap($nested, $row, $groupKey);
658
659 69
				//add it to the object tree graph
660 61
				$groupObject = ['object' => $value, 'property' => $property->getProperty()];
661 1
				if (empty($parent)) {
662
					$this->_groupBy->add($group, $groupKey, $groupObject);
663 61
				} else {
664 61
					$this->_groupBy->add($parent, $groupKey, $groupObject);
665
				}
666
			}
667
		}
668 8
		return $resultObject;
669
	}
670 69
671
	/**
672
	 * Gets the result 'group by' groupping key for each row.
673
	 * @param TResultMap $resultMap result mapping details.
674
	 * @param array $row a result set row retrieved from the database
675
	 * @return string groupping key.
676
	 */
677
	protected function getResultMapGroupKey($resultMap, $row)
678
	{
679
		$groupBy = $resultMap->getGroupBy();
680
		if (isset($row[$groupBy])) {
681
			return $resultMap->getID() . $row[$groupBy];
682 1
		} else {
683
			return $resultMap->getID() . crc32(serialize($row));
684 1
		}
685
	}
686 1
687 1
	/**
688 1
	 * Fill the result map using default settings. If <tt>$resultMap</tt> is null
689
	 * the result object returned will be guessed from <tt>$resultObject</tt>.
690
	 * @param TResultMap $resultMap result mapping details.
691 1
	 * @param array $row a result set row retrieved from the database
692
	 * @param object $resultObject the result object
693 1
	 * @return mixed the result object filled with data.
694 1
	 */
695
	protected function fillDefaultResultMap($resultMap, $row, $resultObject)
696
	{
697 1
		if ($resultObject === null) {
698 1
			$resultObject = '';
699 1
		}
700
701
		if ($resultMap !== null) {
702 1
			$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

702
			$result = $this->fillArrayResultMap($resultMap, $row, /** @scrutinizer ignore-type */ $resultObject);
Loading history...
703 1
		} else {
704
			$result = $row;
705
		}
706
707 1
		//if scalar result types
708
		if (count($result) == 1 && ($type = gettype($resultObject)) != 'array') {
709
			return $this->getScalarResult($result, $type);
710 1
		} else {
711 1
			return $result;
712 1
		}
713
	}
714 1
715
	/**
716
	 * Retrieve the result map as an array.
717
	 * @param TResultMap $resultMap result mapping details.
718 1
	 * @param array $row a result set row retrieved from the database
719
	 * @param object $resultObject the result object
720
	 * @return array array list of result objects.
721
	 */
722
	protected function fillArrayResultMap($resultMap, $row, $resultObject)
723
	{
724
		$result = [];
725
		$registry = $this->getManager()->getTypeHandlers();
726
		foreach ($resultMap->getColumns() as $column) {
727 1
			if (($column->getType() === null)
728
				&& ($resultObject !== null) && !is_object($resultObject)) {
729 1
				$column->setType(gettype($resultObject));
730 1
			}
731 1
			$result[$column->getProperty()] = $column->getPropertyValue($registry, $row);
732
		}
733 1
		return $result;
734
	}
735
736
	/**
737
	 * Converts the first array value to scalar value of given type.
738
	 * @param array $result list of results
739
	 * @param string $type scalar type.
740
	 * @return mixed scalar value.
741
	 */
742
	protected function getScalarResult($result, $type)
743
	{
744
		$scalar = array_shift($result);
745 40
		settype($scalar, $type);
746
		return $scalar;
747 40
	}
748 11
749
	/**
750
	 * Set a property of the result object with appropriate value.
751 40
	 * @param TResultMap $resultMap result mapping details.
752 8
	 * @param TResultProperty $property the result property to fill.
753
	 * @param array $row a result set row retrieved from the database
754 32
	 * @param object $resultObject the result object
755
	 */
756
	protected function setObjectProperty($resultMap, $property, $row, &$resultObject)
757
	{
758 40
		$select = $property->getSelect();
759 34
		$key = $property->getProperty();
760
		$nested = $property->getNestedResultMap();
761 6
		$registry = $this->getManager()->getTypeHandlers();
762
		if ($key === '') {
763
			$resultObject = $property->getPropertyValue($registry, $row);
764
		} elseif (($select === null || strlen($select) == 0) && ($nested === null)) {
765
			$value = $property->getPropertyValue($registry, $row);
766
767
			$this->_IsRowDataFound = $this->_IsRowDataFound || ($value != null);
768
			if (is_array($resultObject) || is_object($resultObject)) {
769
				TPropertyAccess::set($resultObject, $key, $value);
770
			} else {
771
				$resultObject = $value;
772 8
			}
773
		} elseif ($nested !== null) {
774 8
			if ($property->instanceOfListType($resultObject) || $property->instanceOfArrayType($resultObject)) {
775 8
				if (strlen($resultMap->getGroupBy()) <= 0) {
776 8
					throw new TSqlMapExecutionException(
777 8
						'sqlmap_non_groupby_array_list_type',
778 8
						$resultMap->getID(),
779 7
						$resultObject::class,
780
						$key
781 8
					);
782
				}
783 8
			} else {
784
				$obj = $nested->createInstanceOfResult($this->getManager()->getTypeHandlers());
785
				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...
786
					$obj = null;
787
				}
788
				TPropertyAccess::set($resultObject, $key, $obj);
789
			}
790
		} else { //'select' ResultProperty
791
			$this->enquequePostSelect($select, $resultMap, $property, $row, $resultObject);
792 34
		}
793
	}
794 34
795 34
	/**
796 34
	 * Add nested result property to post select queue.
797
	 * @param string $select post select statement ID
798
	 * @param TResultMap $resultMap current result mapping details.
799
	 * @param TResultProperty $property current result property.
800
	 * @param array $row a result set row retrieved from the database
801
	 * @param object $resultObject the result object
802
	 */
803
	protected function enquequePostSelect($select, $resultMap, $property, $row, $resultObject)
804
	{
805
		$statement = $this->getManager()->getMappedStatement($select);
806 61
		$key = $this->getPostSelectKeys($resultMap, $property, $row);
807
		$postSelect = new TPostSelectBinding();
808 61
		$postSelect->setStatement($statement);
809 61
		$postSelect->setResultObject($resultObject);
810 61
		$postSelect->setResultProperty($property);
811 61
		$postSelect->setKeys($key);
812 61
813
		if ($property->instanceOfListType($resultObject)) {
814 61
			$values = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $values is dead and can be removed.
Loading history...
815 61
			if ($property->getLazyLoad()) {
816
				$values = TLazyLoadList::newInstance(
817 61
					$statement,
818 61
					$key,
819 61
					$resultObject,
820
					$property->getProperty()
821 61
				);
822
				TPropertyAccess::set($resultObject, $property->getProperty(), $values);
823 14
			} else {
824 4
				$postSelect->setMethod(self::QUERY_FOR_LIST);
825 1
			}
826
		} elseif ($property->instanceOfArrayType($resultObject)) {
827
			$postSelect->setMethod(self::QUERY_FOR_ARRAY);
828
		} else {
829
			$postSelect->setMethod(self::QUERY_FOR_OBJECT);
830 1
		}
831
832
		if (!$property->getLazyLoad()) {
833
			$this->_selectQueue[] = $postSelect;
834 3
		}
835 3
	}
836 2
837
	/**
838 4
	 * Finds in the post select property the SQL statement primary selection keys.
839
	 * @param TResultMap $resultMap result mapping details
840
	 * @param TResultProperty $property result property
841 10
	 * @param array $row current row data.
842
	 * @return array list of primary key values.
843 61
	 */
844
	protected function getPostSelectKeys($resultMap, $property, $row)
845
	{
846
		$value = $property->getColumn();
847
		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...
848
			$keys = [];
849
			foreach (explode(',', $value) as $entry) {
850
				$pair = explode('=', $entry);
851
				$keys[trim($pair[0])] = $row[trim($pair[1])];
852
			}
853 10
			return $keys;
854
		} else {
855 10
			$registry = $this->getManager()->getTypeHandlers();
856 10
			return $property->getPropertyValue($registry, $row);
857 10
		}
858 10
	}
859 10
860 10
	/**
861 10
	 * Fills the property with result mapping results.
862
	 * @param TResultMap $resultMap nested result mapping details.
863 10
	 * @param array $row a result set row retrieved from the database
864 7
	 * @param object $resultObject the result object
865 7
	 * @return bool true if the data was found, false otherwise.
866 4
	 */
867 4
	protected function fillPropertyWithResultMap($resultMap, $row, &$resultObject)
868 4
	{
869 4
		$dataFound = false;
870 4
		foreach ($resultMap->getColumns() as $property) {
871
			$this->_IsRowDataFound = false;
872 4
			$this->setObjectProperty($resultMap, $property, $row, $resultObject);
873
			$dataFound = $dataFound || $this->_IsRowDataFound;
874 7
		}
875
		$this->_IsRowDataFound = $dataFound;
876 3
		return $dataFound;
877 1
	}
878
879 2
	public function __wakeup()
880
	{
881
		if (null === $this->_selectQueue) {
882 10
			$this->_selectQueue = [];
883 6
		}
884
		parent::__wakeup();
885 10
	}
886
887
	public function __sleep()
888
	{
889
		$exprops = [];
890
		$cn = __CLASS__;
891
		if (!count($this->_selectQueue)) {
892
			$exprops[] = "\0$cn\0_selectQueue";
893
		}
894 10
		if (null === $this->_groupBy) {
895
			$exprops[] = "\0$cn\0_groupBy";
896 10
		}
897 10
		if (!$this->_IsRowDataFound) {
898 1
			$exprops[] = "\0$cn\0_IsRowDataFound";
899 1
		}
900 1
		return array_diff(parent::__sleep(), $exprops);
901 1
	}
902
}
903