Test Failed
Branch master (206474)
by Fabio
18:24
created

TMappedStatement::applyResultMap()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 15
nc 7
nop 2
dl 0
loc 23
rs 8.5906
c 0
b 0
f 0
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
 * @copyright Copyright &copy; 2005-2016 The PRADO Group
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 * @package Prado\Data\SqlMap\Statements
10
 */
11
12
namespace Prado\Data\SqlMap\Statements;
13
14
use Prado\Collections\TList;
15
use Prado\Data\ActiveRecord\TActiveRecord;
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\DataMapper\TLazyLoadList;
20
use Prado\Data\SqlMap\DataMapper\TPropertyAccess;
21
use Prado\Data\SqlMap\DataMapper\TSqlMapExecutionException;
22
use Prado\Data\SqlMap\TSqlMapManager;
23
use Prado\Exceptions\TInvalidDataValueException;
24
25
/**
26
 * TMappedStatement class executes SQL mapped statements. Mapped Statements can
27
 * hold any SQL statement and use Parameter Maps and Result Maps for input and output.
28
 *
29
 * This class is usualy instantiated during SQLMap configuration by TSqlDomBuilder.
30
 *
31
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
32
 * @package Prado\Data\SqlMap\Statements
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 TSqlMapper 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 Post select is to query for list.
69
	 */
70
	const QUERY_FOR_LIST = 0;
71
72
	/**
73
	 * @var Post select is to query for list.
74
	 */
75
	const QUERY_FOR_ARRAY = 1;
76
77
	/**
78
	 * @var Post select is to query for object.
79
	 */
80
	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
	 */
86
	public function getID()
87
	{
88
		return $this->_statement->ID;
89
	}
90
91
	/**
92
	 * @return TSqlMapStatement The SQL statment used by this MappedStatement
93
	 */
94
	public function getStatement()
95
	{
96
		return $this->_statement;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->_statement; (Prado\Data\SqlMap\Configuration\TSqlMapStatement) is incompatible with the return type declared by the interface Prado\Data\SqlMap\Statem...Statement::getStatement of type Prado\Data\SqlMap\Statements\TSqlMapStatement.

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
97
	}
98
99
	/**
100
	 * @return TSqlMapper The SqlMap used by this MappedStatement
101
	 */
102
	public function getManager()
103
	{
104
		return $this->_manager;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->_manager; (Prado\Data\SqlMap\Statements\TSqlMapper) is incompatible with the return type declared by the interface Prado\Data\SqlMap\Statem...edStatement::getManager of type Prado\Data\SqlMap\Statements\TSqlMap.

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
105
	}
106
107
	/**
108
	 * @return TPreparedCommand command to prepare SQL statements.
109
	 */
110
	public function getCommand()
111
	{
112
		return $this->_command;
113
	}
114
115
	/**
116
	 * Empty the group by results cache.
117
	 */
118
	protected function initialGroupByResults()
119
	{
120
		$this->_groupBy = new TSqlMapObjectCollectionTree();
121
	}
122
123
	/**
124
	 * Creates a new mapped statement.
125
	 * @param TSqlMapper $sqlMap an sqlmap.
126
	 * @param TSqlMapStatement $statement An SQL statement.
127
	 */
128
	public function __construct(TSqlMapManager $sqlMap, TSqlMapStatement $statement)
129
	{
130
		$this->_manager = $sqlMap;
0 ignored issues
show
Documentation Bug introduced by
It seems like $sqlMap of type object<Prado\Data\SqlMap\TSqlMapManager> is incompatible with the declared type object<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...
131
		$this->_statement = $statement;
132
		$this->_command = new TPreparedCommand();
133
		$this->initialGroupByResults();
134
	}
135
136
	public function getSqlString()
137
	{
138
		return $this->getStatement()->getSqlText()->getPreparedStatement()->getPreparedSql();
139
	}
140
141
	/**
142
	 * Execute SQL Query.
143
	 * @param IDbConnection $connection database connection
144
	 * @param array $sql SQL statement and parameters.
145
	 * @throws TSqlMapExecutionException if execution error or false record set.
146
	 * @throws TSqlMapQueryExecutionException if any execution error
147
	 * @return mixed record set if applicable.
148
	 */
149
	/*	protected function executeSQLQuery($connection, $sql)
150
		{
151
			try
152
			{
153
				if(!($recordSet = $connection->execute($sql['sql'],$sql['parameters'])))
154
				{
155
					throw new TSqlMapExecutionException(
156
						'sqlmap_execution_error_no_record', $this->getID(),
157
						$connection->ErrorMsg());
158
				}
159
				return $recordSet;
160
			}
161
			catch (Exception $e)
162
			{
163
				throw new TSqlMapQueryExecutionException($this->getStatement(), $e);
164
			}
165
		}*/
166
167
	/**
168
	 * Execute SQL Query with limits.
169
	 * @param IDbConnection $connection database connection
170
	 * @param $command
171
	 * @param int $max The maximum number of rows to return.
172
	 * @param int $skip The number of rows to skip over.
173
	 * @throws TSqlMapExecutionException if execution error or false record set.
174
	 * @throws TSqlMapQueryExecutionException if any execution error
175
	 * @return mixed record set if applicable.
176
	 */
177
	protected function executeSQLQueryLimit($connection, $command, $max, $skip)
178
	{
179
		if ($max > -1 || $skip > -1) {
180
			$maxStr = $max > 0 ? ' LIMIT ' . $max : '';
181
			$skipStr = $skip > 0 ? ' OFFSET ' . $skip : '';
182
			$command->setText($command->getText() . $maxStr . $skipStr);
183
		}
184
		$connection->setActive(true);
185
		return $command->query();
186
187
		/*//var_dump($command);
188
		try
189
		{
190
			$recordSet = $connection->selectLimit($sql['sql'],$max,$skip,$sql['parameters']);
191
			if(!$recordSet)
192
			{
193
				throw new TSqlMapExecutionException(
194
							'sqlmap_execution_error_query_for_list',
195
							$connection->ErrorMsg());
196
			}
197
			return $recordSet;
198
		}
199
		catch (Exception $e)
200
		{
201
			throw new TSqlMapQueryExecutionException($this->getStatement(), $e);
202
		}*/
203
	}
204
205
	/**
206
	 * Executes the SQL and retuns a List of result objects.
207
	 * @param IDbConnection $connection database connection
208
	 * @param mixed $parameter The object used to set the parameters in the SQL.
209
	 * @param null|object $result result collection object.
210
	 * @param int $skip The number of rows to skip over.
211
	 * @param int $max The maximum number of rows to return.
212
	 * @param null|callable $delegate row delegate handler
213
	 * @return array a list of result objects
214
	 * @see executeQueryForList()
215
	 */
216
	public function executeQueryForList($connection, $parameter, $result = null, $skip = -1, $max = -1, $delegate = null)
217
	{
218
		$sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter, $skip, $max);
219
		return $this->runQueryForList($connection, $parameter, $sql, $result, $delegate);
0 ignored issues
show
Bug introduced by
It seems like $result defined by parameter $result on line 216 can also be of type null; however, Prado\Data\SqlMap\Statem...ment::runQueryForList() does only seem to accept object, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug Best Practice introduced by
The return type of return $this->runQueryFo...l, $result, $delegate); (array|ArrayAccess) is incompatible with the return type declared by the interface Prado\Data\SqlMap\Statem...nt::executeQueryForList of type Prado\Data\SqlMap\Statements\TList.

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
220
	}
221
222
	/**
223
	 * Executes the SQL and retuns a List of result objects.
224
	 *
225
	 * This method should only be called by internal developers, consider using
226
	 * <tt>executeQueryForList()</tt> first.
227
	 *
228
	 * @param IDbConnection $connection database connection
229
	 * @param mixed $parameter The object used to set the parameters in the SQL.
230
	 * @param array $sql SQL string and subsititution parameters.
231
	 * @param object $result result collection object.
232
	 * @param null|callable $delegate row delegate handler
233
	 * @return array a list of result objects
234
	 * @see executeQueryForList()
235
	 */
236
	public function runQueryForList($connection, $parameter, $sql, $result, $delegate = null)
237
	{
238
		$registry = $this->getManager()->getTypeHandlers();
239
		$list = $result instanceof \ArrayAccess ? $result :
240
							$this->_statement->createInstanceOfListClass($registry);
241
		$connection->setActive(true);
242
		$reader = $sql->query();
0 ignored issues
show
Bug introduced by
The method query cannot be called on $sql (of type array).

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

Loading history...
243
		//$reader = $this->executeSQLQueryLimit($connection, $sql, $max, $skip);
244
		if ($delegate !== null) {
245
			foreach ($reader as $row) {
246
				$obj = $this->applyResultMap($row);
247
				$param = new TResultSetListItemParameter($obj, $parameter, $list);
248
				$this->raiseRowDelegate($delegate, $param);
249
			}
250
		} else {
251
			//var_dump($sql,$parameter);
252
			foreach ($reader as $row) {
253
//				var_dump($row);
254
				$list[] = $this->applyResultMap($row);
255
			}
256
		}
257
258
		if (!$this->_groupBy->isEmpty()) {
259
			$list = $this->_groupBy->collect();
260
			$this->initialGroupByResults();
261
		}
262
263
		$this->executePostSelect($connection);
264
		$this->onExecuteQuery($sql);
265
266
		return $list;
267
	}
268
269
	/**
270
	 * Executes the SQL and retuns all rows selected in a map that is keyed on
271
	 * the property named in the keyProperty parameter.  The value at each key
272
	 * will be the value of the property specified in the valueProperty parameter.
273
	 * If valueProperty is null, the entire result object will be entered.
274
	 * @param IDbConnection $connection database connection
275
	 * @param mixed $parameter The object used to set the parameters in the SQL.
276
	 * @param string $keyProperty The property of the result object to be used as the key.
277
	 * @param null|string $valueProperty The property of the result object to be used as the value (or null).
278
	 * @param int $skip The number of rows to skip over.
279
	 * @param int $max The maximum number of rows to return.
280
	 * @param null|callable $delegate row delegate handler
281
	 * @return array An array of object containing the rows keyed by keyProperty.
282
	 */
283
	public function executeQueryForMap($connection, $parameter, $keyProperty, $valueProperty = null, $skip = -1, $max = -1, $delegate = null)
284
	{
285
		$sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter, $skip, $max);
286
		return $this->runQueryForMap($connection, $parameter, $sql, $keyProperty, $valueProperty, $delegate);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->runQueryFo...ueProperty, $delegate); (array) is incompatible with the return type declared by the interface Prado\Data\SqlMap\Statem...ent::executeQueryForMap of type Prado\Data\SqlMap\Statements\TMap.

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
287
	}
288
289
	/**
290
	 * Executes the SQL and retuns all rows selected in a map that is keyed on
291
	 * the property named in the keyProperty parameter.  The value at each key
292
	 * will be the value of the property specified in the valueProperty parameter.
293
	 * If valueProperty is null, the entire result object will be entered.
294
	 *
295
	 * This method should only be called by internal developers, consider using
296
	 * <tt>executeQueryForMap()</tt> first.
297
	 *
298
	 * @param IDbConnection $connection database connection
299
	 * @param mixed $parameter The object used to set the parameters in the SQL.
300
	 * @param mixed $command
301
	 * @param string $keyProperty The property of the result object to be used as the key.
302
	 * @param null|string $valueProperty The property of the result object to be used as the value (or null).
303
	 * @param null|callable $delegate row delegate, a callback function
304
	 * @return array An array of object containing the rows keyed by keyProperty.
305
	 * @see executeQueryForMap()
306
	 */
307
	public function runQueryForMap($connection, $parameter, $command, $keyProperty, $valueProperty = null, $delegate = null)
308
	{
309
		$map = [];
310
		//$recordSet = $this->executeSQLQuery($connection, $sql);
311
		$connection->setActive(true);
312
		$reader = $command->query();
313
		if ($delegate !== null) {
314
			//while($row = $recordSet->fetchRow())
315
			foreach ($reader as $row) {
316
				$obj = $this->applyResultMap($row);
317
				$key = TPropertyAccess::get($obj, $keyProperty);
318
				$value = ($valueProperty === null) ? $obj :
319
							TPropertyAccess::get($obj, $valueProperty);
320
				$param = new TResultSetMapItemParameter($key, $value, $parameter, $map);
321
				$this->raiseRowDelegate($delegate, $param);
322
			}
323
		} else {
324
			//while($row = $recordSet->fetchRow())
325
			foreach ($reader as $row) {
326
				$obj = $this->applyResultMap($row);
327
				$key = TPropertyAccess::get($obj, $keyProperty);
328
				$map[$key] = ($valueProperty === null) ? $obj :
329
								TPropertyAccess::get($obj, $valueProperty);
330
			}
331
		}
332
		$this->onExecuteQuery($command);
333
		return $map;
334
	}
335
336
	/**
337
	 * Raises delegate handler.
338
	 * This method is invoked for each new list item. It is the responsibility
339
	 * of the handler to add the item to the list.
340
	 * @param object $handler event parameter
341
	 * @param mixed $param
342
	 */
343
	protected function raiseRowDelegate($handler, $param)
344
	{
345
		if (is_string($handler)) {
346
			call_user_func($handler, $this, $param);
347
		} elseif (is_callable($handler, true)) {
348
			// an array: 0 - object, 1 - method name/path
349
			list($object, $method) = $handler;
350
			if (is_string($object)) {	// static method call
351
				call_user_func($handler, $this, $param);
352 View Code Duplication
			} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
353
				if (($pos = strrpos($method, '.')) !== false) {
354
					$object = $this->getSubProperty(substr($method, 0, $pos));
355
					$method = substr($method, $pos + 1);
356
				}
357
				$object->$method($this, $param);
358
			}
359
		} else {
360
			throw new TInvalidDataValueException('sqlmap_invalid_delegate', $this->getID(), $handler);
361
		}
362
	}
363
364
	/**
365
	 * Executes an SQL statement that returns a single row as an object of the
366
	 * type of the <tt>$result</tt> passed in as a parameter.
367
	 * @param IDbConnection $connection database connection
368
	 * @param mixed $parameter The parameter data (object, arrary, primitive) used to set the parameters in the SQL
369
	 * @param null|mixed $result The result object.
370
	 * @return object the object.
371
	 */
372
	public function executeQueryForObject($connection, $parameter, $result = null)
373
	{
374
		$sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter);
375
		return $this->runQueryForObject($connection, $sql, $result);
376
	}
377
378
	/**
379
	 * Executes an SQL statement that returns a single row as an object of the
380
	 * type of the <tt>$result</tt> passed in as a parameter.
381
	 *
382
	 * This method should only be called by internal developers, consider using
383
	 * <tt>executeQueryForObject()</tt> first.
384
	 *
385
	 * @param IDbConnection $connection database connection
386
	 * @param $command
387
	 * @param object &$result The result object.
388
	 * @return object the object.
389
	 * @see executeQueryForObject()
390
	 */
391
	public function runQueryForObject($connection, $command, &$result)
392
	{
393
		$object = null;
394
		$connection->setActive(true);
395
		foreach ($command->query() as $row) {
396
			$object = $this->applyResultMap($row, $result);
397
		}
398
399
		if (!$this->_groupBy->isEmpty()) {
400
			$list = $this->_groupBy->collect();
401
			$this->initialGroupByResults();
402
			$object = $list[0];
403
		}
404
405
		$this->executePostSelect($connection);
406
		$this->onExecuteQuery($command);
407
408
		return $object;
409
	}
410
411
	/**
412
	 * Execute an insert statement. Fill the parameter object with the ouput
413
	 * parameters if any, also could return the insert generated key.
414
	 * @param IDbConnection $connection database connection
415
	 * @param mixed $parameter The parameter object used to fill the statement.
416
	 * @return string the insert generated key.
417
	 */
418
	public function executeInsert($connection, $parameter)
419
	{
420
		$generatedKey = $this->getPreGeneratedSelectKey($connection, $parameter);
421
422
		$command = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter);
423
//		var_dump($command,$parameter);
424
		$result = $command->execute();
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
425
426
		if ($generatedKey === null) {
427
			$generatedKey = $this->getPostGeneratedSelectKey($connection, $parameter);
428
		}
429
430
		$this->executePostSelect($connection);
431
		$this->onExecuteQuery($command);
432
		return $generatedKey;
433
	}
434
435
	/**
436
	 * Gets the insert generated ID before executing an insert statement.
437
	 * @param IDbConnection $connection database connection
438
	 * @param mixed $parameter insert statement parameter.
439
	 * @return string new insert ID if pre-select key statement was executed, null otherwise.
440
	 */
441 View Code Duplication
	protected function getPreGeneratedSelectKey($connection, $parameter)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
442
	{
443
		if ($this->_statement instanceof TSqlMapInsert) {
444
			$selectKey = $this->_statement->getSelectKey();
445
			if (($selectKey !== null) && !$selectKey->getIsAfter()) {
446
				return $this->executeSelectKey($connection, $parameter, $selectKey);
447
			}
448
		}
449
	}
450
451
	/**
452
	 * Gets the inserted row ID after executing an insert statement.
453
	 * @param IDbConnection $connection database connection
454
	 * @param mixed $parameter insert statement parameter.
455
	 * @return string last insert ID, null otherwise.
456
	 */
457 View Code Duplication
	protected function getPostGeneratedSelectKey($connection, $parameter)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
458
	{
459
		if ($this->_statement instanceof TSqlMapInsert) {
460
			$selectKey = $this->_statement->getSelectKey();
461
			if (($selectKey !== null) && $selectKey->getIsAfter()) {
462
				return $this->executeSelectKey($connection, $parameter, $selectKey);
463
			}
464
		}
465
	}
466
467
	/**
468
	 * Execute the select key statement, used to obtain last insert ID.
469
	 * @param IDbConnection $connection database connection
470
	 * @param mixed $parameter insert statement parameter
471
	 * @param TSqlMapSelectKey $selectKey select key statement
472
	 * @return string last insert ID.
473
	 */
474
	protected function executeSelectKey($connection, $parameter, $selectKey)
475
	{
476
		$mappedStatement = $this->getManager()->getMappedStatement($selectKey->getID());
477
		$generatedKey = $mappedStatement->executeQueryForObject(
478
									$connection,
479
			$parameter,
480
			null
481
		);
482
		if (strlen($prop = $selectKey->getProperty()) > 0) {
483
			TPropertyAccess::set($parameter, $prop, $generatedKey);
484
		}
485
		return $generatedKey;
486
	}
487
488
	/**
489
	 * Execute an update statement. Also used for delete statement.
490
	 * Return the number of rows effected.
491
	 * @param IDbConnection $connection database connection
492
	 * @param mixed $parameter The object used to set the parameters in the SQL.
493
	 * @return int The number of rows effected.
494
	 */
495
	public function executeUpdate($connection, $parameter)
496
	{
497
		$sql = $this->_command->create($this->getManager(), $connection, $this->_statement, $parameter);
498
		$affectedRows = $sql->execute();
499
		//$this->executeSQLQuery($connection, $sql);
500
		$this->executePostSelect($connection);
501
		$this->onExecuteQuery($sql);
502
		return $affectedRows;
503
	}
504
505
	/**
506
	 * Process 'select' result properties
507
	 * @param IDbConnection $connection database connection
508
	 */
509
	protected function executePostSelect($connection)
510
	{
511
		while (count($this->_selectQueue)) {
512
			$postSelect = array_shift($this->_selectQueue);
513
			$method = $postSelect->getMethod();
514
			$statement = $postSelect->getStatement();
515
			$property = $postSelect->getResultProperty()->getProperty();
516
			$keys = $postSelect->getKeys();
517
			$resultObject = $postSelect->getResultObject();
518
519
			if ($method == self::QUERY_FOR_LIST || $method == self::QUERY_FOR_ARRAY) {
520
				$values = $statement->executeQueryForList($connection, $keys, null);
521
522
				if ($method == self::QUERY_FOR_ARRAY) {
523
					$values = $values->toArray();
524
				}
525
				TPropertyAccess::set($resultObject, $property, $values);
526
			} elseif ($method == self::QUERY_FOR_OBJECT) {
527
				$value = $statement->executeQueryForObject($connection, $keys, null);
528
				TPropertyAccess::set($resultObject, $property, $value);
529
			}
530
		}
531
	}
532
533
	/**
534
	 * Raise the execute query event.
535
	 * @param array $sql prepared SQL statement and subsititution parameters
536
	 */
537
	public function onExecuteQuery($sql)
538
	{
539
		$this->raiseEvent('OnExecuteQuery', $this, $sql);
540
	}
541
542
	/**
543
	 * Apply result mapping.
544
	 * @param array $row a result set row retrieved from the database
545
	 * @param null|&object $resultObject the result object, will create if necessary.
0 ignored issues
show
Documentation introduced by
The doc-type null|&object could not be parsed: Unknown type name "&object" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
546
	 * @return object the result filled with data, null if not filled.
547
	 */
548
	protected function applyResultMap($row, &$resultObject = null)
549
	{
550
		if ($row === false) {
551
			return null;
552
		}
553
554
		$resultMapName = $this->_statement->getResultMap();
555
		$resultClass = $this->_statement->getResultClass();
556
557
		$obj = null;
0 ignored issues
show
Unused Code introduced by
$obj is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
558
		if ($this->getManager()->getResultMaps()->contains($resultMapName)) {
559
			$obj = $this->fillResultMap($resultMapName, $row, null, $resultObject);
560
		} elseif (strlen($resultClass) > 0) {
561
			$obj = $this->fillResultClass($resultClass, $row, $resultObject);
562
		} else {
563
			$obj = $this->fillDefaultResultMap(null, $row, $resultObject);
564
		}
565
		if (class_exists('TActiveRecord', false) && $obj instanceof TActiveRecord) {
566
			//Create a new clean active record.
567
			$obj = TActiveRecord::createRecord(get_class($obj), $obj);
568
		}
569
		return $obj;
570
	}
571
572
	/**
573
	 * Fill the result using ResultClass, will creates new result object if required.
574
	 * @param string $resultClass result object class name
575
	 * @param array $row a result set row retrieved from the database
576
	 * @param object $resultObject the result object, will create if necessary.
577
	 * @return object result object filled with data
578
	 */
579
	protected function fillResultClass($resultClass, $row, $resultObject)
580
	{
581
		if ($resultObject === null) {
582
			$registry = $this->getManager()->getTypeHandlers();
583
			$resultObject = $this->_statement->createInstanceOfResultClass($registry, $row);
584
		}
585
586
		if ($resultObject instanceof \ArrayAccess) {
587
			return $this->fillResultArrayList($row, $resultObject);
588
		} elseif (is_object($resultObject)) {
589
			return $this->fillResultObjectProperty($row, $resultObject);
590
		} else {
591
			return $this->fillDefaultResultMap(null, $row, $resultObject);
592
		}
593
	}
594
595
	/**
596
	 * Apply the result to a TList or an array.
597
	 * @param array $row a result set row retrieved from the database
598
	 * @param object $resultObject result object, array or list
599
	 * @return object result filled with data.
600
	 */
601
	protected function fillResultArrayList($row, $resultObject)
602
	{
603
		if ($resultObject instanceof TList) {
604
			foreach ($row as $v) {
605
				$resultObject[] = $v;
606
			}
607
		} else {
608
			foreach ($row as $k => $v) {
609
				$resultObject[$k] = $v;
610
			}
611
		}
612
		return $resultObject;
613
	}
614
615
	/**
616
	 * Apply the result to an object.
617
	 * @param array $row a result set row retrieved from the database
618
	 * @param object $resultObject result object, array or list
619
	 * @return object result filled with data.
620
	 */
621
	protected function fillResultObjectProperty($row, $resultObject)
622
	{
623
		$index = 0;
624
		$registry = $this->getManager()->getTypeHandlers();
625
		foreach ($row as $k => $v) {
626
			$property = new TResultProperty;
627
			if (is_string($k) && strlen($k) > 0) {
628
				$property->setColumn($k);
629
			}
630
			$property->setColumnIndex(++$index);
631
			$type = gettype(TPropertyAccess::get($resultObject, $k));
632
			$property->setType($type);
633
			$value = $property->getPropertyValue($registry, $row);
634
			TPropertyAccess::set($resultObject, $k, $value);
635
		}
636
		return $resultObject;
637
	}
638
639
	/**
640
	 * Fills the result object according to result mappings.
641
	 * @param string $resultMapName result map name.
642
	 * @param array $row a result set row retrieved from the database
643
	 * @param null|mixed $parentGroup
644
	 * @param null|&object $resultObject result object to fill, will create new instances if required.
0 ignored issues
show
Documentation introduced by
The doc-type null|&object could not be parsed: Unknown type name "&object" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
645
	 * @return object result object filled with data.
646
	 */
647
	protected function fillResultMap($resultMapName, $row, $parentGroup = null, &$resultObject = null)
648
	{
649
		$resultMap = $this->getManager()->getResultMap($resultMapName);
650
		$registry = $this->getManager()->getTypeHandlers();
651
		$resultMap = $resultMap->resolveSubMap($registry, $row);
652
653
		if ($resultObject === null) {
654
			$resultObject = $resultMap->createInstanceOfResult($registry);
655
		}
656
657
		if (is_object($resultObject)) {
658
			if (strlen($resultMap->getGroupBy()) > 0) {
659
				return $this->addResultMapGroupBy($resultMap, $row, $parentGroup, $resultObject);
660
			} else {
661
				foreach ($resultMap->getColumns() as $property) {
662
					$this->setObjectProperty($resultMap, $property, $row, $resultObject);
663
				}
664
			}
665
		} else {
666
			$resultObject = $this->fillDefaultResultMap($resultMap, $row, $resultObject);
667
		}
668
		return $resultObject;
669
	}
670
671
	/**
672
	 * ResultMap with GroupBy property. Save object collection graph in a tree
673
	 * and collect the result later.
674
	 * @param TResultMap $resultMap result mapping details.
675
	 * @param array $row a result set row retrieved from the database
676
	 * @param mixed $parent
677
	 * @param object &$resultObject the result object
678
	 * @return object result object.
679
	 */
680
	protected function addResultMapGroupBy($resultMap, $row, $parent, &$resultObject)
681
	{
682
		$group = $this->getResultMapGroupKey($resultMap, $row);
683
684
		if (empty($parent)) {
685
			$rootObject = ['object' => $resultObject, 'property' => null];
686
			$this->_groupBy->add(null, $group, $rootObject);
687
		}
688
689
		foreach ($resultMap->getColumns() as $property) {
690
			//set properties.
691
			$this->setObjectProperty($resultMap, $property, $row, $resultObject);
692
			$nested = $property->getResultMapping();
693
694
			//nested property
695
			if ($this->getManager()->getResultMaps()->contains($nested)) {
696
				$nestedMap = $this->getManager()->getResultMap($nested);
697
				$groupKey = $this->getResultMapGroupKey($nestedMap, $row);
698
699
				//add the node reference first
700
				if (empty($parent)) {
701
					$this->_groupBy->add($group, $groupKey, '');
702
				}
703
704
				//get the nested result mapping value
705
				$value = $this->fillResultMap($nested, $row, $groupKey);
706
707
				//add it to the object tree graph
708
				$groupObject = ['object' => $value, 'property' => $property->getProperty()];
709
				if (empty($parent)) {
710
					$this->_groupBy->add($group, $groupKey, $groupObject);
711
				} else {
712
					$this->_groupBy->add($parent, $groupKey, $groupObject);
713
				}
714
			}
715
		}
716
		return $resultObject;
717
	}
718
719
	/**
720
	 * Gets the result 'group by' groupping key for each row.
721
	 * @param TResultMap $resultMap result mapping details.
722
	 * @param array $row a result set row retrieved from the database
723
	 * @return string groupping key.
724
	 */
725
	protected function getResultMapGroupKey($resultMap, $row)
726
	{
727
		$groupBy = $resultMap->getGroupBy();
728
		if (isset($row[$groupBy])) {
729
			return $resultMap->getID() . $row[$groupBy];
730
		} else {
731
			return $resultMap->getID() . crc32(serialize($row));
732
		}
733
	}
734
735
	/**
736
	 * Fill the result map using default settings. If <tt>$resultMap</tt> is null
737
	 * the result object returned will be guessed from <tt>$resultObject</tt>.
738
	 * @param TResultMap $resultMap result mapping details.
739
	 * @param array $row a result set row retrieved from the database
740
	 * @param object $resultObject the result object
741
	 * @return mixed the result object filled with data.
742
	 */
743
	protected function fillDefaultResultMap($resultMap, $row, $resultObject)
744
	{
745
		if ($resultObject === null) {
746
			$resultObject = '';
747
		}
748
749
		if ($resultMap !== null) {
750
			$result = $this->fillArrayResultMap($resultMap, $row, $resultObject);
0 ignored issues
show
Bug introduced by
It seems like $resultObject defined by '' on line 746 can also be of type string; however, Prado\Data\SqlMap\Statem...t::fillArrayResultMap() does only seem to accept object, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
751
		} else {
752
			$result = $row;
753
		}
754
755
		//if scalar result types
756
		if (count($result) == 1 && ($type = gettype($resultObject)) != 'array') {
757
			return $this->getScalarResult($result, $type);
758
		} else {
759
			return $result;
760
		}
761
	}
762
763
	/**
764
	 * Retrieve the result map as an array.
765
	 * @param TResultMap $resultMap result mapping details.
766
	 * @param array $row a result set row retrieved from the database
767
	 * @param object $resultObject the result object
768
	 * @return array array list of result objects.
769
	 */
770
	protected function fillArrayResultMap($resultMap, $row, $resultObject)
771
	{
772
		$result = [];
773
		$registry = $this->getManager()->getTypeHandlers();
774
		foreach ($resultMap->getColumns() as $column) {
775
			if (($column->getType() === null)
776
				&& ($resultObject !== null) && !is_object($resultObject)) {
777
				$column->setType(gettype($resultObject));
778
			}
779
			$result[$column->getProperty()] = $column->getPropertyValue($registry, $row);
780
		}
781
		return $result;
782
	}
783
784
	/**
785
	 * Converts the first array value to scalar value of given type.
786
	 * @param array $result list of results
787
	 * @param string $type scalar type.
788
	 * @return mixed scalar value.
789
	 */
790
	protected function getScalarResult($result, $type)
791
	{
792
		$scalar = array_shift($result);
793
		settype($scalar, $type);
794
		return $scalar;
795
	}
796
797
	/**
798
	 * Set a property of the result object with appropriate value.
799
	 * @param TResultMap $resultMap result mapping details.
800
	 * @param TResultProperty $property the result property to fill.
801
	 * @param array $row a result set row retrieved from the database
802
	 * @param object &$resultObject the result object
803
	 */
804
	protected function setObjectProperty($resultMap, $property, $row, &$resultObject)
805
	{
806
		$select = $property->getSelect();
807
		$key = $property->getProperty();
808
		$nested = $property->getNestedResultMap();
809
		$registry = $this->getManager()->getTypeHandlers();
810
		if ($key === '') {
811
			$resultObject = $property->getPropertyValue($registry, $row);
812
		} elseif (strlen($select) == 0 && ($nested === null)) {
813
			$value = $property->getPropertyValue($registry, $row);
814
815
			$this->_IsRowDataFound = $this->_IsRowDataFound || ($value != null);
816
			if (is_array($resultObject) || is_object($resultObject)) {
817
				TPropertyAccess::set($resultObject, $key, $value);
818
			} else {
819
				$resultObject = $value;
820
			}
821
		} elseif ($nested !== null) {
822
			if ($property->instanceOfListType($resultObject) || $property->instanceOfArrayType($resultObject)) {
823
				if (strlen($resultMap->getGroupBy()) <= 0) {
824
					throw new TSqlMapExecutionException(
825
						'sqlmap_non_groupby_array_list_type',
826
						$resultMap->getID(),
827
						get_class($resultObject),
828
						$key
829
					);
830
				}
831
			} else {
832
				$obj = $nested->createInstanceOfResult($this->getManager()->getTypeHandlers());
833
				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...
834
					$obj = null;
835
				}
836
				TPropertyAccess::set($resultObject, $key, $obj);
837
			}
838
		} else { //'select' ResultProperty
839
			$this->enquequePostSelect($select, $resultMap, $property, $row, $resultObject);
840
		}
841
	}
842
843
	/**
844
	 * Add nested result property to post select queue.
845
	 * @param string $select post select statement ID
846
	 * @param TResultMap $resultMap current result mapping details.
847
	 * @param TResultProperty $property current result property.
848
	 * @param array $row a result set row retrieved from the database
849
	 * @param object $resultObject the result object
850
	 */
851
	protected function enquequePostSelect($select, $resultMap, $property, $row, $resultObject)
852
	{
853
		$statement = $this->getManager()->getMappedStatement($select);
854
		$key = $this->getPostSelectKeys($resultMap, $property, $row);
855
		$postSelect = new TPostSelectBinding;
856
		$postSelect->setStatement($statement);
857
		$postSelect->setResultObject($resultObject);
858
		$postSelect->setResultProperty($property);
859
		$postSelect->setKeys($key);
860
861
		if ($property->instanceOfListType($resultObject)) {
862
			$values = null;
0 ignored issues
show
Unused Code introduced by
$values is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
863
			if ($property->getLazyLoad()) {
864
				$values = TLazyLoadList::newInstance(
865
					$statement,
866
					$key,
867
								$resultObject,
868
					$property->getProperty()
869
				);
870
				TPropertyAccess::set($resultObject, $property->getProperty(), $values);
871
			} else {
872
				$postSelect->setMethod(self::QUERY_FOR_LIST);
873
			}
874
		} elseif ($property->instanceOfArrayType($resultObject)) {
875
			$postSelect->setMethod(self::QUERY_FOR_ARRAY);
876
		} else {
877
			$postSelect->setMethod(self::QUERY_FOR_OBJECT);
878
		}
879
880
		if (!$property->getLazyLoad()) {
881
			$this->_selectQueue[] = $postSelect;
882
		}
883
	}
884
885
	/**
886
	 * Finds in the post select property the SQL statement primary selection keys.
887
	 * @param TResultMap $resultMap result mapping details
888
	 * @param TResultProperty $property result property
889
	 * @param array $row current row data.
890
	 * @return array list of primary key values.
891
	 */
892
	protected function getPostSelectKeys($resultMap, $property, $row)
893
	{
894
		$value = $property->getColumn();
895
		if (is_int(strpos($value . ',', 0)) || is_int(strpos($value, '=', 0))) {
896
			$keys = [];
897
			foreach (explode(',', $value) as $entry) {
898
				$pair = explode('=', $entry);
899
				$keys[trim($pair[0])] = $row[trim($pair[1])];
900
			}
901
			return $keys;
902
		} else {
903
			$registry = $this->getManager()->getTypeHandlers();
904
			return $property->getPropertyValue($registry, $row);
905
		}
906
	}
907
908
	/**
909
	 * Fills the property with result mapping results.
910
	 * @param TResultMap $resultMap nested result mapping details.
911
	 * @param array $row a result set row retrieved from the database
912
	 * @param object &$resultObject the result object
913
	 * @return bool true if the data was found, false otherwise.
914
	 */
915
	protected function fillPropertyWithResultMap($resultMap, $row, &$resultObject)
916
	{
917
		$dataFound = false;
918
		foreach ($resultMap->getColumns() as $property) {
919
			$this->_IsRowDataFound = false;
920
			$this->setObjectProperty($resultMap, $property, $row, $resultObject);
921
			$dataFound = $dataFound || $this->_IsRowDataFound;
922
		}
923
		$this->_IsRowDataFound = $dataFound;
924
		return $dataFound;
925
	}
926
927
	public function __wakeup()
928
	{
929
		if (null === $this->_selectQueue) {
930
			$this->_selectQueue = [];
931
		}
932
	}
933
934 View Code Duplication
	public function __sleep()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
935
	{
936
		$exprops = [];
937
		$cn = __CLASS__;
938
		if (!count($this->_selectQueue)) {
939
			$exprops[] = "\0$cn\0_selectQueue";
940
		}
941
		if (null === $this->_groupBy) {
942
			$exprops[] = "\0$cn\0_groupBy";
943
		}
944
		if (!$this->_IsRowDataFound) {
945
			$exprops[] = "\0$cn\0_IsRowDataFound";
946
		}
947
		return array_diff(parent::__sleep(), $exprops);
948
	}
949
}
950