1: <?php
2: /**
3: * Wei Framework
4: *
5: * @copyright Copyright (c) 2008-2015 Twin Huang
6: * @license http://opensource.org/licenses/mit-license.php MIT License
7: */
8:
9: namespace Wei;
10:
11: use PDO;
12:
13: /**
14: * A database service inspired by Doctrine DBAL
15: *
16: * @author Twin Huang <twinhuang@qq.com>
17: */
18: class Db extends Base
19: {
20: /**
21: * The name of PDO driver, could be mysql, sqlite or pgsql
22: *
23: * @var string
24: */
25: protected $driver = 'mysql';
26:
27: /**
28: * The database username
29: *
30: * @var string
31: */
32: protected $user;
33:
34: /**
35: * The database password
36: *
37: * @var string
38: */
39: protected $password;
40:
41: /**
42: * The hostname on which the database server resides, for mysql and pgsql
43: *
44: * @var string
45: */
46: protected $host = '127.0.0.1';
47:
48: /**
49: * The port on which the database server is running, for mysql and pgsql
50: *
51: * @var string
52: */
53: protected $port;
54:
55: /**
56: * The name of the database, for mysql and pgsql
57: *
58: * @var string
59: */
60: protected $dbname;
61:
62: /**
63: * The MySQL Unix socket (shouldn't be used with host or port), for mysql only
64: *
65: * @var string
66: */
67: protected $unixSocket;
68:
69: /**
70: * The character set, for mysql only
71: *
72: * @var string
73: */
74: protected $charset;
75:
76: /**
77: * The path for sqlite database
78: *
79: * @var string
80: */
81: protected $path;
82:
83: /**
84: * The dsn parameter for PDO constructor
85: *
86: * @var string
87: */
88: protected $dsn;
89:
90: /**
91: * A key=>value array of driver-specific connection attributes
92: *
93: * @var array
94: * @link http://www.php.net/manual/zh/pdo.setattribute.php
95: */
96: protected $attrs = array();
97:
98: /**
99: * The PDO object
100: *
101: * @var \PDO
102: */
103: protected $pdo;
104:
105: /**
106: * The callback triggers before connect to database
107: *
108: * @var callback
109: */
110: protected $beforeConnect;
111:
112: /**
113: * The callback triggers when fails to connect to database
114: *
115: * @var callback
116: */
117: protected $connectFails;
118:
119: /**
120: * The callback triggers after connect to database
121: *
122: * @var callback
123: */
124: protected $afterConnect;
125:
126: /**
127: * The callback triggers before execute query
128: *
129: * @var callback
130: */
131: protected $beforeQuery;
132:
133: /**
134: * The callback triggers after execute query
135: *
136: * @var callback
137: */
138: protected $afterQuery;
139:
140: /**
141: * Whether connected to the database server
142: *
143: * @var bool
144: */
145: protected $isConnected = false;
146:
147: /**
148: * The database table definitions
149: *
150: * @var array
151: */
152: protected $tables = array();
153:
154: /**
155: * The base record class when instance a new record object
156: *
157: * @var string
158: */
159: protected $recordClass = 'Wei\Record';
160:
161: /**
162: * An associative array that the key is table name and the value is class name
163: *
164: * @var array
165: */
166: protected $recordClasses = array();
167:
168: /**
169: * The record namespace without ending "\"
170: *
171: * @var string
172: */
173: protected $recordNamespace;
174:
175: /**
176: * The prefix of table name
177: *
178: * @var string
179: */
180: protected $tablePrefix;
181:
182: /**
183: * Whether use the options from the default db service
184: *
185: * @var bool
186: */
187: protected $global = false;
188:
189: /**
190: * All executed SQL queries
191: *
192: * @var array
193: */
194: protected $queries = array();
195:
196: /**
197: * The field names of table
198: *
199: * @var array
200: */
201: protected $tableFields = array();
202:
203: /**
204: * The salve db configuration name
205: *
206: * @var string
207: */
208: protected $slaveDb;
209:
210: /**
211: * Constructor
212: *
213: * @param array $options
214: */
215: public function __construct(array $options = array())
216: {
217: if (isset($options['global']) && true == $options['global']) {
218: $options += (array)$options['wei']->getConfig('db');
219: }
220: return parent::__construct($options);
221: }
222:
223: /**
224: * Create a new instance of a SQL query builder with specified table name
225: *
226: * @param string $table The name of database table
227: * @return Record
228: */
229: public function __invoke($table = null)
230: {
231: return $this->init($table);
232: }
233:
234: /**
235: * Connect to the database server
236: *
237: * @throws \PDOException When fails to connect to database server
238: * @return boolean
239: */
240: public function connect()
241: {
242: if ($this->isConnected) {
243: return false;
244: }
245:
246: if (!$this->pdo) {
247: $this->beforeConnect && call_user_func($this->beforeConnect, $this);
248:
249: $dsn = $this->getDsn();
250: $attrs = $this->attrs + array(
251: PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
252: PDO::ATTR_STRINGIFY_FETCHES => true,
253: PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
254: );
255:
256: try {
257: $this->pdo = new PDO($dsn, $this->user, $this->password, $attrs);
258: } catch (\PDOException $e) {
259: $this->connectFails && call_user_func($this->connectFails, $this, $e);
260: throw $e;
261: }
262:
263: $this->afterConnect && call_user_func($this->afterConnect, $this, $this->pdo);
264: }
265:
266: $this->isConnected = true;
267:
268: return true;
269: }
270:
271: /**
272: * Check if has connected to the database server
273: *
274: * @return bool
275: */
276: public function isConnected()
277: {
278: return $this->isConnected;
279: }
280:
281: /**
282: * Close the database connection
283: */
284: public function close()
285: {
286: $this->pdo = null;
287: $this->isConnected = false;
288: }
289:
290: /**
291: * Close the current connection and create a new one
292: */
293: public function reconnect()
294: {
295: $this->close();
296: $this->connect();
297: }
298:
299: /**
300: * Executes an INSERT query to insert specified data into table
301: *
302: * @param string $table The name of table
303: * @param array $data An associative array containing column-value pairs
304: * @return int The number of affected rows
305: */
306: public function insert($table, array $data)
307: {
308: $table = $this->getTable($table);
309: $field = implode(', ', array_keys($data));
310:
311: $placeholder = array();
312: foreach ($data as $key => $value) {
313: if ($value instanceof \stdClass && isset($value->scalar)) {
314: $placeholder[] = $value->scalar;
315: unset($data[$key]);
316: } else {
317: $placeholder[] = '?';
318: }
319: }
320: $placeholder = implode(', ', $placeholder);
321:
322: $query = "INSERT INTO $table ($field) VALUES ($placeholder)";
323: return $this->executeUpdate($query, array_values($data));
324: }
325:
326: /**
327: * Insert batch data into table
328: *
329: * @param string $table The name of table
330: * @param array $data A two-dimensional array
331: * @return int The number of inserted rows
332: */
333: public function insertBatch($table, array $data)
334: {
335: $table = $this->getTable($table);
336: $field = implode(', ', array_keys($data[0]));
337: $placeholders = array();
338: $values = array();
339:
340: switch ($this->driver) {
341: default:
342: case 'mysql':
343: case 'pgsql':
344: foreach ($data as $row) {
345: $placeholders[] = '(' . implode(', ', array_pad(array(), count($row), '?')) . ')';
346: $values = array_merge($values, array_values($row));
347: }
348: $placeholder = 'VALUES ' . implode(', ', $placeholders);
349: break;
350:
351: // For SQLite before 3.7.11, http://www.sqlite.org/releaselog/3_7_11.html
352: case 'sqlite':
353: foreach ($data as $row) {
354: $placeholders[] = 'SELECT ' . implode(', ', array_pad(array(), count($row), '?'));
355: $values = array_merge($values, array_values($row));
356: }
357: $placeholder = implode(' UNION ', $placeholders);
358: break;
359: }
360: $query = "INSERT INTO $table ($field) $placeholder";
361: return $this->executeUpdate($query, $values);
362: }
363:
364: /**
365: * Executes a UPDATE query
366: *
367: * @param string $table The name of table
368: * @param array $data An associative array containing column-value pairs
369: * @param array $conditions The conditions to search records
370: * @return int The number of affected rows
371: */
372: public function update($table, array $data, array $conditions)
373: {
374: $table = $this->getTable($table);
375: $set = $this->buildSqlObject($data, ', ');
376: $where = $this->buildSqlObject($conditions, ' AND ');
377:
378: $query = "UPDATE $table SET $set WHERE $where";
379: $params = array_merge(array_values($data), array_values($conditions));
380: return $this->executeUpdate($query, $params);
381: }
382:
383: /**
384: * Executes a DELETE query
385: *
386: * @param string $table The name of table
387: * @param array $conditions The conditions to search records
388: * @return int The number of affected rows
389: */
390: public function delete($table, array $conditions)
391: {
392: $table = $this->getTable($table);
393: $where = $this->buildSqlObject($conditions, ' AND ');
394:
395: $query = "DELETE FROM $table WHERE " . $where;
396: return $this->executeUpdate($query, array_values($conditions));
397: }
398:
399: /**
400: * Executes a SELECT query and return the first result
401: *
402: * ```php
403: * // Find by the "id" key
404: * $db->select('user', 1);
405: *
406: * // Find by specified column
407: * $db->select('user', array('username' => 'twin'));
408: *
409: * // Find in list
410: * $db->select('user', array('id' => array(1, 2, 3)));
411: * ```
412: *
413: * @param string $table The name of table
414: * @param string|array $conditions The "id" key value or an associative array containing column-value pairs
415: * @param string $select The table columns to select
416: * @return array|false An associative array containing column-value pairs
417: */
418: public function select($table, $conditions, $select = '*')
419: {
420: $data = $this->selectAll($table, $conditions, $select, 1);
421: return $data ? $data[0] : false;
422: }
423:
424: /**
425: * Executes a SELECT query and return all results
426: *
427: * ```php
428: * // Find by the "id" key
429: * $db->selectAll('user', 1);
430: *
431: * // Find by specified column
432: * $db->selectAll('user', array('username' => 'twin'));
433: *
434: * // Find in list
435: * $db->selectAll('user', array('id' => array(1, 2, 3)));
436: * ```
437: *
438: * @param string $table The name of table
439: * @param bool $conditions The "id" key value or an associative array containing column-value pairs
440: * @param string $select The table columns to select
441: * @param int $limit The row number to retrieve
442: * @return array
443: */
444: public function selectAll($table, $conditions = false, $select = '*', $limit = null)
445: {
446: $params = array();
447: $query = "SELECT $select FROM " . $this->getTable($table) . ' ';
448:
449: if (is_array($conditions)) {
450: if (!empty($conditions)) {
451: $query .= "WHERE " . implode(' = ? AND ', array_keys($conditions)) . ' = ?';
452: $params = array_values($conditions);
453: }
454: } elseif ($conditions !== false) {
455: $query .= "WHERE id = :id";
456: $params = array('id' => $conditions);
457: }
458:
459: if ($limit) {
460: $query .= " LIMIT $limit";
461: }
462:
463: return $this->query($query, $params)->fetchAll();
464: }
465:
466: /**
467: * Executes a query and returns the first array result
468: *
469: * @param string $sql The SQL query
470: * @param mixed $params The query parameters
471: * @param array $types The parameter types to bind
472: * @return array|false Return an array or false when no result found
473: */
474: public function fetch($sql, $params = array(), $types = array())
475: {
476: return $this->query($sql, $params, $types)->fetch();
477: }
478:
479: /**
480: * Executes a query and returns all array results
481: *
482: * @param string $sql The SQL query
483: * @param mixed $params The query parameters
484: * @param array $types The parameter types to bind
485: * @return array|false Return an array or false when no result found
486: */
487: public function fetchAll($sql, $params = array(), $types = array())
488: {
489: return $this->query($sql, $params, $types)->fetchAll();
490: }
491:
492: /**
493: * Executes a query and returns a column value of the first row
494: *
495: * @param string $sql The SQL query
496: * @param mixed $params The query parameters
497: * @param int $column The index of column
498: * @return string
499: */
500: public function fetchColumn($sql, $params = array(), $column = 0)
501: {
502: return $this->query($sql, $params)->fetchColumn($column);
503: }
504:
505: /**
506: * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
507: * and returns the number of affected rows
508: *
509: * @param string $sql The SQL query
510: * @param array $params The query parameters
511: * @param array $types The parameter types to bind
512: * @return int The number of affected rows
513: */
514: public function executeUpdate($sql, $params = array(), $types = array())
515: {
516: return $this->query($sql, $params, $types, true);
517: }
518:
519: /**
520: * Executes an SQL statement, returning a PDOStatement object or the number of affected rows
521: *
522: * @param string $sql The SQL query
523: * @param array $params The SQL parameters
524: * @param array $types The parameter types to bind
525: * @param bool $returnRows Whether returns a PDOStatement object or the number of affected rows
526: * @throws \PDOException
527: * @return \PDOStatement|int
528: */
529: public function query($sql, $params = array(), $types = array(), $returnRows = false)
530: {
531: // A select query, using slave db if configured
532: if (!$returnRows && $this->slaveDb) {
533: /** @var $slaveDb \Wei\Db */
534: $slaveDb = $this->wei->get($this->slaveDb);
535: return $slaveDb->query($sql, $params, $types, $returnRows);
536: }
537:
538: $this->connect();
539: $this->queries[] = $sql;
540: if ($this->beforeQuery) {
541: call_user_func_array($this->beforeQuery, array($sql, $params, $types, $this));
542: }
543:
544: try {
545: if (!$returnRows) {
546: if ($params) {
547: $stmt = $this->pdo->prepare($sql);
548: if ($types) {
549: $this->bindParameter($stmt, $params, $types);
550: $stmt->execute();
551: } else {
552: $stmt->execute((array)$params);
553: }
554: } else {
555: $stmt = $this->pdo->query($sql);
556: }
557: $result = $stmt;
558: } else {
559: if ($params) {
560: $stmt = $this->pdo->prepare($sql);
561: $stmt->execute($params);
562: $result = $stmt->rowCount();
563: } else {
564: $result = $this->pdo->exec($sql);
565: }
566: }
567: } catch (\PDOException $e) {
568: // Builder exception message
569: $msg = sprintf("An exception occurred while executing \"%s\" : \n\n %s", $sql, $e->getMessage());
570: $params && $msg .= ', with parameters ' . json_encode($params);
571:
572: // Reset exception message
573: $message = new \ReflectionProperty($e, 'message');
574: $message->setAccessible(true);
575: $message->setValue($e, $msg);
576: throw $e;
577: }
578:
579: if ($this->afterQuery) {
580: call_user_func_array($this->afterQuery, array($sql, $params, $types, $this));
581: }
582:
583: return $result;
584: }
585:
586: /**
587: * Returns the rows number of table search by specified parameters
588: *
589: * @param string $table
590: * @param mixed $conditions
591: * @return int
592: */
593: public function count($table, $conditions = false)
594: {
595: return (int)$this->executeAggregate('COUNT', $table, '1', $conditions);
596: }
597:
598: /**
599: * Returns the sum of specified table field and conditions
600: *
601: * @param string $table
602: * @param string $field
603: * @param mixed $conditions
604: * @return float
605: */
606: public function sum($table, $field, $conditions = false)
607: {
608: return $this->executeAggregate('SUM', $table, $field, $conditions);
609: }
610:
611: /**
612: * Returns the max value of specified table field and conditions
613: *
614: * @param string $table
615: * @param string $field
616: * @param mixed $conditions
617: * @return float
618: */
619: public function max($table, $field, $conditions = false)
620: {
621: return $this->executeAggregate('MAX', $table, $field, $conditions);
622: }
623:
624: /**
625: * Returns the min value of specified table field and conditions
626: *
627: * @param string $table
628: * @param string $field
629: * @param mixed $conditions
630: * @return float
631: */
632: public function min($table, $field, $conditions = false)
633: {
634: return $this->executeAggregate('MIN', $table, $field, $conditions);
635: }
636:
637: /**
638: * Returns the avg value of specified table field and conditions
639: *
640: * @param string $table
641: * @param string $field
642: * @param mixed $conditions
643: * @return float
644: */
645: public function avg($table, $field, $conditions = false)
646: {
647: return $this->executeAggregate('AVG', $table, $field, $conditions);
648: }
649:
650: /**
651: * Returns the ID of the last inserted row or sequence value
652: *
653: * @param string $sequence The name of PostgreSQL sequence
654: * @return string
655: */
656: public function lastInsertId($sequence = null)
657: {
658: return $this->pdo->lastInsertId($sequence);
659: }
660:
661: /**
662: * Fetch the SQLSTATE associated with the last operation on the database handle
663: *
664: * @return mixed
665: */
666: public function errorCode()
667: {
668: $this->connect();
669: return $this->pdo->errorCode();
670: }
671:
672: /**
673: * Fetch extended error information associated with the last operation on the database handle
674: *
675: * @return array
676: */
677: public function errorInfo()
678: {
679: $this->connect();
680: return $this->pdo->errorInfo();
681: }
682:
683: /**
684: * Init a record instance
685: *
686: * @param string $table The name of database table
687: * @param array $data The data for table record
688: * @param bool $isNew Whether it's a new record and have not save to database
689: * @return Record
690: */
691: public function init($table, $data = array(), $isNew = true)
692: {
693: $class = $this->getRecordClass($table);
694: return new $class(array(
695: 'wei' => $this->wei,
696: 'db' => $this,
697: 'table' => $table,
698: 'isNew' => $isNew,
699: 'data' => $data,
700: ));
701: }
702:
703: /**
704: * Find a record from specified table and conditions
705: *
706: * @param string $table The name of table
707: * @param string|array $id The primary key value or an associative array containing column-value pairs
708: * @return Record|false
709: */
710: public function find($table, $id)
711: {
712: $data = $this->select($table, $id);
713: return $data ? $this->init($table, $data, false) : false;
714: }
715:
716: /**
717: * Find a record from specified table and conditions
718: * and throws 404 exception when record not found
719: *
720: * @param string $table
721: * @param string|array $id The primary key value or an associative array containing column-value pairs
722: * @return Record
723: * @throws \Exception
724: */
725: public function findOne($table, $id)
726: {
727: return $this->init($table)->findOne($id);
728: }
729:
730: /**
731: * Find a record, if not found, create a new one from specified data
732: *
733: * @param string $table The name of table
734: * @param string $id The primary key value or an associative array containing column-value pairs
735: * @param array $data The data to create a new record when record not found
736: * @return Record
737: */
738: public function findOrInit($table, $id, $data = array())
739: {
740: return $this->init($table)->findOrInit($id, $data);
741: }
742:
743: /**
744: * Find records from specified table and conditions
745: *
746: * @param string $table The name of table
747: * @param mixed $where The primary key value or an associative array containing column-value pairs
748: * @return Record
749: */
750: public function findAll($table, $where = false)
751: {
752: return $this->init($table)->findAll($where);
753: }
754:
755: /**
756: * Set the record class for specified table
757: *
758: * @param string $table
759: * @param string $class
760: * @return $this
761: */
762: public function addRecordClass($table, $class)
763: {
764: $this->recordClasses[$table] = $class;
765: return $this;
766: }
767:
768: /**
769: * Returns the record class name of table
770: *
771: * @param string $table The name of table
772: * @return string The record class name for table
773: */
774: public function getRecordClass($table)
775: {
776: if (isset($this->recordClasses[$table])) {
777: return $this->recordClasses[$table];
778: }
779:
780: if ($this->recordNamespace) {
781: $class = $this->recordNamespace . '\\' . implode('', array_map('ucfirst', explode('_', $table)));
782: if (class_exists($class)) {
783: return $class;
784: }
785: }
786:
787: return $this->recordClass;
788: }
789:
790: /**
791: * Returns the full table name with prefix
792: *
793: * @param string $table
794: * @return string
795: */
796: public function getTable($table)
797: {
798: return $this->tablePrefix . $table;
799: }
800:
801: /**
802: * Returns the database username
803: *
804: * @return string
805: */
806: public function getUser()
807: {
808: return $this->user;
809: }
810:
811: /**
812: * Returns the database password
813: *
814: * @return string
815: */
816: public function getPassword()
817: {
818: return $this->password;
819: }
820:
821: /**
822: * Returns the hostname on which the database server resides
823: *
824: * @return string
825: */
826: public function getHost()
827: {
828: return $this->host;
829: }
830:
831: /**
832: * Returns the port on which the database server is running
833: *
834: * @return string
835: */
836: public function getPort()
837: {
838: return $this->port;
839: }
840:
841: /**
842: * Returns the name of the database
843: *
844: * @return string
845: */
846: public function getDbname()
847: {
848: return $this->dbname;
849: }
850:
851: /**
852: * Returns the name of PDO driver
853: *
854: * @return string
855: */
856: public function getDriver()
857: {
858: return $this->driver;
859: }
860:
861: /**
862: * Returns the PDO DSN
863: *
864: * @return string
865: * @throws \RuntimeException When database driver is unsupported
866: */
867: public function getDsn()
868: {
869: if ($this->dsn) {
870: return $this->dsn;
871: }
872:
873: $dsn = $this->driver . ':';
874: switch ($this->driver) {
875: case 'mysql':
876: $this->host && $dsn .= 'host=' . $this->host . ';';
877: $this->port && $dsn .= 'port=' . $this->port . ';';
878: $this->dbname && $dsn .= 'dbname=' . $this->dbname . ';';
879: $this->unixSocket && $dsn .= 'unix_socket=' . $this->unixSocket . ';';
880: $this->charset && $dsn .= 'charset=' . $this->charset;
881: break;
882:
883: case 'sqlite':
884: $dsn .= $this->path;
885: break;
886:
887: case 'pgsql':
888: $this->host && $dsn .= 'host=' . $this->host . ';';
889: $this->port && $dsn .= 'port=' . $this->port . ';';
890: $this->dbname && $dsn .= 'dbname=' . $this->dbname;
891: break;
892:
893: default:
894: throw new \RuntimeException(sprintf('Unsupported database driver: %s', $this->driver));
895: }
896: return $this->dsn = $dsn;
897: }
898:
899: /**
900: * Returns the original PDO object
901: *
902: * @return PDO
903: */
904: public function getPdo()
905: {
906: $this->connect();
907: return $this->pdo;
908: }
909:
910: /**
911: * Returns the prefix string of table name
912: *
913: * @return string
914: */
915: public function getTablePrefix()
916: {
917: return $this->tablePrefix;
918: }
919:
920: /**
921: * Returns the last executed SQL query
922: *
923: * @return string
924: */
925: public function getLastQuery()
926: {
927: return end($this->queries);
928: }
929:
930: /**
931: * Returns all executed SQL queries
932: *
933: * @return array
934: */
935: public function getQueries()
936: {
937: return $this->queries;
938: }
939:
940: /**
941: * Returns the name of fields of specified table
942: *
943: * @param string $table
944: * @param bool $withPrefix
945: * @throws \PDOException
946: * @return array
947: */
948: public function getTableFields($table, $withPrefix = false)
949: {
950: $fullTable = $withPrefix ? $table : $this->getTable($table);
951: if (isset($this->tableFields[$fullTable])) {
952: return $this->tableFields[$fullTable];
953: } else {
954: $fields = array();
955: switch ($this->driver) {
956: case 'mysql':
957: $tableInfo = $this->fetchAll("SHOW COLUMNS FROM $fullTable");
958: $fields = $this->filter($tableInfo, 'Field');
959: break;
960:
961: case 'sqlite':
962: $tableInfo = $this->fetchAll("PRAGMA table_info($fullTable)");
963: $fields = $this->filter($tableInfo, 'name');
964: break;
965:
966: case 'pgsql':
967: $tableInfo = $this->fetchAll(
968: "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS
969: WHERE table_catalog = ? AND table_name = ?
970: ORDER BY dtd_identifier ASC",
971: array($this->dbname, $fullTable)
972: );
973: $fields = $this->filter($tableInfo, 'column_name');
974: }
975: if (empty($fields)) {
976: // For SQLite and PostgreSQL
977: throw new \PDOException(sprintf('Table or view "%s" not found', $fullTable));
978: }
979: return $this->tableFields[$table] = $fields;
980: }
981: }
982:
983: protected function filter($data, $name)
984: {
985: $fields = array();
986: foreach ($data as $row) {
987: $fields[] = $row[$name];
988: }
989: return $fields;
990: }
991:
992: protected function buildSqlObject(array &$data, $glue)
993: {
994: $query = array();
995: foreach ($data as $field => $value) {
996: if ($value instanceof \stdClass && isset($value->scalar)) {
997: $query[] = $field . ' = ' . $value->scalar;
998: unset($data[$field]);
999: } else {
1000: $query[] = $field . ' = ?';
1001: }
1002: }
1003: return implode($glue, $query);
1004: }
1005:
1006: /**
1007: * Bind parameters to statement object
1008: *
1009: * @param \PDOStatement $stmt
1010: * @param array $params
1011: * @param array $types
1012: */
1013: protected function bindParameter(\PDOStatement $stmt, $params, $types)
1014: {
1015: !is_array($params) && $params = array($params);
1016: !is_array($types) && $types = array($types);
1017:
1018: $isIndex = is_int(key($params));
1019: $index = 1;
1020:
1021: foreach ($params as $name => $param) {
1022: // Number index parameters
1023: if ($isIndex) {
1024: if (isset($types[$index - 1])) {
1025: $stmt->bindValue($index, $param, $types[$index - 1]);
1026: } else {
1027: $stmt->bindValue($index, $param);
1028: }
1029: $index++;
1030: // Named parameters
1031: } else {
1032: if (isset($types[$name])) {
1033: $stmt->bindValue($name, $param, $types[$name]);
1034: } else {
1035: $stmt->bindValue($name, $param);
1036: }
1037: }
1038: }
1039: }
1040:
1041: /**
1042: * Execute a query with aggregate function
1043: *
1044: * @param string $fn
1045: * @param string $table
1046: * @param string $field
1047: * @param mixed $conditions
1048: * @return float
1049: */
1050: protected function executeAggregate($fn, $table, $field, $conditions)
1051: {
1052: $data = $this->selectAll($table, $conditions, $fn . '(' . $field . ')');
1053: return $data ? (float)current($data[0]) : 0.0;
1054: }
1055: }
1056: