Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/*
3
 *  $Id: BasePeer.php 1608 2010-03-15 23:09:22Z francois $
4
 *
5
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16
 *
17
 * This software consists of voluntary contributions made by many individuals
18
 * and is licensed under the LGPL. For more information please see
19
 * <http://propel.phpdb.org>.
20
 */
21
 
22
/**
23
 * This is a utility class for all generated Peer classes in the system.
24
 *
25
 * Peer classes are responsible for isolating all of the database access
26
 * for a specific business object.  They execute all of the SQL
27
 * against the database.  Over time this class has grown to include
28
 * utility methods which ease execution of cross-database queries and
29
 * the implementation of concrete Peers.
30
 *
31
 * @author     Hans Lellelid <hans@xmpl.org> (Propel)
32
 * @author     Kaspars Jaudzems <kaspars.jaudzems@inbox.lv> (Propel)
33
 * @author     Heltem <heltem@o2php.com> (Propel)
34
 * @author     Frank Y. Kim <frank.kim@clearink.com> (Torque)
35
 * @author     John D. McNally <jmcnally@collab.net> (Torque)
36
 * @author     Brett McLaughlin <bmclaugh@algx.net> (Torque)
37
 * @author     Stephen Haberman <stephenh@chase3000.com> (Torque)
38
 * @version    $Revision: 1608 $
39
 * @package    propel.util
40
 */
41
class BasePeer
42
{
43
 
44
	/** Array (hash) that contains the cached mapBuilders. */
45
	private static $mapBuilders = array();
46
 
47
	/** Array (hash) that contains cached validators */
48
	private static $validatorMap = array();
49
 
50
	/**
51
	 * phpname type
52
	 * e.g. 'AuthorId'
53
	 */
54
	const TYPE_PHPNAME = 'phpName';
55
 
56
	/**
57
	 * studlyphpname type
58
	 * e.g. 'authorId'
59
	 */
60
	const TYPE_STUDLYPHPNAME = 'studlyPhpName';
61
 
62
	/**
63
	 * column (peer) name type
64
	 * e.g. 'book.AUTHOR_ID'
65
	 */
66
	const TYPE_COLNAME = 'colName';
67
 
68
	/**
69
	 * column fieldname type
70
	 * e.g. 'author_id'
71
	 */
72
	const TYPE_FIELDNAME = 'fieldName';
73
 
74
	/**
75
	 * num type
76
	 * simply the numerical array index, e.g. 4
77
	 */
78
	const TYPE_NUM = 'num';
79
 
80
	static public function getFieldnames ($classname, $type = self::TYPE_PHPNAME) {
81
 
82
		// TODO we should take care of including the peer class here
83
 
84
		$peerclass = 'Base' . $classname . 'Peer'; // TODO is this always true?
85
		$callable = array($peerclass, 'getFieldnames');
86
		$args = array($type);
87
 
88
		return call_user_func_array($callable, $args);
89
	}
90
 
91
	static public function translateFieldname($classname, $fieldname, $fromType, $toType) {
92
 
93
		// TODO we should take care of including the peer class here
94
 
95
		$peerclass = 'Base' . $classname . 'Peer'; // TODO is this always true?
96
		$callable = array($peerclass, 'translateFieldname');
97
		$args = array($fieldname, $fromType, $toType);
98
 
99
		return call_user_func_array($callable, $args);
100
	}
101
 
102
	/**
103
	 * Method to perform deletes based on values and keys in a
104
	 * Criteria.
105
	 *
106
	 * @param      Criteria $criteria The criteria to use.
107
	 * @param      PropelPDO $con A PropelPDO connection object.
108
	 * @return     int	The number of rows affected by last statement execution.  For most
109
	 * 				uses there is only one delete statement executed, so this number
110
	 * 				will correspond to the number of rows affected by the call to this
111
	 * 				method.  Note that the return value does require that this information
112
	 * 				is returned (supported) by the PDO driver.
113
	 * @throws     PropelException
114
	 */
115
	public static function doDelete(Criteria $criteria, PropelPDO $con)
116
	{
117
		$db = Propel::getDB($criteria->getDbName());
118
		$dbMap = Propel::getDatabaseMap($criteria->getDbName());
119
 
120
		// Set up a list of required tables (one DELETE statement will
121
		// be executed per table)
122
 
123
		$tables_keys = array();
124
		foreach ($criteria as $c) {
125
			foreach ($c->getAllTables() as $tableName) {
126
				$tableName2 = $criteria->getTableForAlias($tableName);
127
				if ($tableName2 !== null) {
128
					$tables_keys[$tableName2 . ' ' . $tableName] = true;
129
				} else {
130
					$tables_keys[$tableName] = true;
131
				}
132
			}
133
		} // foreach criteria->keys()
134
 
135
		$affectedRows = 0; // initialize this in case the next loop has no iterations.
136
 
137
		$tables = array_keys($tables_keys);
138
 
139
		foreach ($tables as $tableName) {
140
 
141
			$whereClause = array();
142
			$selectParams = array();
143
			foreach ($dbMap->getTable($tableName)->getColumns() as $colMap) {
144
				$key = $tableName . '.' . $colMap->getColumnName();
145
				if ($criteria->containsKey($key)) {
146
					$sb = "";
147
					$criteria->getCriterion($key)->appendPsTo($sb, $selectParams);
148
					$whereClause[] = $sb;
149
				}
150
			}
151
 
152
			if (empty($whereClause)) {
153
				throw new PropelException("Cowardly refusing to delete from table $tableName with empty WHERE clause.");
154
			}
155
 
156
			// Execute the statement.
157
			try {
158
				$sql = "DELETE FROM " . $tableName . " WHERE " .  implode(" AND ", $whereClause);
159
				$stmt = $con->prepare($sql);
160
				self::populateStmtValues($stmt, $selectParams, $dbMap, $db);
161
				$stmt->execute();
162
				$affectedRows = $stmt->rowCount();
163
			} catch (Exception $e) {
164
				Propel::log($e->getMessage(), Propel::LOG_ERR);
165
				throw new PropelException("Unable to execute DELETE statement.",$e);
166
			}
167
 
168
		} // for each table
169
 
170
		return $affectedRows;
171
	}
172
 
173
	/**
174
	 * Method to deletes all contents of specified table.
175
	 *
176
	 * This method is invoked from generated Peer classes like this:
177
	 * <code>
178
	 * public static function doDeleteAll($con = null)
179
	 * {
180
	 *   if ($con === null) $con = Propel::getConnection(self::DATABASE_NAME);
181
	 *   BasePeer::doDeleteAll(self::TABLE_NAME, $con);
182
	 * }
183
	 * </code>
184
	 *
185
	 * @param      string $tableName The name of the table to empty.
186
	 * @param      PropelPDO $con A PropelPDO connection object.
187
	 * @return     int	The number of rows affected by the statement.  Note
188
	 * 				that the return value does require that this information
189
	 * 				is returned (supported) by the Creole db driver.
190
	 * @throws     PropelException - wrapping SQLException caught from statement execution.
191
	 */
192
	public static function doDeleteAll($tableName, PropelPDO $con)
193
	{
194
		try {
195
			$sql = "DELETE FROM " . $tableName;
196
			$stmt = $con->prepare($sql);
197
			$stmt->execute();
198
			return $stmt->rowCount();
199
		} catch (Exception $e) {
200
			Propel::log($e->getMessage(), Propel::LOG_ERR);
201
			throw new PropelException("Unable to perform DELETE ALL operation.", $e);
202
		}
203
	}
204
 
205
	/**
206
	 * Method to perform inserts based on values and keys in a
207
	 * Criteria.
208
	 * <p>
209
	 * If the primary key is auto incremented the data in Criteria
210
	 * will be inserted and the auto increment value will be returned.
211
	 * <p>
212
	 * If the primary key is included in Criteria then that value will
213
	 * be used to insert the row.
214
	 * <p>
215
	 * If no primary key is included in Criteria then we will try to
216
	 * figure out the primary key from the database map and insert the
217
	 * row with the next available id using util.db.IDBroker.
218
	 * <p>
219
	 * If no primary key is defined for the table the values will be
220
	 * inserted as specified in Criteria and null will be returned.
221
	 *
222
	 * @param      Criteria $criteria Object containing values to insert.
223
	 * @param      PropelPDO $con A PropelPDO connection.
224
	 * @return     mixed The primary key for the new row if (and only if!) the primary key
225
	 *				is auto-generated.  Otherwise will return <code>null</code>.
226
	 * @throws     PropelException
227
	 */
228
	public static function doInsert(Criteria $criteria, PropelPDO $con) {
229
 
230
		// the primary key
231
		$id = null;
232
 
233
		$db = Propel::getDB($criteria->getDbName());
234
 
235
		// Get the table name and method for determining the primary
236
		// key value.
237
		$keys = $criteria->keys();
238
		if (!empty($keys)) {
239
			$tableName = $criteria->getTableName( $keys[0] );
240
		} else {
241
			throw new PropelException("Database insert attempted without anything specified to insert");
242
		}
243
 
244
		$dbMap = Propel::getDatabaseMap($criteria->getDbName());
245
		$tableMap = $dbMap->getTable($tableName);
246
		$keyInfo = $tableMap->getPrimaryKeyMethodInfo();
247
		$useIdGen = $tableMap->isUseIdGenerator();
248
		//$keyGen = $con->getIdGenerator();
249
 
250
		$pk = self::getPrimaryKey($criteria);
251
 
252
		// only get a new key value if you need to
253
		// the reason is that a primary key might be defined
254
		// but you are still going to set its value. for example:
255
		// a join table where both keys are primary and you are
256
		// setting both columns with your own values
257
 
258
		// pk will be null if there is no primary key defined for the table
259
		// we're inserting into.
260
		if ($pk !== null && $useIdGen && !$criteria->keyContainsValue($pk->getFullyQualifiedName()) && $db->isGetIdBeforeInsert()) {
261
			try {
262
				$id = $db->getId($con, $keyInfo);
263
			} catch (Exception $e) {
264
				throw new PropelException("Unable to get sequence id.", $e);
265
			}
266
			$criteria->add($pk->getFullyQualifiedName(), $id);
267
		}
268
 
269
		try {
270
			$adapter = Propel::getDB($criteria->getDBName());
271
 
272
			$qualifiedCols = $criteria->keys(); // we need table.column cols when populating values
273
			$columns = array(); // but just 'column' cols for the SQL
274
			foreach ($qualifiedCols as $qualifiedCol) {
275
				$columns[] = substr($qualifiedCol, strrpos($qualifiedCol, '.') + 1);
276
			}
277
 
278
			// add identifiers
279
			if ($adapter->useQuoteIdentifier()) {
280
				$columns = array_map(array($adapter, 'quoteIdentifier'), $columns);
281
			}
282
 
283
			$sql = 'INSERT INTO ' . $tableName
284
			. ' (' . implode(',', $columns) . ')'
285
			. ' VALUES (';
286
			// . substr(str_repeat("?,", count($columns)), 0, -1) .
287
			for($p=1, $cnt=count($columns); $p <= $cnt; $p++) {
288
				$sql .= ':p'.$p;
289
				if ($p !== $cnt) $sql .= ',';
290
			}
291
			$sql .= ')';
292
 
293
			$stmt = $con->prepare($sql);
294
			self::populateStmtValues($stmt, self::buildParams($qualifiedCols, $criteria), $dbMap, $db);
295
			$stmt->execute();
296
 
297
		} catch (Exception $e) {
298
			Propel::log($e->getMessage(), Propel::LOG_ERR);
299
			throw new PropelException("Unable to execute INSERT statement.", $e);
300
		}
301
 
302
		// If the primary key column is auto-incremented, get the id now.
303
		if ($pk !== null && $useIdGen && $db->isGetIdAfterInsert()) {
304
			try {
305
				$id = $db->getId($con, $keyInfo);
306
			} catch (Exception $e) {
307
				throw new PropelException("Unable to get autoincrement id.", $e);
308
			}
309
		}
310
 
311
		return $id;
312
	}
313
 
314
	/**
315
	 * Method used to update rows in the DB.  Rows are selected based
316
	 * on selectCriteria and updated using values in updateValues.
317
	 * <p>
318
	 * Use this method for performing an update of the kind:
319
	 * <p>
320
	 * WHERE some_column = some value AND could_have_another_column =
321
	 * another value AND so on.
322
	 *
323
	 * @param      $selectCriteria A Criteria object containing values used in where
324
	 *		clause.
325
	 * @param      $updateValues A Criteria object containing values used in set
326
	 *		clause.
327
	 * @param      PropelPDO $con The PropelPDO connection object to use.
328
	 * @return     int	The number of rows affected by last update statement.  For most
329
	 * 				uses there is only one update statement executed, so this number
330
	 * 				will correspond to the number of rows affected by the call to this
331
	 * 				method.  Note that the return value does require that this information
332
	 * 				is returned (supported) by the Creole db driver.
333
	 * @throws     PropelException
334
	 */
335
	public static function doUpdate(Criteria $selectCriteria, Criteria $updateValues, PropelPDO $con) {
336
 
337
		$db = Propel::getDB($selectCriteria->getDbName());
338
		$dbMap = Propel::getDatabaseMap($selectCriteria->getDbName());
339
 
340
		// Get list of required tables, containing all columns
341
		$tablesColumns = $selectCriteria->getTablesColumns();
342
 
343
		// we also need the columns for the update SQL
344
		$updateTablesColumns = $updateValues->getTablesColumns();
345
 
346
		$affectedRows = 0; // initialize this in case the next loop has no iterations.
347
 
348
		foreach ($tablesColumns as $tableName => $columns) {
349
 
350
			$whereClause = array();
351
 
352
			$params = array();
353
 
354
			$stmt = null;
355
			try {
356
 
357
				$sql = "UPDATE " . $tableName . " SET ";
358
				$p = 1;
359
				foreach ($updateTablesColumns[$tableName] as $col) {
360
					$updateColumnName = substr($col, strrpos($col, '.') + 1);
361
					// add identifiers for the actual database?
362
					if ($db->useQuoteIdentifier()) {
363
						$updateColumnName = $db->quoteIdentifier($updateColumnName);
364
					}
365
					if ($updateValues->getComparison($col) != Criteria::CUSTOM_EQUAL) {
366
						$sql .= $updateColumnName . '=:p'.$p++.', ';
367
					} else {
368
						$param = $updateValues->get($col);
369
						$sql .= $updateColumnName . ' = ';
370
						if (is_array($param)) {
371
							if (isset($param['raw'])) {
372
								$raw = $param['raw'];
373
								$rawcvt = '';
374
								// parse the $params['raw'] for ? chars
375
								for($r=0,$len=strlen($raw); $r < $len; $r++) {
376
									if ($raw{$r} == '?') {
377
										$rawcvt .= ':p'.$p++;
378
									} else {
379
										$rawcvt .= $raw{$r};
380
									}
381
								}
382
								$sql .= $rawcvt . ', ';
383
							} else {
384
								$sql .= ':p'.$p++.', ';
385
							}
386
							if (isset($param['value'])) {
387
								$updateValues->put($col, $param['value']);
388
							}
389
						} else {
390
							$updateValues->remove($col);
391
							$sql .= $param . ', ';
392
						}
393
					}
394
				}
395
 
396
				$params = self::buildParams($updateTablesColumns[$tableName], $updateValues);
397
 
398
				foreach ($columns as $colName) {
399
					$sb = "";
400
					$selectCriteria->getCriterion($colName)->appendPsTo($sb, $params);
401
					$whereClause[] = $sb;
402
				}
403
 
404
				$sql = substr($sql, 0, -2) . " WHERE " .  implode(" AND ", $whereClause);
405
 
406
				$stmt = $con->prepare($sql);
407
 
408
				// Replace ':p?' with the actual values
409
				self::populateStmtValues($stmt, $params, $dbMap, $db);
410
 
411
				$stmt->execute();
412
 
413
				$affectedRows = $stmt->rowCount();
414
 
415
				$stmt = null; // close
416
 
417
			} catch (Exception $e) {
418
				if ($stmt) $stmt = null; // close
419
				Propel::log($e->getMessage(), Propel::LOG_ERR);
420
				throw new PropelException("Unable to execute UPDATE statement.", $e);
421
			}
422
 
423
		} // foreach table in the criteria
424
 
425
		return $affectedRows;
426
	}
427
 
428
	/**
429
	 * Executes query build by createSelectSql() and returns the resultset statement.
430
	 *
431
	 * @param      Criteria $criteria A Criteria.
432
	 * @param      PropelPDO $con A PropelPDO connection to use.
433
	 * @return     PDOStatement The resultset.
434
	 * @throws     PropelException
435
	 * @see        createSelectSql()
436
	 */
437
	public static function doSelect(Criteria $criteria, PropelPDO $con = null)
438
	{
439
		$dbMap = Propel::getDatabaseMap($criteria->getDbName());
440
		$db = Propel::getDB($criteria->getDbName());
441
 
442
		if ($con === null) {
443
			$con = Propel::getConnection($criteria->getDbName(), Propel::CONNECTION_READ);
444
		}
445
 
446
		$stmt = null;
447
 
448
		if ($criteria->isUseTransaction()) $con->beginTransaction();
449
 
450
		try {
451
 
452
			$params = array();
453
			$sql = self::createSelectSql($criteria, $params);
454
 
455
			$stmt = $con->prepare($sql);
456
 
457
			self::populateStmtValues($stmt, $params, $dbMap, $db);
458
 
459
			$stmt->execute();
460
 
461
			if ($criteria->isUseTransaction()) $con->commit();
462
 
463
		} catch (Exception $e) {
464
			if ($stmt) $stmt = null; // close
465
			if ($criteria->isUseTransaction()) $con->rollBack();
466
			Propel::log($e->getMessage(), Propel::LOG_ERR);
467
			throw new PropelException($e);
468
		}
469
 
470
		return $stmt;
471
	}
472
 
473
	/**
474
	 * Executes a COUNT query using either a simple SQL rewrite or, for more complex queries, a
475
	 * sub-select of the SQL created by createSelectSql() and returns the statement.
476
	 *
477
	 * @param      Criteria $criteria A Criteria.
478
	 * @param      PropelPDO $con A PropelPDO connection to use.
479
	 * @return     PDOStatement The resultset statement.
480
	 * @throws     PropelException
481
	 * @see        createSelectSql()
482
	 */
483
	public static function doCount(Criteria $criteria, PropelPDO $con = null)
484
	{
485
		$dbMap = Propel::getDatabaseMap($criteria->getDbName());
486
		$db = Propel::getDB($criteria->getDbName());
487
 
488
		if ($con === null) {
489
			$con = Propel::getConnection($criteria->getDbName(), Propel::CONNECTION_READ);
490
		}
491
 
492
		$stmt = null;
493
 
494
		if ($criteria->isUseTransaction()) $con->beginTransaction();
495
 
496
		$needsComplexCount = ($criteria->getGroupByColumns() || $criteria->getOffset()
497
								|| $criteria->getLimit() || $criteria->getHaving() || in_array(Criteria::DISTINCT, $criteria->getSelectModifiers()));
498
 
499
		try {
500
 
501
			$params = array();
502
 
503
			if ($needsComplexCount) {
504
				if (self::needsSelectAliases($criteria)) {
505
					self::turnSelectColumnsToAliases($criteria);
506
				}
507
				$selectSql = self::createSelectSql($criteria, $params);
508
				$sql = 'SELECT COUNT(*) FROM (' . $selectSql . ') AS propelmatch4cnt';
509
			} else {
510
				// Replace SELECT columns with COUNT(*)
511
				$criteria->clearSelectColumns()->addSelectColumn('COUNT(*)');
512
				$sql = self::createSelectSql($criteria, $params);
513
			}
514
 
515
			$stmt = $con->prepare($sql);
516
			self::populateStmtValues($stmt, $params, $dbMap, $db);
517
			$stmt->execute();
518
 
519
			if ($criteria->isUseTransaction()) $con->commit();
520
 
521
		} catch (Exception $e) {
522
			if ($stmt) $stmt = null; // close
523
			if ($criteria->isUseTransaction()) $con->rollBack();
524
			Propel::log($e->getMessage(), Propel::LOG_ERR);
525
			throw new PropelException($e);
526
		}
527
 
528
		return $stmt;
529
	}
530
 
531
	/**
532
	 * Populates values in a prepared statement.
533
	 *
534
	 * This method is designed to work with the createSelectSql() method, which creates
535
	 * both the SELECT SQL statement and populates a passed-in array of parameter
536
	 * values that should be substituted.
537
	 *
538
	 * <code>
539
	 * $params = array();
540
	 * $sql = BasePeer::createSelectSql($criteria, $params);
541
	 * BasePeer::populateStmtValues($stmt, $params, Propel::getDatabaseMap($critera->getDbName()), Propel::getDB($criteria->getDbName()));
542
	 * </code>
543
	 *
544
	 * @param      PDOStatement $stmt
545
	 * @param      array $params array('column' => ..., 'table' => ..., 'value' => ...)
546
	 * @param      DatabaseMap $dbMap
547
	 * @return     int The number of params replaced.
548
	 * @see        createSelectSql()
549
	 * @see        doSelect()
550
	 */
551
	public static function populateStmtValues(PDOStatement $stmt, array $params, DatabaseMap $dbMap, DBAdapter $db)
552
	{
553
		$i = 1;
554
		foreach ($params as $param) {
555
			$tableName = $param['table'];
556
			$columnName = $param['column'];
557
			$value = $param['value'];
558
 
559
			if ($value === null) {
560
 
561
				$stmt->bindValue(':p'.$i++, null, PDO::PARAM_NULL);
562
 
563
			} elseif (isset($tableName) ) {
564
 
565
				$cMap = $dbMap->getTable($tableName)->getColumn($columnName);
566
				$type = $cMap->getType();
567
				$pdoType = $cMap->getPdoType();
568
 
569
				// FIXME - This is a temporary hack to get around apparent bugs w/ PDO+MYSQL
570
				// See http://pecl.php.net/bugs/bug.php?id=9919
571
				if ($pdoType == PDO::PARAM_BOOL && $db instanceof DBMySQL) {
572
					$value = (int) $value;
573
					$pdoType = PDO::PARAM_INT;
574
				} elseif (is_numeric($value) && $cMap->isEpochTemporal()) { // it's a timestamp that needs to be formatted
575
					if ($type == PropelColumnTypes::TIMESTAMP) {
576
						$value = date($db->getTimestampFormatter(), $value);
577
					} else if ($type == PropelColumnTypes::DATE) {
578
						$value = date($db->getDateFormatter(), $value);
579
					} else if ($type == PropelColumnTypes::TIME) {
580
						$value = date($db->getTimeFormatter(), $value);
581
					}
582
				} elseif ($value instanceof DateTime && $cMap->isTemporal()) { // it's a timestamp that needs to be formatted
583
					if ($type == PropelColumnTypes::TIMESTAMP || $type == PropelColumnTypes::BU_TIMESTAMP) {
584
						$value = $value->format($db->getTimestampFormatter());
585
					} else if ($type == PropelColumnTypes::DATE || $type == PropelColumnTypes::BU_DATE) {
586
						$value = $value->format($db->getDateFormatter());
587
					} else if ($type == PropelColumnTypes::TIME) {
588
						$value = $value->format($db->getTimeFormatter());
589
					}
590
				} elseif (is_resource($value) && $cMap->isLob()) {
591
					// we always need to make sure that the stream is rewound, otherwise nothing will
592
					// get written to database.
593
					rewind($value);
594
				}
595
 
596
				$stmt->bindValue(':p'.$i++, $value, $pdoType);
597
			} else {
598
				$stmt->bindValue(':p'.$i++, $value);
599
			}
600
		} // foreach
601
	}
602
 
603
	/**
604
	 * Applies any validators that were defined in the schema to the specified columns.
605
	 *
606
	 * @param      string $dbName The name of the database
607
	 * @param      string $tableName The name of the table
608
	 * @param      array $columns Array of column names as key and column values as value.
609
	 */
610
	public static function doValidate($dbName, $tableName, $columns)
611
	{
612
		$dbMap = Propel::getDatabaseMap($dbName);
613
		$tableMap = $dbMap->getTable($tableName);
614
		$failureMap = array(); // map of ValidationFailed objects
615
		foreach ($columns as $colName => $colValue) {
616
			if ($tableMap->containsColumn($colName)) {
617
				$col = $tableMap->getColumn($colName);
618
				foreach ($col->getValidators() as $validatorMap) {
619
					$validator = BasePeer::getValidator($validatorMap->getClass());
620
					if ($validator && ($col->isNotNull() || $colValue !== null) && $validator->isValid($validatorMap, $colValue) === false) {
621
						if (!isset($failureMap[$colName])) { // for now we do one ValidationFailed per column, not per rule
622
							$failureMap[$colName] = new ValidationFailed($colName, $validatorMap->getMessage(), $validator);
623
						}
624
					}
625
				}
626
			}
627
		}
628
		return (!empty($failureMap) ? $failureMap : true);
629
	}
630
 
631
	/**
632
	 * Helper method which returns the primary key contained
633
	 * in the given Criteria object.
634
	 *
635
	 * @param      Criteria $criteria A Criteria.
636
	 * @return     ColumnMap If the Criteria object contains a primary
637
	 *		  key, or null if it doesn't.
638
	 * @throws     PropelException
639
	 */
640
	private static function getPrimaryKey(Criteria $criteria)
641
	{
642
		// Assume all the keys are for the same table.
643
		$keys = $criteria->keys();
644
		$key = $keys[0];
645
		$table = $criteria->getTableName($key);
646
 
647
		$pk = null;
648
 
649
		if (!empty($table)) {
650
 
651
			$dbMap = Propel::getDatabaseMap($criteria->getDbName());
652
 
653
			if ($dbMap === null) {
654
				throw new PropelException("\$dbMap is null");
655
			}
656
 
657
			if ($dbMap->getTable($table) === null) {
658
				throw new PropelException("\$dbMap->getTable() is null");
659
			}
660
 
661
			$columns = $dbMap->getTable($table)->getColumns();
662
			foreach (array_keys($columns) as $key) {
663
				if ($columns[$key]->isPrimaryKey()) {
664
					$pk = $columns[$key];
665
					break;
666
				}
667
			}
668
		}
669
		return $pk;
670
	}
671
 
672
	/**
673
	 * Checks whether the Criteria needs to use column aliasing
674
	 * This is implemented in a service class rather than in Criteria itself
675
	 * in order to avoid doing the tests when it's not necessary (e.g. for SELECTs)
676
	 */
677
	public static function needsSelectAliases(Criteria $criteria)
678
	{
679
		$columnNames = array();
680
		foreach ($criteria->getSelectColumns() as $fullyQualifiedColumnName) {
681
			if ($pos = strrpos($fullyQualifiedColumnName, '.')) {
682
				$columnName = substr($fullyQualifiedColumnName, $pos);
683
				if (isset($columnNames[$columnName])) {
684
					// more than one column with the same name, so aliasing is required
685
					return true;
686
				}
687
				$columnNames[$columnName] = true;
688
			}
689
		}
690
		return false;
691
	}
692
 
693
	/**
694
	 * Ensures uniqueness of select column names by turning them all into aliases
695
	 * This is necessary for queries on more than one table when the tables share a column name
696
	 * @see http://propel.phpdb.org/trac/ticket/795
697
	 *
698
	 * @param Criteria $criteria
699
	 *
700
	 * @return Criteria The input, with Select columns replaced by aliases
701
	 */
702
	public static function turnSelectColumnsToAliases(Criteria $criteria)
703
	{
704
		$selectColumns = $criteria->getSelectColumns();
705
		// clearSelectColumns also clears the aliases, so get them too
706
		$asColumns = $criteria->getAsColumns();
707
		$criteria->clearSelectColumns();
708
		$columnAliases = $asColumns;
709
		// add the select columns back
710
		foreach ($selectColumns as $clause) {
711
			// Generate a unique alias
712
			$baseAlias = preg_replace('/\W/', '_', $clause);
713
			$alias = $baseAlias;
714
			// If it already exists, add a unique suffix
715
			$i = 0;
716
			while (isset($columnAliases[$alias])) {
717
				$i++;
718
				$alias = $baseAlias . '_' . $i;
719
			}
720
			// Add it as an alias
721
			$criteria->addAsColumn($alias, $clause);
722
			$columnAliases[$alias] = $clause;
723
		}
724
		// Add the aliases back, don't modify them
725
		foreach ($asColumns as $name => $clause) {
726
			$criteria->addAsColumn($name, $clause);
727
		}
728
 
729
		return $criteria;
730
	}
731
 
732
	/**
733
	 * Method to create an SQL query based on values in a Criteria.
734
	 *
735
	 * This method creates only prepared statement SQL (using ? where values
736
	 * will go).  The second parameter ($params) stores the values that need
737
	 * to be set before the statement is executed.  The reason we do it this way
738
	 * is to let the PDO layer handle all escaping & value formatting.
739
	 *
740
	 * @param      Criteria $criteria Criteria for the SELECT query.
741
	 * @param      array &$params Parameters that are to be replaced in prepared statement.
742
	 * @return     string
743
	 * @throws     PropelException Trouble creating the query string.
744
	 */
745
	public static function createSelectSql(Criteria $criteria, &$params)
746
	{
747
		$db = Propel::getDB($criteria->getDbName());
748
		$dbMap = Propel::getDatabaseMap($criteria->getDbName());
749
 
750
		// redundant definition $selectModifiers = array();
751
		$selectClause = array();
752
		$fromClause = array();
753
		$joinClause = array();
754
		$joinTables = array();
755
		$whereClause = array();
756
		$orderByClause = array();
757
		// redundant definition $groupByClause = array();
758
 
759
		$orderBy = $criteria->getOrderByColumns();
760
		$groupBy = $criteria->getGroupByColumns();
761
		$ignoreCase = $criteria->isIgnoreCase();
762
		$select = $criteria->getSelectColumns();
763
		$aliases = $criteria->getAsColumns();
764
 
765
		// simple copy
766
		$selectModifiers = $criteria->getSelectModifiers();
767
 
768
		// get selected columns
769
		foreach ($select as $columnName) {
770
 
771
			// expect every column to be of "table.column" formation
772
			// it could be a function:  e.g. MAX(books.price)
773
 
774
			$tableName = null;
775
 
776
			$selectClause[] = $columnName; // the full column name: e.g. MAX(books.price)
777
 
778
			$parenPos = strrpos($columnName, '(');
779
			$dotPos = strrpos($columnName, '.', ($parenPos !== false ? $parenPos : 0));
780
 
781
			// [HL] I think we really only want to worry about adding stuff to
782
			// the fromClause if this function has a TABLE.COLUMN in it at all.
783
			// e.g. COUNT(*) should not need this treatment -- or there needs to
784
			// be special treatment for '*'
785
			if ($dotPos !== false) {
786
 
787
				if ($parenPos === false) { // table.column
788
					$tableName = substr($columnName, 0, $dotPos);
789
				} else { // FUNC(table.column)
790
					$tableName = substr($columnName, $parenPos + 1, $dotPos - ($parenPos + 1));
791
					// functions may contain qualifiers so only take the last
792
					// word as the table name.
793
					// COUNT(DISTINCT books.price)
794
					$lastSpace = strpos($tableName, ' ');
795
					if ($lastSpace !== false) { // COUNT(DISTINCT books.price)
796
						$tableName = substr($tableName, $lastSpace + 1);
797
					}
798
				}
799
				$tableName2 = $criteria->getTableForAlias($tableName);
800
				if ($tableName2 !== null) {
801
					$fromClause[] = $tableName2 . ' ' . $tableName;
802
				} else {
803
					$fromClause[] = $tableName;
804
				}
805
 
806
			} // if $dotPost !== null
807
		}
808
 
809
		// set the aliases
810
		foreach ($aliases as $alias => $col) {
811
			$selectClause[] = $col . " AS " . $alias;
812
		}
813
 
814
		// add the criteria to WHERE clause
815
		// this will also add the table names to the FROM clause if they are not already
816
		// invluded via a LEFT JOIN
817
		foreach ($criteria->keys() as $key) {
818
 
819
			$criterion = $criteria->getCriterion($key);
820
			$someCriteria = $criterion->getAttachedCriterion();
821
			$someCriteriaLength = count($someCriteria);
822
			$table = null;
823
			for ($i=0; $i < $someCriteriaLength; $i++) {
824
				$tableName = $someCriteria[$i]->getTable();
825
 
826
				$table = $criteria->getTableForAlias($tableName);
827
				if ($table !== null) {
828
					$fromClause[] = $table . ' ' . $tableName;
829
				} else {
830
					$fromClause[] = $tableName;
831
					$table = $tableName;
832
				}
833
 
834
				$ignoreCase =
835
				(($criteria->isIgnoreCase()
836
				|| $someCriteria[$i]->isIgnoreCase())
837
				&& (strpos($dbMap->getTable($table)->getColumn($someCriteria[$i]->getColumn())->getType(), "VARCHAR") !== false)
838
				);
839
 
840
				$someCriteria[$i]->setIgnoreCase($ignoreCase);
841
			}
842
 
843
			$criterion->setDB($db);
844
 
845
			$sb = "";
846
			$criterion->appendPsTo($sb, $params);
847
			$whereClause[] = $sb;
848
		}
849
 
850
		// Handle joins
851
		// joins with a null join type will be added to the FROM clause and the condition added to the WHERE clause.
852
		// joins of a specified type: the LEFT side will be added to the fromClause and the RIGHT to the joinClause
853
		foreach ((array) $criteria->getJoins() as $join) {
854
			// The join might have been established using an alias name
855
			$leftTable = $join->getLeftTableName();
856
			$leftTableAlias = '';
857
			if ($realTable = $criteria->getTableForAlias($leftTable)) {
858
				$leftTableAlias = " $leftTable";
859
				$leftTable = $realTable;
860
			}
861
 
862
			$rightTable = $join->getRightTableName();
863
			$rightTableAlias = '';
864
			if ($realTable = $criteria->getTableForAlias($rightTable)) {
865
				$rightTableAlias = " $rightTable";
866
				$rightTable = $realTable;
867
			}
868
 
869
			// determine if casing is relevant.
870
			if ($ignoreCase = $criteria->isIgnoreCase()) {
871
				$leftColType = $dbMap->getTable($leftTable)->getColumn($join->getLeftColumnName())->getType();
872
				$rightColType = $dbMap->getTable($rightTable)->getColumn($join->getRightColumnName())->getType();
873
				$ignoreCase = ($leftColType == 'string' || $rightColType == 'string');
874
			}
875
 
876
			// build the condition
877
			$condition = '';
878
			foreach ($join->getConditions() as $index => $conditionDesc)
879
			{
880
				if ($ignoreCase) {
881
					$condition .= $db->ignoreCase($conditionDesc['left']) . $conditionDesc['operator'] . $db->ignoreCase($conditionDesc['right']);
882
				} else {
883
					$condition .= implode($conditionDesc);
884
				}
885
				if ($index + 1 < $join->countConditions()) {
886
					$condition .= ' AND ';
887
				}
888
			}
889
 
890
			// add 'em to the queues..
891
			if ($joinType = $join->getJoinType()) {
892
			  // real join
893
				if (!$fromClause) {
894
					$fromClause[] = $leftTable . $leftTableAlias;
895
				}
896
				$joinTables[] = $rightTable . $rightTableAlias;
897
				$joinClause[] = $join->getJoinType() . ' ' . $rightTable . $rightTableAlias . " ON ($condition)";
898
			} else {
899
			  // implicit join, translates to a where
900
				$fromClause[] = $leftTable . $leftTableAlias;
901
				$fromClause[] = $rightTable . $rightTableAlias;
902
				$whereClause[] = $condition;
903
			}
904
		}
905
 
906
		// Unique from clause elements
907
		$fromClause = array_unique($fromClause);
908
		$fromClause = array_diff($fromClause, array(''));
909
 
910
		// tables should not exist in both the from and join clauses
911
		if ($joinTables && $fromClause) {
912
			foreach ($fromClause as $fi => $ftable) {
913
				if (in_array($ftable, $joinTables)) {
914
					unset($fromClause[$fi]);
915
				}
916
			}
917
		}
918
 
919
		// Add the GROUP BY columns
920
		$groupByClause = $groupBy;
921
 
922
		$having = $criteria->getHaving();
923
		$havingString = null;
924
		if ($having !== null) {
925
			$sb = "";
926
			$having->appendPsTo($sb, $params);
927
			$havingString = $sb;
928
		}
929
 
930
		if (!empty($orderBy)) {
931
 
932
			foreach ($orderBy as $orderByColumn) {
933
 
934
				// Add function expression as-is.
935
 
936
				if (strpos($orderByColumn, '(') !== false) {
937
					$orderByClause[] = $orderByColumn;
938
					continue;
939
				}
940
 
941
				// Split orderByColumn (i.e. "table.column DESC")
942
 
943
				$dotPos = strrpos($orderByColumn, '.');
944
 
945
				if ($dotPos !== false) {
946
					$tableName = substr($orderByColumn, 0, $dotPos);
947
					$columnName = substr($orderByColumn, $dotPos+1);
948
				}
949
				else {
950
					$tableName = '';
951
					$columnName = $orderByColumn;
952
				}
953
 
954
				$spacePos = strpos($columnName, ' ');
955
 
956
				if ($spacePos !== false) {
957
					$direction = substr($columnName, $spacePos);
958
					$columnName = substr($columnName, 0, $spacePos);
959
				}
960
				else {
961
					$direction = '';
962
				}
963
 
964
				$tableAlias = $tableName;
965
				if ($aliasTableName = $criteria->getTableForAlias($tableName)) {
966
					$tableName = $aliasTableName;
967
				}
968
 
969
				$columnAlias = $columnName;
970
				if ($asColumnName = $criteria->getColumnForAs($columnName)) {
971
					$columnName = $asColumnName;
972
				}
973
 
974
				$column = $tableName ? $dbMap->getTable($tableName)->getColumn($columnName) : null;
975
 
976
				if ($criteria->isIgnoreCase() && $column && $column->isText()) {
977
					$orderByClause[] = $db->ignoreCaseInOrderBy("$tableAlias.$columnAlias") . $direction;
978
					$selectClause[] = $db->ignoreCaseInOrderBy("$tableAlias.$columnAlias");
979
				} else {
980
					$orderByClause[] = $orderByColumn;
981
				}
982
			}
983
		}
984
 
985
		if (empty($fromClause) && $criteria->getPrimaryTableName()) {
986
			$fromClause[] = $criteria->getPrimaryTableName();
987
		}
988
 
989
		// from / join tables quoten if it is necessary
990
		if ($db->useQuoteIdentifier()) {
991
			$fromClause = array_map(array($db, 'quoteIdentifierTable'), $fromClause);
992
			$joinClause = $joinClause ? $joinClause : array_map(array($db, 'quoteIdentifierTable'), $joinClause);
993
		}
994
 
995
		// build from-clause
996
		$from = '';
997
		if (!empty($joinClause) && count($fromClause) > 1) {
998
			$from .= implode(" CROSS JOIN ", $fromClause);
999
		} else {
1000
			$from .= implode(", ", $fromClause);
1001
		}
1002
 
1003
		$from .= $joinClause ? ' ' . implode(' ', $joinClause) : '';
1004
 
1005
		// Build the SQL from the arrays we compiled
1006
		$sql =  "SELECT "
1007
		.($selectModifiers ? implode(" ", $selectModifiers) . " " : "")
1008
		.implode(", ", $selectClause)
1009
		." FROM "  . $from
1010
		.($whereClause ? " WHERE ".implode(" AND ", $whereClause) : "")
1011
		.($groupByClause ? " GROUP BY ".implode(",", $groupByClause) : "")
1012
		.($havingString ? " HAVING ".$havingString : "")
1013
		.($orderByClause ? " ORDER BY ".implode(",", $orderByClause) : "");
1014
 
1015
		// APPLY OFFSET & LIMIT to the query.
1016
		if ($criteria->getLimit() || $criteria->getOffset()) {
1017
			$db->applyLimit($sql, $criteria->getOffset(), $criteria->getLimit());
1018
		}
1019
 
1020
		return $sql;
1021
	}
1022
 
1023
	/**
1024
	 * Builds a params array, like the kind populated by Criterion::appendPsTo().
1025
	 * This is useful for building an array even when it is not using the appendPsTo() method.
1026
	 * @param      array $columns
1027
	 * @param      Criteria $values
1028
	 * @return     array params array('column' => ..., 'table' => ..., 'value' => ...)
1029
	 */
1030
	private static function buildParams($columns, Criteria $values)
1031
	{
1032
		$params = array();
1033
		foreach ($columns as $key) {
1034
			if ($values->containsKey($key)) {
1035
				$crit = $values->getCriterion($key);
1036
				$params[] = array('column' => $crit->getColumn(), 'table' => $crit->getTable(), 'value' => $crit->getValue());
1037
			}
1038
		}
1039
		return $params;
1040
	}
1041
 
1042
	/**
1043
	 * This function searches for the given validator $name under propel/validator/$name.php,
1044
	 * imports and caches it.
1045
	 *
1046
	 * @param      string $classname The dot-path name of class (e.g. myapp.propel.MyValidator)
1047
	 * @return     Validator object or null if not able to instantiate validator class (and error will be logged in this case)
1048
	 */
1049
	public static function getValidator($classname)
1050
	{
1051
		try {
1052
			$v = isset(self::$validatorMap[$classname]) ? self::$validatorMap[$classname] : null;
1053
			if ($v === null) {
1054
				$cls = Propel::importClass($classname);
1055
				$v = new $cls();
1056
				self::$validatorMap[$classname] = $v;
1057
			}
1058
			return $v;
1059
		} catch (Exception $e) {
1060
			Propel::log("BasePeer::getValidator(): failed trying to instantiate " . $classname . ": ".$e->getMessage(), Propel::LOG_ERR);
1061
		}
1062
	}
1063
 
1064
}