Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * TMappedStatement and related classes.
4
 *
5
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
6
 * @link http://www.pradosoft.com/
7
 * @copyright Copyright &copy; 2005-2008 PradoSoft
8
 * @license http://www.pradosoft.com/license/
9
 * @version $Id: TMappedStatement.php 2582 2008-12-03 07:36:59Z christophe.boulain $
10
 * @package System.Data.SqlMap.Statements
11
 */
12
 
13
/**
14
 * TMappedStatement class executes SQL mapped statements. Mapped Statements can
15
 * hold any SQL statement and use Parameter Maps and Result Maps for input and output.
16
 *
17
 * This class is usualy instantiated during SQLMap configuration by TSqlDomBuilder.
18
 *
19
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
20
 * @version $Id: TMappedStatement.php 2582 2008-12-03 07:36:59Z christophe.boulain $
21
 * @package System.Data.SqlMap.Statements
22
 * @since 3.0
23
 */
24
class TMappedStatement extends TComponent implements IMappedStatement
25
{
26
	/**
27
	 * @var TSqlMapStatement current SQL statement.
28
	 */
29
	private $_statement;
30
 
31
	/**
32
	 * @var TPreparedCommand SQL command prepareer
33
	 */
34
	private $_command;
35
 
36
	/**
37
	 * @var TSqlMapper sqlmap used by this mapper.
38
	 */
39
	private $_manager;
40
 
41
	/**
42
	 * @var TPostSelectBinding[] post select statement queue.
43
	 */
44
	private $_selectQueque=array();
45
 
46
	/**
47
	 * @var boolean true when data is mapped to a particular row.
48
	 */
49
	private $_IsRowDataFound = false;
50
 
51
	/**
52
	 * @var TSQLMapObjectCollectionTree group by object collection tree
53
	 */
54
	private $_groupBy;
55
 
56
	/**
57
	 * @var Post select is to query for list.
58
	 */
59
	const QUERY_FOR_LIST = 0;
60
 
61
	/**
62
	 * @var Post select is to query for list.
63
	 */
64
	const QUERY_FOR_ARRAY = 1;
65
 
66
	/**
67
	 * @var Post select is to query for object.
68
	 */
69
	const QUERY_FOR_OBJECT = 2;
70
 
71
	/**
72
	 * @return string Name used to identify the TMappedStatement amongst the others.
73
	 * This the name of the SQL statement by default.
74
	 */
75
	public function getID()
76
	{
77
		return $this->_statement->ID;
78
	}
79
 
80
	/**
81
	 * @return TSqlMapStatement The SQL statment used by this MappedStatement
82
	 */
83
	public function getStatement()
84
	{
85
		return $this->_statement;
86
	}
87
 
88
	/**
89
	 * @return TSqlMapper The SqlMap used by this MappedStatement
90
	 */
91
	public function getManager()
92
	{
93
		return $this->_manager;
94
	}
95
 
96
	/**
97
	 * @return TPreparedCommand command to prepare SQL statements.
98
	 */
99
	public function getCommand()
100
	{
101
		return $this->_command;
102
	}
103
 
104
	/**
105
	 * Empty the group by results cache.
106
	 */
107
	protected function initialGroupByResults()
108
	{
109
		$this->_groupBy = new TSqlMapObjectCollectionTree();
110
	}
111
 
112
	/**
113
	 * Creates a new mapped statement.
114
	 * @param TSqlMapper an sqlmap.
115
	 * @param TSqlMapStatement An SQL statement.
116
	 */
117
	public function __construct(TSqlMapManager $sqlMap, TSqlMapStatement $statement)
118
	{
119
		$this->_manager = $sqlMap;
120
		$this->_statement = $statement;
121
		$this->_command = new TPreparedCommand();
122
		$this->initialGroupByResults();
123
	}
124
 
125
	public function getSqlString()
126
	{
127
		return $this->getStatement()->getSqlText()->getPreparedStatement()->getPreparedSql();
128
	}
129
 
130
	/**
131
	 * Execute SQL Query.
132
	 * @param IDbConnection database connection
133
	 * @param array SQL statement and parameters.
134
	 * @return mixed record set if applicable.
135
	 * @throws TSqlMapExecutionException if execution error or false record set.
136
	 * @throws TSqlMapQueryExecutionException if any execution error
137
	 */
138
/*	protected function executeSQLQuery($connection, $sql)
139
	{
140
		try
141
		{
142
			if(!($recordSet = $connection->execute($sql['sql'],$sql['parameters'])))
143
			{
144
				throw new TSqlMapExecutionException(
145
					'sqlmap_execution_error_no_record', $this->getID(),
146
					$connection->ErrorMsg());
147
			}
148
			return $recordSet;
149
		}
150
		catch (Exception $e)
151
		{
152
			throw new TSqlMapQueryExecutionException($this->getStatement(), $e);
153
		}
154
	}*/
155
 
156
	/**
157
	 * Execute SQL Query with limits.
158
	 * @param IDbConnection database connection
159
	 * @param array SQL statement and parameters.
160
	 * @return mixed record set if applicable.
161
	 * @throws TSqlMapExecutionException if execution error or false record set.
162
	 * @throws TSqlMapQueryExecutionException if any execution error
163
	 */
164
	protected function executeSQLQueryLimit($connection, $command, $max, $skip)
165
	{
166
		if($max>-1 || $skip > -1)
167
		{
168
			$maxStr=$max>0?' LIMIT '.$max:'';
169
			$skipStr=$skip>0?' OFFSET '.$skip:'';
170
			$command->setText($command->getText().$maxStr.$skipStr);
171
		}
172
		$connection->setActive(true);
173
		return $command->query();
174
 
175
		/*//var_dump($command);
176
		try
177
		{
178
			$recordSet = $connection->selectLimit($sql['sql'],$max,$skip,$sql['parameters']);
179
			if(!$recordSet)
180
			{
181
				throw new TSqlMapExecutionException(
182
							'sqlmap_execution_error_query_for_list',
183
							$connection->ErrorMsg());
184
			}
185
			return $recordSet;
186
		}
187
		catch (Exception $e)
188
		{
189
			throw new TSqlMapQueryExecutionException($this->getStatement(), $e);
190
		}*/
191
	}
192
 
193
	/**
194
	 * Executes the SQL and retuns a List of result objects.
195
	 * @param IDbConnection database connection
196
	 * @param mixed The object used to set the parameters in the SQL.
197
	 * @param object result collection object.
198
	 * @param integer The number of rows to skip over.
199
	 * @param integer The maximum number of rows to return.
200
	 * @return array a list of result objects
201
	 * @param callback row delegate handler
202
	 * @see executeQueryForList()
203
	 */
204
	public function executeQueryForList($connection, $parameter, $result=null, $skip=-1, $max=-1, $delegate=null)
205
	{
206
		$sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter,$skip,$max);
207
		return $this->runQueryForList($connection, $parameter, $sql, $result, $delegate);
208
	}
209
 
210
	/**
211
	 * Executes the SQL and retuns a List of result objects.
212
	 *
213
	 * This method should only be called by internal developers, consider using
214
	 * <tt>executeQueryForList()</tt> first.
215
	 *
216
	 * @param IDbConnection database connection
217
	 * @param mixed The object used to set the parameters in the SQL.
218
	 * @param array SQL string and subsititution parameters.
219
	 * @param object result collection object.
220
	 * @param integer The number of rows to skip over.
221
	 * @param integer The maximum number of rows to return.
222
	 * @param callback row delegate handler
223
	 * @return array a list of result objects
224
	 * @see executeQueryForList()
225
	 */
226
	public function runQueryForList($connection, $parameter, $sql, $result, $delegate=null)
227
	{
228
		$registry=$this->getManager()->getTypeHandlers();
229
		$list = $result instanceof ArrayAccess ? $result :
230
							$this->_statement->createInstanceOfListClass($registry);
231
		$connection->setActive(true);
232
		$reader = $sql->query();
233
		//$reader = $this->executeSQLQueryLimit($connection, $sql, $max, $skip);
234
		if(!is_null($delegate))
235
		{
236
			foreach($reader as $row)
237
			{
238
				$obj = $this->applyResultMap($row);
239
				$param = new TResultSetListItemParameter($obj, $parameter, $list);
240
				$this->raiseRowDelegate($delegate, $param);
241
			}
242
		}
243
		else
244
		{
245
			//var_dump($sql,$parameter);
246
			foreach($reader as $row)
247
			{
248
//				var_dump($row);
249
				$list[] = $this->applyResultMap($row);
250
			}
251
		}
252
 
253
		if(!$this->_groupBy->isEmpty())
254
		{
255
			$list = $this->_groupBy->collect();
256
			$this->initialGroupByResults();
257
		}
258
 
259
		$this->executePostSelect($connection);
260
		$this->onExecuteQuery($sql);
261
 
262
		return $list;
263
	}
264
 
265
	/**
266
	 * Executes the SQL and retuns all rows selected in a map that is keyed on
267
	 * the property named in the keyProperty parameter.  The value at each key
268
	 * will be the value of the property specified in the valueProperty parameter.
269
	 * If valueProperty is null, the entire result object will be entered.
270
	 * @param IDbConnection database connection
271
	 * @param mixed The object used to set the parameters in the SQL.
272
	 * @param string The property of the result object to be used as the key.
273
	 * @param string The property of the result object to be used as the value (or null).
274
	 * @param callback row delegate handler
275
	 * @return array An array of object containing the rows keyed by keyProperty.
276
	 */
277
	public function executeQueryForMap($connection, $parameter, $keyProperty, $valueProperty=null,  $skip=-1, $max=-1, $delegate=null)
278
	{
279
		$sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter, $skip, $max);
280
		return $this->runQueryForMap($connection, $parameter, $sql, $keyProperty, $valueProperty, $delegate);
281
	}
282
 
283
	/**
284
	 * Executes the SQL and retuns all rows selected in a map that is keyed on
285
	 * the property named in the keyProperty parameter.  The value at each key
286
	 * will be the value of the property specified in the valueProperty parameter.
287
	 * If valueProperty is null, the entire result object will be entered.
288
	 *
289
	 * This method should only be called by internal developers, consider using
290
	 * <tt>executeQueryForMap()</tt> first.
291
	 *
292
	 * @param IDbConnection database connection
293
	 * @param mixed The object used to set the parameters in the SQL.
294
	 * @param array SQL string and subsititution parameters.
295
	 * @param string The property of the result object to be used as the key.
296
	 * @param string The property of the result object to be used as the value (or null).
297
	 * @param callback row delegate, a callback function
298
	 * @return array An array of object containing the rows keyed by keyProperty.
299
	 * @see executeQueryForMap()
300
	 */
301
	public function runQueryForMap($connection, $parameter, $command, $keyProperty, $valueProperty=null, $delegate=null)
302
	{
303
		$map = array();
304
		//$recordSet = $this->executeSQLQuery($connection, $sql);
305
		$connection->setActive(true);
306
		$reader = $command->query();
307
		if(!is_null($delegate))
308
		{
309
			//while($row = $recordSet->fetchRow())
310
			foreach($reader as $row)
311
			{
312
				$obj = $this->applyResultMap($row);
313
				$key = TPropertyAccess::get($obj, $keyProperty);
314
				$value = is_null($valueProperty) ? $obj :
315
							TPropertyAccess::get($obj, $valueProperty);
316
				$param = new TResultSetMapItemParameter($key, $value, $parameter, $map);
317
				$this->raiseRowDelegate($delegate, $param);
318
			}
319
		}
320
		else
321
		{
322
			//while($row = $recordSet->fetchRow())
323
			foreach($reader as $row)
324
			{
325
				$obj = $this->applyResultMap($row);
326
				$key = TPropertyAccess::get($obj, $keyProperty);
327
				$map[$key] = is_null($valueProperty) ? $obj :
328
								TPropertyAccess::get($obj, $valueProperty);
329
			}
330
		}
331
		$this->onExecuteQuery($command);
332
		return $map;
333
	}
334
 
335
	/**
336
	 * Raises delegate handler.
337
	 * This method is invoked for each new list item. It is the responsibility
338
	 * of the handler to add the item to the list.
339
	 * @param object event parameter
340
	 */
341
	protected function raiseRowDelegate($handler, $param)
342
	{
343
		if(is_string($handler))
344
		{
345
			call_user_func($handler,$this,$param);
346
		}
347
		else if(is_callable($handler,true))
348
		{
349
			// an array: 0 - object, 1 - method name/path
350
			list($object,$method)=$handler;
351
			if(is_string($object))	// static method call
352
				call_user_func($handler,$this,$param);
353
			else
354
			{
355
				if(($pos=strrpos($method,'.'))!==false)
356
				{
357
					$object=$this->getSubProperty(substr($method,0,$pos));
358
					$method=substr($method,$pos+1);
359
				}
360
				$object->$method($this,$param);
361
			}
362
		}
363
		else
364
			throw new TInvalidDataValueException('sqlmap_invalid_delegate', $this->getID(), $handler);
365
	}
366
 
367
	/**
368
	 * Executes an SQL statement that returns a single row as an object of the
369
	 * type of the <tt>$result</tt> passed in as a parameter.
370
	 * @param IDbConnection database connection
371
	 * @param mixed The parameter data (object, arrary, primitive) used to set the parameters in the SQL
372
	 * @param mixed The result object.
373
	 * @return ${return}
374
	 */
375
	public function executeQueryForObject($connection, $parameter, $result=null)
376
	{
377
		$sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter);
378
		return $this->runQueryForObject($connection, $sql, $result);
379
	}
380
 
381
	/**
382
	 * Executes an SQL statement that returns a single row as an object of the
383
	 * type of the <tt>$result</tt> passed in as a parameter.
384
	 *
385
	 * This method should only be called by internal developers, consider using
386
	 * <tt>executeQueryForObject()</tt> first.
387
	 *
388
	 * @param IDbConnection database connection
389
	 * @param array SQL string and subsititution parameters.
390
	 * @param object The result object.
391
	 * @return object the object.
392
	 * @see executeQueryForObject()
393
	 */
394
	public function runQueryForObject($connection, $command, &$result)
395
	{
396
		$object = null;
397
		$connection->setActive(true);
398
		foreach($command->query() as $row)
399
			$object = $this->applyResultMap($row, $result);
400
 
401
		if(!$this->_groupBy->isEmpty())
402
		{
403
			$list = $this->_groupBy->collect();
404
			$this->initialGroupByResults();
405
			$object = $list[0];
406
		}
407
 
408
		$this->executePostSelect($connection);
409
		$this->onExecuteQuery($command);
410
 
411
		return $object;
412
	}
413
 
414
	/**
415
	 * Execute an insert statement. Fill the parameter object with the ouput
416
	 * parameters if any, also could return the insert generated key.
417
	 * @param IDbConnection database connection
418
	 * @param mixed The parameter object used to fill the statement.
419
	 * @return string the insert generated key.
420
	 */
421
	public function executeInsert($connection, $parameter)
422
	{
423
		$generatedKey = $this->getPreGeneratedSelectKey($connection, $parameter);
424
 
425
		$command = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter);
426
//		var_dump($command,$parameter);
427
		$result = $command->execute();
428
 
429
		if(is_null($generatedKey))
430
			$generatedKey = $this->getPostGeneratedSelectKey($connection, $parameter);
431
 
432
		$this->executePostSelect($connection);
433
		$this->onExecuteQuery($command);
434
		return $generatedKey;
435
	}
436
 
437
	/**
438
	 * Gets the insert generated ID before executing an insert statement.
439
	 * @param IDbConnection database connection
440
	 * @param mixed insert statement parameter.
441
	 * @return string new insert ID if pre-select key statement was executed, null otherwise.
442
	 */
443
	protected function getPreGeneratedSelectKey($connection, $parameter)
444
	{
445
		if($this->_statement instanceof TSqlMapInsert)
446
		{
447
			$selectKey = $this->_statement->getSelectKey();
448
			if(!is_null($selectKey) && !$selectKey->getIsAfter())
449
				return $this->executeSelectKey($connection, $parameter, $selectKey);
450
		}
451
	}
452
 
453
	/**
454
	 * Gets the inserted row ID after executing an insert statement.
455
	 * @param IDbConnection database connection
456
	 * @param mixed insert statement parameter.
457
	 * @return string last insert ID, null otherwise.
458
	 */
459
	protected function getPostGeneratedSelectKey($connection, $parameter)
460
	{
461
		if($this->_statement instanceof TSqlMapInsert)
462
		{
463
			$selectKey = $this->_statement->getSelectKey();
464
			if(!is_null($selectKey) && $selectKey->getIsAfter())
465
				return $this->executeSelectKey($connection, $parameter, $selectKey);
466
		}
467
	}
468
 
469
	/**
470
	 * Execute the select key statement, used to obtain last insert ID.
471
	 * @param IDbConnection database connection
472
	 * @param mixed insert statement parameter
473
	 * @param TSqlMapSelectKey select key statement
474
	 * @return string last insert ID.
475
	 */
476
	protected function executeSelectKey($connection, $parameter, $selectKey)
477
	{
478
		$mappedStatement = $this->getManager()->getMappedStatement($selectKey->getID());
479
		$generatedKey = $mappedStatement->executeQueryForObject(
480
									$connection, $parameter, null);
481
		if(strlen($prop = $selectKey->getProperty()) > 0)
482
				TPropertyAccess::set($parameter, $prop, $generatedKey);
483
		return $generatedKey;
484
	}
485
 
486
	/**
487
	 * Execute an update statement. Also used for delete statement.
488
	 * Return the number of rows effected.
489
	 * @param IDbConnection database connection
490
	 * @param mixed The object used to set the parameters in the SQL.
491
	 * @return integer The number of rows effected.
492
	 */
493
	public function executeUpdate($connection, $parameter)
494
	{
495
		$sql = $this->_command->create($this->getManager(),$connection, $this->_statement, $parameter);
496
		$affectedRows = $sql->execute();
497
		//$this->executeSQLQuery($connection, $sql);
498
		$this->executePostSelect($connection);
499
		$this->onExecuteQuery($sql);
500
		return $affectedRows;
501
	}
502
 
503
	/**
504
	 * Process 'select' result properties
505
	 * @param IDbConnection database connection
506
	 */
507
	protected function executePostSelect($connection)
508
	{
509
		while(count($this->_selectQueque))
510
		{
511
			$postSelect = array_shift($this->_selectQueque);
512
			$method = $postSelect->getMethod();
513
			$statement = $postSelect->getStatement();
514
			$property = $postSelect->getResultProperty()->getProperty();
515
			$keys = $postSelect->getKeys();
516
			$resultObject = $postSelect->getResultObject();
517
 
518
			if($method == self::QUERY_FOR_LIST || $method == self::QUERY_FOR_ARRAY)
519
			{
520
				$values = $statement->executeQueryForList($connection, $keys, null);
521
 
522
				if($method == self::QUERY_FOR_ARRAY)
523
					$values = $values->toArray();
524
				TPropertyAccess::set($resultObject, $property, $values);
525
			}
526
			else if($method == self::QUERY_FOR_OBJECT)
527
			{
528
				$value = $statement->executeQueryForObject($connection, $keys, null);
529
				TPropertyAccess::set($resultObject, $property, $value);
530
			}
531
		}
532
	}
533
 
534
	/**
535
	 * Raise the execute query event.
536
	 * @param array prepared SQL statement and subsititution parameters
537
	 */
538
	public function onExecuteQuery($sql)
539
	{
540
		$this->raiseEvent('OnExecuteQuery', $this, $sql);
541
	}
542
 
543
	/**
544
	 * Apply result mapping.
545
	 * @param array a result set row retrieved from the database
546
	 * @param object the result object, will create if necessary.
547
	 * @return object the result filled with data, null if not filled.
548
	 */
549
	protected function applyResultMap($row, &$resultObject=null)
550
	{
551
		if($row === false) return null;
552
 
553
		$resultMapName = $this->_statement->getResultMap();
554
		$resultClass = $this->_statement->getResultClass();
555
 
556
		$obj=null;
557
		if($this->getManager()->getResultMaps()->contains($resultMapName))
558
			$obj = $this->fillResultMap($resultMapName, $row, null, $resultObject);
559
		else if(strlen($resultClass) > 0)
560
			$obj = $this->fillResultClass($resultClass, $row, $resultObject);
561
		else
562
			$obj = $this->fillDefaultResultMap(null, $row, $resultObject);
563
		if(class_exists('TActiveRecord',false) && $obj instanceof TActiveRecord)
564
			//Create a new clean active record.
565
			$obj=TActiveRecord::createRecord(get_class($obj),$obj);
566
		return $obj;
567
	}
568
 
569
	/**
570
	 * Fill the result using ResultClass, will creates new result object if required.
571
	 * @param string result object class name
572
	 * @param array a result set row retrieved from the database
573
	 * @param object the result object, will create if necessary.
574
	 * @return object result object filled with data
575
	 */
576
	protected function fillResultClass($resultClass, $row, $resultObject)
577
	{
578
		if(is_null($resultObject))
579
		{
580
			$registry = $this->getManager()->getTypeHandlers();
581
			$resultObject = $this->_statement->createInstanceOfResultClass($registry,$row);
582
		}
583
 
584
		if($resultObject instanceOf ArrayAccess)
585
			return $this->fillResultArrayList($row, $resultObject);
586
		else if(is_object($resultObject))
587
			return $this->fillResultObjectProperty($row, $resultObject);
588
		else
589
			return $this->fillDefaultResultMap(null, $row, $resultObject);
590
	}
591
 
592
	/**
593
	 * Apply the result to a TList or an array.
594
	 * @param array a result set row retrieved from the database
595
	 * @param object result object, array or list
596
	 * @return object result filled with data.
597
	 */
598
	protected function fillResultArrayList($row, $resultObject)
599
	{
600
		if($resultObject instanceof TList)
601
			foreach($row as $v)
602
				$resultObject[] = $v;
603
		else
604
			foreach($row as $k => $v)
605
				$resultObject[$k] = $v;
606
		return $resultObject;
607
	}
608
 
609
	/**
610
	 * Apply the result to an object.
611
	 * @param array a result set row retrieved from the database
612
	 * @param object result object, array or list
613
	 * @return object result filled with data.
614
	 */
615
	protected function fillResultObjectProperty($row, $resultObject)
616
	{
617
		$index = 0;
618
		$registry=$this->getManager()->getTypeHandlers();
619
		foreach($row as $k=>$v)
620
		{
621
			$property = new TResultProperty;
622
			if(is_string($k) && strlen($k) > 0)
623
				$property->setColumn($k);
624
			$property->setColumnIndex(++$index);
625
			$type = gettype(TPropertyAccess::get($resultObject,$k));
626
			$property->setType($type);
627
			$value = $property->getPropertyValue($registry,$row);
628
			TPropertyAccess::set($resultObject, $k,$value);
629
		}
630
		return $resultObject;
631
	}
632
 
633
	/**
634
	 * Fills the result object according to result mappings.
635
	 * @param string result map name.
636
	 * @param array a result set row retrieved from the database
637
	 * @param object result object to fill, will create new instances if required.
638
	 * @return object result object filled with data.
639
	 */
640
	protected function fillResultMap($resultMapName, $row, $parentGroup=null, &$resultObject=null)
641
	{
642
		$resultMap = $this->getManager()->getResultMap($resultMapName);
643
		$registry = $this->getManager()->getTypeHandlers();
644
		$resultMap = $resultMap->resolveSubMap($registry,$row);
645
 
646
		if(is_null($resultObject))
647
			$resultObject = $resultMap->createInstanceOfResult($registry);
648
 
649
		if(is_object($resultObject))
650
		{
651
			if(strlen($resultMap->getGroupBy()) > 0)
652
				return $this->addResultMapGroupBy($resultMap, $row, $parentGroup, $resultObject);
653
			else
654
				foreach($resultMap->getColumns() as $property)
655
					$this->setObjectProperty($resultMap, $property, $row, $resultObject);
656
		}
657
		else
658
		{
659
			$resultObject = $this->fillDefaultResultMap($resultMap, $row, $resultObject);
660
		}
661
		return $resultObject;
662
	}
663
 
664
	/**
665
	 * ResultMap with GroupBy property. Save object collection graph in a tree
666
	 * and collect the result later.
667
	 * @param TResultMap result mapping details.
668
	 * @param array a result set row retrieved from the database
669
	 * @param object the result object
670
	 * @return object result object.
671
	 */
672
	protected function addResultMapGroupBy($resultMap, $row, $parent, &$resultObject)
673
	{
674
		$group = $this->getResultMapGroupKey($resultMap, $row);
675
 
676
		if(empty($parent))
677
		{
678
			$rootObject = array('object'=>$resultObject, 'property' => null);
679
			$this->_groupBy->add(null, $group, $rootObject);
680
		}
681
 
682
		foreach($resultMap->getColumns() as $property)
683
		{
684
			//set properties.
685
			$this->setObjectProperty($resultMap, $property, $row, $resultObject);
686
			$nested = $property->getResultMapping();
687
 
688
			//nested property
689
			if($this->getManager()->getResultMaps()->contains($nested))
690
			{
691
				$nestedMap = $this->getManager()->getResultMap($nested);
692
				$groupKey = $this->getResultMapGroupKey($nestedMap, $row);
693
 
694
				//add the node reference first
695
				if(empty($parent))
696
					$this->_groupBy->add($group, $groupKey, '');
697
 
698
				//get the nested result mapping value
699
				$value = $this->fillResultMap($nested, $row, $groupKey);
700
 
701
				//add it to the object tree graph
702
				$groupObject = array('object'=>$value, 'property' => $property->getProperty());
703
				if(empty($parent))
704
					$this->_groupBy->add($group, $groupKey, $groupObject);
705
				else
706
					$this->_groupBy->add($parent, $groupKey, $groupObject);
707
			}
708
		}
709
		return $resultObject;
710
	}
711
 
712
	/**
713
	 * Gets the result 'group by' groupping key for each row.
714
	 * @param TResultMap result mapping details.
715
	 * @param array a result set row retrieved from the database
716
	 * @return string groupping key.
717
	 */
718
	protected function getResultMapGroupKey($resultMap, $row)
719
	{
720
		$groupBy = $resultMap->getGroupBy();
721
		if(isset($row[$groupBy]))
722
			return $resultMap->getID().$row[$groupBy];
723
		else
724
			return $resultMap->getID().crc32(serialize($row));
725
	}
726
 
727
	/**
728
	 * Fill the result map using default settings. If <tt>$resultMap</tt> is null
729
	 * the result object returned will be guessed from <tt>$resultObject</tt>.
730
	 * @param TResultMap result mapping details.
731
	 * @param array a result set row retrieved from the database
732
	 * @param object the result object
733
	 * @return mixed the result object filled with data.
734
	 */
735
	protected function fillDefaultResultMap($resultMap, $row, $resultObject)
736
	{
737
		if(is_null($resultObject))
738
			$resultObject='';
739
 
740
		if(!is_null($resultMap))
741
			$result = $this->fillArrayResultMap($resultMap, $row, $resultObject);
742
		else
743
			$result = $row;
744
 
745
		//if scalar result types
746
		if(count($result) == 1 && ($type = gettype($resultObject))!= 'array')
747
			return $this->getScalarResult($result, $type);
748
		else
749
			return $result;
750
	}
751
 
752
	/**
753
	 * Retrieve the result map as an array.
754
	 * @param TResultMap result mapping details.
755
	 * @param array a result set row retrieved from the database
756
	 * @param object the result object
757
	 * @return array array list of result objects.
758
	 */
759
	protected function fillArrayResultMap($resultMap, $row, $resultObject)
760
	{
761
		$result = array();
762
		$registry=$this->getManager()->getTypeHandlers();
763
		foreach($resultMap->getColumns() as $column)
764
		{
765
			if(is_null($column->getType())
766
				&& !is_null($resultObject) && !is_object($resultObject))
767
			$column->setType(gettype($resultObject));
768
			$result[$column->getProperty()] = $column->getPropertyValue($registry,$row);
769
		}
770
		return $result;
771
	}
772
 
773
	/**
774
	 * Converts the first array value to scalar value of given type.
775
	 * @param array list of results
776
	 * @param string scalar type.
777
	 * @return mixed scalar value.
778
	 */
779
	protected function getScalarResult($result, $type)
780
	{
781
		$scalar = array_shift($result);
782
		settype($scalar, $type);
783
		return $scalar;
784
	}
785
 
786
	/**
787
	 * Set a property of the result object with appropriate value.
788
	 * @param TResultMap result mapping details.
789
	 * @param TResultProperty the result property to fill.
790
	 * @param array a result set row retrieved from the database
791
	 * @param object the result object
792
	 */
793
	protected function setObjectProperty($resultMap, $property, $row, &$resultObject)
794
	{
795
		$select = $property->getSelect();
796
		$key = $property->getProperty();
797
		$nested = $property->getNestedResultMap();
798
		$registry=$this->getManager()->getTypeHandlers();
799
		if($key === '')
800
		{
801
			$resultObject = $property->getPropertyValue($registry,$row);
802
		}
803
		else if(strlen($select) == 0 && is_null($nested))
804
		{
805
			$value = $property->getPropertyValue($registry,$row);
806
 
807
			$this->_IsRowDataFound = $this->_IsRowDataFound || ($value != null);
808
			if(is_array($resultObject) || is_object($resultObject))
809
				TPropertyAccess::set($resultObject, $key, $value);
810
			else
811
				$resultObject = $value;
812
		}
813
		else if(!is_null($nested))
814
		{
815
			if($property->instanceOfListType($resultObject) || $property->instanceOfArrayType($resultObject))
816
			{
817
				if(strlen($resultMap->getGroupBy()) <= 0)
818
					throw new TSqlMapExecutionException(
819
						'sqlmap_non_groupby_array_list_type', $resultMap->getID(),
820
						get_class($resultObject), $key);
821
			}
822
			else
823
			{
824
				$obj = $nested->createInstanceOfResult($this->getManager()->getTypeHandlers());
825
				if($this->fillPropertyWithResultMap($nested, $row, $obj) == false)
826
					$obj = null;
827
				TPropertyAccess::set($resultObject, $key, $obj);
828
			}
829
		}
830
		else //'select' ResultProperty
831
		{
832
			$this->enquequePostSelect($select, $resultMap, $property, $row, $resultObject);
833
		}
834
	}
835
 
836
	/**
837
	 * Add nested result property to post select queue.
838
	 * @param string post select statement ID
839
	 * @param TResultMap current result mapping details.
840
	 * @param TResultProperty current result property.
841
	 * @param array a result set row retrieved from the database
842
	 * @param object the result object
843
	 */
844
	protected function enquequePostSelect($select, $resultMap, $property, $row, $resultObject)
845
	{
846
		$statement = $this->getManager()->getMappedStatement($select);
847
		$key = $this->getPostSelectKeys($resultMap, $property, $row);
848
		$postSelect = new TPostSelectBinding;
849
		$postSelect->setStatement($statement);
850
		$postSelect->setResultObject($resultObject);
851
		$postSelect->setResultProperty($property);
852
		$postSelect->setKeys($key);
853
 
854
		if($property->instanceOfListType($resultObject))
855
		{
856
			$values = null;
857
			if($property->getLazyLoad())
858
			{
859
				$values = TLazyLoadList::newInstance($statement, $key,
860
								$resultObject, $property->getProperty());
861
				TPropertyAccess::set($resultObject, $property->getProperty(), $values);
862
			}
863
			else
864
				$postSelect->setMethod(self::QUERY_FOR_LIST);
865
		}
866
		else if($property->instanceOfArrayType($resultObject))
867
			$postSelect->setMethod(self::QUERY_FOR_ARRAY);
868
		else
869
			$postSelect->setMethod(self::QUERY_FOR_OBJECT);
870
 
871
		if(!$property->getLazyLoad())
872
			array_push($this->_selectQueque, $postSelect);
873
	}
874
 
875
	/**
876
	 * Finds in the post select property the SQL statement primary selection keys.
877
	 * @param TResultMap result mapping details
878
	 * @param TResultProperty result property
879
	 * @param array current row data.
880
	 * @return array list of primary key values.
881
	 */
882
	protected function getPostSelectKeys($resultMap, $property,$row)
883
	{
884
		$value = $property->getColumn();
885
		if(is_int(strpos($value.',',0)) || is_int(strpos($value, '=',0)))
886
		{
887
			$keys = array();
888
			foreach(explode(',', $value) as $entry)
889
			{
890
				$pair =explode('=',$entry);
891
				$keys[trim($pair[0])] = $row[trim($pair[1])];
892
			}
893
			return $keys;
894
		}
895
		else
896
		{
897
			$registry=$this->getManager()->getTypeHandlers();
898
			return $property->getPropertyValue($registry,$row);
899
		}
900
	}
901
 
902
	/**
903
	 * Fills the property with result mapping results.
904
	 * @param TResultMap nested result mapping details.
905
	 * @param array a result set row retrieved from the database
906
	 * @param object the result object
907
	 * @return boolean true if the data was found, false otherwise.
908
	 */
909
	protected function fillPropertyWithResultMap($resultMap, $row, &$resultObject)
910
	{
911
		$dataFound = false;
912
		foreach($resultMap->getColumns() as $property)
913
		{
914
			$this->_IsRowDataFound = false;
915
			$this->setObjectProperty($resultMap, $property, $row, $resultObject);
916
			$dataFound = $dataFound || $this->_IsRowDataFound;
917
		}
918
		$this->_IsRowDataFound = $dataFound;
919
		return $dataFound;
920
	}
921
}
922
 
923
/**
924
 * TPostSelectBinding class.
925
 *
926
 * @author Wei Zhuo <weizho[at]gmail[dot]com>
927
 * @version $Id: TMappedStatement.php 2582 2008-12-03 07:36:59Z christophe.boulain $
928
 * @package System.Data.SqlMap.Statements
929
 * @since 3.1
930
 */
931
class TPostSelectBinding
932
{
933
	private $_statement=null;
934
	private $_property=null;
935
	private $_resultObject=null;
936
	private $_keys=null;
937
	private $_method=TMappedStatement::QUERY_FOR_LIST;
938
 
939
	public function getStatement(){ return $this->_statement; }
940
	public function setStatement($value){ $this->_statement = $value; }
941
 
942
	public function getResultProperty(){ return $this->_property; }
943
	public function setResultProperty($value){ $this->_property = $value; }
944
 
945
	public function getResultObject(){ return $this->_resultObject; }
946
	public function setResultObject($value){ $this->_resultObject = $value; }
947
 
948
	public function getKeys(){ return $this->_keys; }
949
	public function setKeys($value){ $this->_keys = $value; }
950
 
951
	public function getMethod(){ return $this->_method; }
952
	public function setMethod($value){ $this->_method = $value; }
953
}
954
 
955
/**
956
 * TSQLMapObjectCollectionTree class.
957
 *
958
 * Maps object collection graphs as trees. Nodes in the collection can
959
 * be {@link add} using parent relationships. The object collections can be
960
 * build using the {@link collect} method.
961
 *
962
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
963
 * @version $Id: TMappedStatement.php 2582 2008-12-03 07:36:59Z christophe.boulain $
964
 * @package System.Data.SqlMap.Statements
965
 * @since 3.1
966
 */
967
class TSqlMapObjectCollectionTree
968
{
969
	/**
970
	 * @var array object graph as tree
971
	 */
972
	private $_tree = array();
973
	/**
974
	 * @var array tree node values
975
	 */
976
	private $_entries = array();
977
	/**
978
	 * @var array resulting object collection
979
	 */
980
	private $_list = array();
981
 
982
	/**
983
	 * @return boolean true if the graph is empty
984
	 */
985
	public function isEmpty()
986
	{
987
		return count($this->_entries) == 0;
988
	}
989
 
990
	/**
991
	 * Add a new node to the object tree graph.
992
	 * @param string parent node id
993
	 * @param string new node id
994
	 * @param mixed node value
995
	 */
996
	public function add($parent, $node, $object='')
997
	{
998
		if(isset($this->_entries[$parent]) && !is_null($this->_entries[$parent])
999
			&& isset($this->_entries[$node]) && !is_null($this->_entries[$node]))
1000
		{
1001
			$this->_entries[$node] = $object;
1002
			return;
1003
		}
1004
		$this->_entries[$node] = $object;
1005
		if(empty($parent))
1006
		{
1007
			if(isset($this->_entries[$node]))
1008
				return;
1009
			$this->_tree[$node] = array();
1010
		}
1011
		$found = $this->addNode($this->_tree, $parent, $node);
1012
		if(!$found && !empty($parent))
1013
		{
1014
			$this->_tree[$parent] = array();
1015
			if(!isset($this->_entries[$parent]) || $object !== '')
1016
				$this->_entries[$parent] = $object;
1017
			$this->addNode($this->_tree, $parent, $node);
1018
		}
1019
	}
1020
 
1021
	/**
1022
	 * Find the parent node and add the new node as its child.
1023
	 * @param array list of nodes to check
1024
	 * @param string parent node id
1025
	 * @param string new node id
1026
	 * @return boolean true if parent node is found.
1027
	 */
1028
	protected function addNode(&$childs, $parent, $node)
1029
	{
1030
		$found = false;
1031
		reset($childs);
1032
		for($i = 0, $k = count($childs); $i < $k; $i++)
1033
		{
1034
			$key = key($childs);
1035
			next($childs);
1036
			if($key == $parent)
1037
			{
1038
				$found = true;
1039
				$childs[$key][$node] = array();
1040
			}
1041
			else
1042
			{
1043
				$found = $found || $this->addNode($childs[$key], $parent, $node);
1044
			}
1045
		}
1046
		return $found;
1047
	}
1048
 
1049
	/**
1050
	 * @return array object collection
1051
	 */
1052
	public function collect()
1053
	{
1054
		while(count($this->_tree) > 0)
1055
			$this->collectChildren(null, $this->_tree);
1056
		return $this->getCollection();
1057
	}
1058
 
1059
	/**
1060
	 * @param array list of nodes to check
1061
	 * @return boolean true if all nodes are leaf nodes, false otherwise
1062
	 */
1063
	protected function hasChildren(&$nodes)
1064
	{
1065
		$hasChildren = false;
1066
		foreach($nodes as $node)
1067
			if(count($node) != 0)
1068
				return true;
1069
		return $hasChildren;
1070
	}
1071
 
1072
	/**
1073
	 * Visit all the child nodes and collect them by removing.
1074
	 * @param string parent node id
1075
	 * @param array list of child nodes.
1076
	 */
1077
	protected function collectChildren($parent, &$nodes)
1078
	{
1079
		$noChildren = !$this->hasChildren($nodes);
1080
		$childs = array();
1081
		for(reset($nodes); $key = key($nodes);)
1082
		{
1083
			next($nodes);
1084
			if($noChildren)
1085
			{
1086
				$childs[] = $key;
1087
				unset($nodes[$key]);
1088
			}
1089
			else
1090
				$this->collectChildren($key, $nodes[$key]);
1091
		}
1092
		if(count($childs) > 0)
1093
			$this->onChildNodesVisited($parent, $childs);
1094
	}
1095
 
1096
	/**
1097
	 * Set the object properties for all the child nodes visited.
1098
	 * @param string parent node id
1099
	 * @param array list of child nodes visited.
1100
	 */
1101
	protected function onChildNodesVisited($parent, $nodes)
1102
	{
1103
		if(empty($parent) || empty($this->_entries[$parent]))
1104
			return;
1105
 
1106
		$parentObject = $this->_entries[$parent]['object'];
1107
		$property = $this->_entries[$nodes[0]]['property'];
1108
 
1109
		$list = TPropertyAccess::get($parentObject, $property);
1110
 
1111
		foreach($nodes as $node)
1112
		{
1113
			if($list instanceof TList)
1114
				$parentObject->{$property}[] = $this->_entries[$node]['object'];
1115
			else if(is_array($list))
1116
				$list[] = $this->_entries[$node]['object'];
1117
			else
1118
				throw new TSqlMapExecutionException(
1119
					'sqlmap_property_must_be_list');
1120
		}
1121
 
1122
		if(is_array($list))
1123
			TPropertyAccess::set($parentObject, $property, $list);
1124
 
1125
		if($this->_entries[$parent]['property'] === null)
1126
			$this->_list[] = $parentObject;
1127
	}
1128
 
1129
	/**
1130
	 * @return array object collection.
1131
	 */
1132
	protected function getCollection()
1133
	{
1134
		return $this->_list;
1135
	}
1136
}
1137
 
1138
/**
1139
 * TResultSetListItemParameter class
1140
 *
1141
 * @author Wei Zhuo <weizho[at]gmail[dot]com>
1142
 * @version $Id: TMappedStatement.php 2582 2008-12-03 07:36:59Z christophe.boulain $
1143
 * @package System.Data.SqlMap.Statements
1144
 * @since 3.1
1145
 */
1146
class TResultSetListItemParameter extends TComponent
1147
{
1148
	private $_resultObject;
1149
	private $_parameterObject;
1150
	private $_list;
1151
 
1152
	public function __construct($result, $parameter, &$list)
1153
	{
1154
		$this->_resultObject = $result;
1155
		$this->_parameterObject = $parameter;
1156
		$this->_list = &$list;
1157
	}
1158
 
1159
	public function getResult()
1160
	{
1161
		return $this->_resultObject;
1162
	}
1163
 
1164
	public function getParameter()
1165
	{
1166
		return $this->_parameterObject;
1167
	}
1168
 
1169
	public function &getList()
1170
	{
1171
		return $this->_list;
1172
	}
1173
}
1174
 
1175
/**
1176
 * TResultSetMapItemParameter class.
1177
 *
1178
 * @author Wei Zhuo <weizho[at]gmail[dot]com>
1179
 * @version $Id: TMappedStatement.php 2582 2008-12-03 07:36:59Z christophe.boulain $
1180
 * @package System.Data.SqlMap.Statements
1181
 * @since 3.1
1182
 */
1183
class TResultSetMapItemParameter extends TComponent
1184
{
1185
	private $_key;
1186
	private $_value;
1187
	private $_parameterObject;
1188
	private $_map;
1189
 
1190
	public function __construct($key, $value, $parameter, &$map)
1191
	{
1192
		$this->_key = $key;
1193
		$this->_value = $value;
1194
		$this->_parameterObject = $parameter;
1195
		$this->_map = &$map;
1196
	}
1197
 
1198
	public function getKey()
1199
	{
1200
		return $this->_key;
1201
	}
1202
 
1203
	public function getValue()
1204
	{
1205
		return $this->_value;
1206
	}
1207
 
1208
	public function getParameter()
1209
	{
1210
		return $this->_parameterObject;
1211
	}
1212
 
1213
	public function &getMap()
1214
	{
1215
		return $this->_map;
1216
	}
1217
}
1218