Passed
Push — master ( a9e422...678640 )
by Fabio
05:24
created

TMappedStatement::applyResultMap()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 14
c 1
b 0
f 0
nc 7
nop 2
dl 0
loc 22
ccs 13
cts 13
cp 1
crap 7
rs 8.8333
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\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
 * @package Prado\Data\SqlMap\Statements
34
 * @since 3.0
35
 */
36
class TMappedStatement extends \Prado\TComponent implements IMappedStatement
37
{
38
	/**
39
	 * @var TSqlMapStatement current SQL statement.
40
	 */
41
	private $_statement;
42
43
	/**
44
	 * @var TPreparedCommand SQL command prepareer
45
	 */
46
	private $_command;
47
48
	/**
49
	 * @var TSqlMapManager sqlmap used by this mapper.
50
	 */
51
	private $_manager;
52
53
	/**
54
	 * @var TPostSelectBinding[] post select statement queue.
55
	 */
56
	private $_selectQueue = [];
57
58
	/**
59
	 * @var bool true when data is mapped to a particular row.
60
	 */
61
	private $_IsRowDataFound = false;
62
63
	/**
64
	 * @var TSqlMapObjectCollectionTree group by object collection tree
65
	 */
66
	private $_groupBy;
67
68
	/**
69
	 * @var int select is to query for list.
70
	 */
71
	public const QUERY_FOR_LIST = 0;
72
73
	/**
74
	 * @var int select is to query for array.
75
	 */
76
	public const QUERY_FOR_ARRAY = 1;
77
78
	/**
79
	 * @var int select is to query for object.
80
	 */
81
	public const QUERY_FOR_OBJECT = 2;
82
83
	/**
84
	 * @return string Name used to identify the TMappedStatement amongst the others.
85 2
	 * This the name of the SQL statement by default.
86
	 */
87 2
	public function getID()
88
	{
89
		return $this->_statement->getID();
90
	}
91
92
	/**
93 9
	 * @return TSqlMapStatement The SQL statment used by this MappedStatement
94
	 */
95 9
	public function getStatement()
96
	{
97
		return $this->_statement;
98
	}
99
100
	/**
101 118
	 * @return TSqlMapManager The SqlMap used by this MappedStatement
102
	 */
103 118
	public function getManager()
104
	{
105
		return $this->_manager;
106
	}
107
108
	/**
109 3
	 * @return TPreparedCommand command to prepare SQL statements.
110
	 */
111 3
	public function getCommand()
112
	{
113
		return $this->_command;
114
	}
115
116
	/**
117 3
	 * Empty the group by results cache.
118
	 */
119 3
	protected function initialGroupByResults()
120 3
	{
121
		$this->_groupBy = new TSqlMapObjectCollectionTree();
122
	}
123
124
	/**
125
	 * Creates a new mapped statement.
126
	 * @param TSqlMapManager $sqlMap an sqlmap.
127 2
	 * @param TSqlMapStatement $statement An SQL statement.
128
	 */
129 2
	public function __construct(TSqlMapManager $sqlMap, TSqlMapStatement $statement)
130 2
	{
131 2
		$this->_manager = $sqlMap;
132 2
		$this->_statement = $statement;
133 2
		$this->_command = new TPreparedCommand();
134
		$this->initialGroupByResults();
135 1
		parent::__construct();
136
	}
137 1
138
	public function getSqlString()
139
	{
140
		return $this->getStatement()->getSqlText()->getPreparedStatement()->getPreparedSql();
141
	}
142
143
	/**
144
	 * Execute SQL Query with limits.
145
	 * @param \Prado\Data\TDbConnection $connection database connection
146
	 * @param \Prado\Data\TDbCommand $command
147
	 * @param int $max The maximum number of rows to return.
148
	 * @param int $skip The number of rows to skip over.
149
	 * @throws TSqlMapExecutionException if execution error or false record set.
150
	 * @return mixed record set if applicable.
151
	 */
152
	protected function executeSQLQueryLimit($connection, $command, $max, $skip)
153
	{
154
		if ($max > -1 || $skip > -1) {
155
			$maxStr = $max > 0 ? ' LIMIT ' . $max : '';
156
			$skipStr = $skip > 0 ? ' OFFSET ' . $skip : '';
157
			$command->setText($command->getText() . $maxStr . $skipStr);
158
		}
159
		$connection->setActive(true);
160
		return $command->query();
161
	}
162
163
	/**
164
	 * Executes the SQL and retuns a List of result objects.
165
	 * @param \Prado\Data\TDbConnection $connection database connection
166
	 * @param mixed $parameter The object used to set the parameters in the SQL.
167
	 * @param null|object $result result collection object.
168
	 * @param int $skip The number of rows to skip over.
169
	 * @param int $max The maximum number of rows to return.
170
	 * @param null|callable $delegate row delegate handler
171
	 * @return array a list of result objects
172
	 * @see executeQueryForList()
173
	 */
174
	public function executeQueryForList($connection, $parameter, $result = null, $skip = -1, $max = -1, $delegate = null)
175
	{
176
		$sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter, $skip, $max);
177
		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...
178
	}
179
180
	/**
181
	 * Executes the SQL and retuns a List of result objects.
182
	 *
183
	 * This method should only be called by internal developers, consider using
184
	 * <tt>executeQueryForList()</tt> first.
185
	 *
186
	 * @param \Prado\Data\TDbConnection $connection database connection
187
	 * @param mixed $parameter The object used to set the parameters in the SQL.
188
	 * @param \Prado\Data\TDbCommand $sql SQL string and subsititution parameters.
189
	 * @param object $result result collection object.
190
	 * @param null|callable $delegate row delegate handler
191
	 * @return array a list of result objects
192
	 * @see executeQueryForList()
193
	 */
194
	public function runQueryForList($connection, $parameter, $sql, $result, $delegate = null)
195
	{
196
		$registry = $this->getManager()->getTypeHandlers();
197
		$list = $result instanceof \ArrayAccess ? $result :
198
							$this->_statement->createInstanceOfListClass($registry);
199
		$connection->setActive(true);
200
		$reader = $sql->query();
201
		if ($delegate !== null) {
202
			foreach ($reader as $row) {
203
				$obj = $this->applyResultMap($row);
204
				$param = new TResultSetListItemParameter($obj, $parameter, $list);
205
				$this->raiseRowDelegate($delegate, $param);
206
			}
207
		} else {
208
			foreach ($reader as $row) {
209
				$list[] = $this->applyResultMap($row);
210
			}
211
		}
212
213
		if (!$this->_groupBy->isEmpty()) {
214
			$list = $this->_groupBy->collect();
215
			$this->initialGroupByResults();
216
		}
217
218 32
		$this->executePostSelect($connection);
219
		$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

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

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

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

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

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

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

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