Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * TDataGatewayCommand, TDataGatewayEventParameter and TDataGatewayResultEventParameter class file.
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$
10
 * @package System.Data.DataGateway
11
 */
12
 
13
/**
14
 * TDataGatewayCommand is command builder and executor class for
15
 * TTableGateway and TActiveRecordGateway.
16
 *
17
 * TDataGatewayCommand builds the TDbCommand for TTableGateway
18
 * and TActiveRecordGateway commands such as find(), update(), insert(), etc,
19
 * using the TDbCommandBuilder classes (database specific TDbCommandBuilder
20
 * classes are used).
21
 *
22
 * Once the command is built and the query parameters are binded, the
23
 * {@link OnCreateCommand} event is raised. Event handlers for the OnCreateCommand
24
 * event should not alter the Command property nor the Criteria property of the
25
 * TDataGatewayEventParameter.
26
 *
27
 * TDataGatewayCommand excutes the TDbCommands and returns the result obtained from the
28
 * database (returned value depends on the method executed). The
29
 * {@link OnExecuteCommand} event is raised after the command is executed and resulting
30
 * data is set in the TDataGatewayResultEventParameter object's Result property.
31
 *
32
 * @author Wei Zhuo <weizho[at]gmail[dot]com>
33
 * @version $Id$
34
 * @package System.Data.DataGateway
35
 * @since 3.1
36
 */
37
class TDataGatewayCommand extends TComponent
38
{
39
	private $_builder;
40
 
41
	/**
42
	 * @param TDbCommandBuilder database specific database command builder.
43
	 */
44
	public function __construct($builder)
45
	{
46
		$this->_builder = $builder;
47
	}
48
 
49
	/**
50
	 * @return TDbTableInfo
51
	 */
52
	public function getTableInfo()
53
	{
54
		return $this->_builder->getTableInfo();
55
	}
56
 
57
	/**
58
	 * @return TDbConnection
59
	 */
60
	public function getDbConnection()
61
	{
62
		return $this->_builder->getDbConnection();
63
	}
64
 
65
	/**
66
	 * @return TDbCommandBuilder
67
	 */
68
	public function getBuilder()
69
	{
70
		return $this->_builder;
71
	}
72
 
73
	/**
74
	 * Executes a delete command.
75
	 * @param TSqlCriteria delete conditions and parameters.
76
	 * @return integer number of records affected.
77
	 */
78
	public function delete($criteria)
79
	{
80
		$where = $criteria->getCondition();
81
		$parameters = $criteria->getParameters()->toArray();
82
		$command = $this->getBuilder()->createDeleteCommand($where, $parameters);
83
		$this->onCreateCommand($command,$criteria);
84
		$command->prepare();
85
		return $command->execute();
86
	}
87
 
88
	/**
89
	 * Updates the table with new data.
90
	 * @param array date for update.
91
	 * @param TSqlCriteria update conditions and parameters.
92
	 * @return integer number of records affected.
93
	 */
94
	public function update($data, $criteria)
95
	{
96
		$where = $criteria->getCondition();
97
		$parameters = $criteria->getParameters()->toArray();
98
		$command = $this->getBuilder()->createUpdateCommand($data,$where, $parameters);
99
		$this->onCreateCommand($command,$criteria);
100
		$command->prepare();
101
		return $this->onExecuteCommand($command, $command->execute());
102
	}
103
 
104
	/**
105
	 * @param array update for update
106
	 * @param array primary key-value name pairs.
107
	 * @return integer number of records affected.
108
	 */
109
	public function updateByPk($data, $keys)
110
	{
111
		list($where, $parameters) = $this->getPrimaryKeyCondition((array)$keys);
112
		return $this->update($data, new TSqlCriteria($where, $parameters));
113
	}
114
 
115
	/**
116
	 * Find one record matching the critera.
117
	 * @param TSqlCriteria find conditions and parameters.
118
	 * @return array matching record.
119
	 */
120
	public function find($criteria)
121
	{
122
		$command = $this->getFindCommand($criteria);
123
		return $this->onExecuteCommand($command, $command->queryRow());
124
	}
125
 
126
	/**
127
	 * Find one or more matching records.
128
	 * @param TSqlCriteria $criteria
129
	 * @return TDbDataReader record reader.
130
	 */
131
	public function findAll($criteria)
132
	{
133
		$command = $this->getFindCommand($criteria);
134
		return $this->onExecuteCommand($command, $command->query());
135
	}
136
 
137
	/**
138
	 * Build the find command from the criteria. Limit, Offset and Ordering are applied if applicable.
139
	 * @param TSqlCriteria $criteria
140
	 * @return TDbCommand.
141
	 */
142
	protected function getFindCommand($criteria)
143
	{
144
		if($criteria===null)
145
			return $this->getBuilder()->createFindCommand();
146
		$where = $criteria->getCondition();
147
		$parameters = $criteria->getParameters()->toArray();
148
		$ordering = $criteria->getOrdersBy();
149
		$limit = $criteria->getLimit();
150
		$offset = $criteria->getOffset();
151
		$command = $this->getBuilder()->createFindCommand($where,$parameters,$ordering,$limit,$offset);
152
		$this->onCreateCommand($command, $criteria);
153
		return $command;
154
	}
155
 
156
	/**
157
	 * @param mixed primary key value, or composite key values as array.
158
	 * @return array matching record.
159
	 */
160
	public function findByPk($keys)
161
	{
162
		list($where, $parameters) = $this->getPrimaryKeyCondition((array)$keys);
163
		$command = $this->getBuilder()->createFindCommand($where, $parameters);
164
		$this->onCreateCommand($command, new TSqlCriteria($where,$parameters));
165
		return $this->onExecuteCommand($command, $command->queryRow());
166
	}
167
 
168
	/**
169
	 * @param array multiple primary key values or composite value arrays
170
	 * @return TDbDataReader record reader.
171
	 */
172
	public function findAllByPk($keys)
173
	{
174
		$where = $this->getCompositeKeyCondition((array)$keys);
175
		$command = $this->getBuilder()->createFindCommand($where);
176
		$this->onCreateCommand($command, new TSqlCriteria($where,$keys));
177
		return $this->onExecuteCommand($command,$command->query());
178
	}
179
 
180
	public function findAllByIndex($criteria,$fields,$values)
181
	{
182
		$index = $this->getIndexKeyCondition($this->getTableInfo(),$fields,$values);
183
		if(strlen($where = $criteria->getCondition())>0)
184
			$criteria->setCondition("({$index}) AND ({$where})");
185
		else
186
			$criteria->setCondition($index);
187
		$command = $this->getFindCommand($criteria);
188
		$this->onCreateCommand($command, $criteria);
189
		return $this->onExecuteCommand($command,$command->query());
190
	}
191
 
192
	/**
193
	 * @param array multiple primary key values or composite value arrays
194
	 * @return integer number of rows affected.
195
	 */
196
	public function deleteByPk($keys)
197
	{
198
		$where = $this->getCompositeKeyCondition((array)$keys);
199
		$command = $this->getBuilder()->createDeleteCommand($where);
200
		$this->onCreateCommand($command, new TSqlCriteria($where,$keys));
201
		$command->prepare();
202
		return $this->onExecuteCommand($command,$command->execute());
203
	}
204
 
205
	public function getIndexKeyCondition($table,$fields,$values)
206
	{
207
		if (!count($values))
208
			return 'FALSE';
209
		$columns = array();
210
		$tableName = $table->getTableFullName();
211
		foreach($fields as $field)
212
			$columns[] = $tableName.'.'.$table->getColumn($field)->getColumnName();
213
		return '('.implode(', ',$columns).') IN '.$this->quoteTuple($values);
214
	}
215
 
216
	/**
217
	 * Construct a "pk IN ('key1', 'key2', ...)" criteria.
218
	 * @param array values for IN predicate
219
	 * @param string SQL string for primary keys IN a list.
220
	 */
221
	protected function getCompositeKeyCondition($values)
222
	{
223
		$primary = $this->getTableInfo()->getPrimaryKeys();
224
		$count = count($primary);
225
		if($count===0)
226
		{
227
			throw new TDbException('dbtablegateway_no_primary_key_found',
228
				$this->getTableInfo()->getTableFullName());
229
		}
230
		if(!is_array($values) || count($values) === 0)
231
		{
232
			throw new TDbException('dbtablegateway_missing_pk_values',
233
				$this->getTableInfo()->getTableFullName());
234
		}
235
		if($count>1 && !is_array($values[0]))
236
			$values = array($values);
237
		if($count > 1 && count($values[0]) !== $count)
238
		{
239
			throw new TDbException('dbtablegateway_pk_value_count_mismatch',
240
				$this->getTableInfo()->getTableFullName());
241
		}
242
		return $this->getIndexKeyCondition($this->getTableInfo(),$primary, $values);
243
	}
244
 
245
	/**
246
	 * @param TDbConnection database connection.
247
	 * @param array values
248
	 * @return string quoted recursive tuple values, e.g. "('val1', 'val2')".
249
	 */
250
	protected function quoteTuple($array)
251
	{
252
		$conn = $this->getDbConnection();
253
		$data = array();
254
		foreach($array as $k=>$v)
255
			$data[] = is_array($v) ? $this->quoteTuple($v) : $conn->quoteString($v);
256
		return '('.implode(', ', $data).')';
257
	}
258
 
259
	/**
260
	 * Create the condition and parameters for find by primary.
261
	 * @param array primary key values
262
	 * @return array tuple($where, $parameters)
263
	 */
264
	protected function getPrimaryKeyCondition($values)
265
	{
266
		$primary = $this->getTableInfo()->getPrimaryKeys();
267
		if(count($primary)===0)
268
		{
269
			throw new TDbException('dbtablegateway_no_primary_key_found',
270
				$this->getTableInfo()->getTableFullName());
271
		}
272
		$criteria=array();
273
		$bindings=array();
274
		$i = 0;
275
		foreach($primary as $key)
276
		{
277
			$column = $this->getTableInfo()->getColumn($key)->getColumnName();
278
			$criteria[] = $column.' = :'.$key;
279
			$bindings[$key] = $values[$i++];
280
		}
281
		return array(implode(' AND ', $criteria), $bindings);
282
	}
283
 
284
	/**
285
	 * Find one matching records for arbituary SQL.
286
	 * @param TSqlCriteria $criteria
287
	 * @return TDbDataReader record reader.
288
	 */
289
	public function findBySql($criteria)
290
	{
291
		$command = $this->getSqlCommand($criteria);
292
		return $this->onExecuteCommand($command, $command->queryRow());
293
	}
294
 
295
	/**
296
	 * Find zero or more matching records for arbituary SQL.
297
	 * @param TSqlCriteria $criteria
298
	 * @return TDbDataReader record reader.
299
	 */
300
	public function findAllBySql($criteria)
301
	{
302
		$command = $this->getSqlCommand($criteria);
303
		return $this->onExecuteCommand($command, $command->query());
304
	}
305
 
306
	/**
307
	 * Build sql command from the criteria. Limit, Offset and Ordering are applied if applicable.
308
	 * @param TSqlCriteria $criteria
309
	 * @return TDbCommand command corresponding to the criteria.
310
	 */
311
	protected function getSqlCommand($criteria)
312
	{
313
		$sql = $criteria->getCondition();
314
		$ordering = $criteria->getOrdersBy();
315
		$limit = $criteria->getLimit();
316
		$offset = $criteria->getOffset();
317
		if(count($ordering) > 0)
318
			$sql = $this->getBuilder()->applyOrdering($sql, $ordering);
319
		if($limit>=0 || $offset>=0)
320
			$sql = $this->getBuilder()->applyLimitOffset($sql, $limit, $offset);
321
		$command = $this->getBuilder()->createCommand($sql);
322
		$this->getBuilder()->bindArrayValues($command, $criteria->getParameters()->toArray());
323
		$this->onCreateCommand($command, $criteria);
324
		return $command;
325
	}
326
 
327
	/**
328
	 * @param TSqlCriteria $criteria
329
	 * @return integer number of records.
330
	 */
331
	public function count($criteria)
332
	{
333
		if($criteria===null)
334
			return (int)$this->getBuilder()->createCountCommand()->queryScalar();
335
		$where = $criteria->getCondition();
336
		$parameters = $criteria->getParameters()->toArray();
337
		$ordering = $criteria->getOrdersBy();
338
		$limit = $criteria->getLimit();
339
		$offset = $criteria->getOffset();
340
		$command = $this->getBuilder()->createCountCommand($where,$parameters,$ordering,$limit,$offset);
341
		$this->onCreateCommand($command, $criteria);
342
		return $this->onExecuteCommand($command, (int)$command->queryScalar());
343
	}
344
 
345
	/**
346
	 * Inserts a new record into the table. Each array key must
347
	 * correspond to a column name in the table unless a null value is permitted.
348
	 * @param array new record data.
349
	 * @return mixed last insert id if one column contains a serial or sequence,
350
	 * otherwise true if command executes successfully and affected 1 or more rows.
351
	 */
352
	public function insert($data)
353
	{
354
		$command=$this->getBuilder()->createInsertCommand($data);
355
		$this->onCreateCommand($command, new TSqlCriteria(null,$data));
356
		$command->prepare();
357
		if($this->onExecuteCommand($command, $command->execute()) > 0)
358
		{
359
			$value = $this->getLastInsertId();
360
			return $value !== null ? $value : true;
361
		}
362
		return false;
363
	}
364
 
365
	/**
366
	 * Iterate through all the columns and returns the last insert id of the
367
	 * first column that has a sequence or serial.
368
	 * @return mixed last insert id, null if none is found.
369
	 */
370
	public function getLastInsertID()
371
	{
372
		return $this->getBuilder()->getLastInsertID();
373
	}
374
 
375
	/**
376
	 * @param string __call method name
377
	 * @param string criteria conditions
378
	 * @param array method arguments
379
	 * @return TActiveRecordCriteria criteria created from the method name and its arguments.
380
	 */
381
	public function createCriteriaFromString($method, $condition, $args)
382
	{
383
		$fields = $this->extractMatchingConditions($method, $condition);
384
		$args=count($args) === 1 && is_array($args[0]) ? $args[0] : $args;
385
		if(count($fields)>count($args))
386
		{
387
			throw new TDbException('dbtablegateway_mismatch_args_exception',
388
				$method,count($fields),count($args));
389
		}
390
		return new TSqlCriteria(implode(' ',$fields), $args);
391
	}
392
 
393
	/**
394
	 * Calculates the AND/OR condition from dynamic method substrings using
395
	 * table meta data, allows for any AND-OR combinations.
396
	 * @param string dynamic method name
397
	 * @param string dynamic method search criteria
398
	 * @return array search condition substrings
399
	 */
400
	protected function extractMatchingConditions($method, $condition)
401
	{
402
		$table = $this->getTableInfo();
403
		$columns = $table->getLowerCaseColumnNames();
404
		$regexp = '/('.implode('|', array_keys($columns)).')(and|_and_|or|_or_)?/i';
405
		$matches = array();
406
		if(!preg_match_all($regexp, strtolower($condition), $matches,PREG_SET_ORDER))
407
		{
408
			throw new TDbException('dbtablegateway_mismatch_column_name',
409
				$method, implode(', ', $columns), $table->getTableFullName());
410
		}
411
 
412
		$fields = array();
413
		foreach($matches as $match)
414
		{
415
			$key = $columns[$match[1]];
416
			$column = $table->getColumn($key)->getColumnName();
417
			$sql = $column . ' = ? ';
418
			if(count($match) > 2)
419
				$sql .= strtoupper(str_replace('_', '', $match[2]));
420
			$fields[] = $sql;
421
		}
422
		return $fields;
423
	}
424
 
425
	/**
426
	 * Raised when a command is prepared and parameter binding is completed.
427
	 * The parameter object is TDataGatewayEventParameter of which the
428
	 * {@link TDataGatewayEventParameter::getCommand Command} property can be
429
	 * inspected to obtain the sql query to be executed.
430
	 * @param TDataGatewayCommand originator $sender
431
	 * @param TDataGatewayEventParameter
432
	 */
433
	public function onCreateCommand($command, $criteria)
434
	{
435
		$this->raiseEvent('OnCreateCommand', $this, new TDataGatewayEventParameter($command,$criteria));
436
	}
437
 
438
	/**
439
	 * Raised when a command is executed and the result from the database was returned.
440
	 * The parameter object is TDataGatewayResultEventParameter of which the
441
	 * {@link TDataGatewayEventParameter::getResult Result} property contains
442
	 * the data return from the database. The data returned can be changed
443
	 * by setting the {@link TDataGatewayEventParameter::setResult Result} property.
444
	 * @param TDataGatewayCommand originator $sender
445
	 * @param TDataGatewayResultEventParameter
446
	 */
447
	public function onExecuteCommand($command, $result)
448
	{
449
		$parameter = new TDataGatewayResultEventParameter($command, $result);
450
		$this->raiseEvent('OnExecuteCommand', $this, $parameter);
451
		return $parameter->getResult();
452
	}
453
}
454
 
455
/**
456
 * TDataGatewayEventParameter class contains the TDbCommand to be executed as
457
 * well as the criteria object.
458
 *
459
 * @author Wei Zhuo <weizho[at]gmail[dot]com>
460
 * @version $Id$
461
 * @package System.Data.DataGateway
462
 * @since 3.1
463
 */
464
class TDataGatewayEventParameter extends TEventParameter
465
{
466
	private $_command;
467
	private $_criteria;
468
 
469
	public function __construct($command,$criteria)
470
	{
471
		$this->_command=$command;
472
		$this->_criteria=$criteria;
473
	}
474
 
475
	/**
476
	 * The database command to be executed. Do not rebind the parameters or change
477
	 * the sql query string.
478
	 * @return TDbCommand command to be executed.
479
	 */
480
	public function getCommand()
481
	{
482
		return $this->_command;
483
	}
484
 
485
	/**
486
	 * @return TSqlCriteria criteria used to bind the sql query parameters.
487
	 */
488
	public function getCriteria()
489
	{
490
		return $this->_criteria;
491
	}
492
}
493
 
494
/**
495
 * TDataGatewayResultEventParameter contains the TDbCommand executed and the resulting
496
 * data returned from the database. The data can be changed by changing the
497
 * {@link setResult Result} property.
498
 *
499
 * @author Wei Zhuo <weizho[at]gmail[dot]com>
500
 * @version $Id$
501
 * @package System.Data.DataGateway
502
 * @since 3.1
503
 */
504
class TDataGatewayResultEventParameter extends TEventParameter
505
{
506
	private $_command;
507
	private $_result;
508
 
509
	public function __construct($command,$result)
510
	{
511
		$this->_command=$command;
512
		$this->_result=$result;
513
	}
514
 
515
	/**
516
	 * @return TDbCommand database command executed.
517
	 */
518
	public function getCommand()
519
	{
520
		return $this->_command;
521
	}
522
 
523
	/**
524
	 * @return mixed result returned from executing the command.
525
	 */
526
	public function getResult()
527
	{
528
		return $this->_result;
529
	}
530
 
531
	/**
532
	 * @param mixed change the result returned by the gateway.
533
	 */
534
	public function setResult($value)
535
	{
536
		$this->_result=$value;
537
	}
538
}
539
 
540
?>