1: <?php
2: /**
3: * Zend Framework
4: *
5: * LICENSE
6: *
7: * This source file is subject to the new BSD license that is bundled
8: * with this package in the file LICENSE.txt.
9: * It is also available through the world-wide-web at this URL:
10: * http://framework.zend.com/license/new-bsd
11: * If you did not receive a copy of the license and are unable to
12: * obtain it through the world-wide-web, please send an email
13: * to license@zend.com so we can send you a copy immediately.
14: *
15: * @category Zend
16: * @package Zend_Db
17: * @subpackage Adapter
18: * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
19: * @license http://framework.zend.com/license/new-bsd New BSD License
20: * @version $Id: Abstract.php 20096 2010-01-06 02:05:09Z bkarwin $
21: */
22:
23:
24: /**
25: * @see Zend_Db_Adapter_Abstract
26: */
27: require_once 'Zend/Db/Adapter/Abstract.php';
28:
29:
30: /**
31: * @see Zend_Db_Statement_Pdo
32: */
33: require_once 'Zend/Db/Statement/Pdo.php';
34:
35:
36: /**
37: * Class for connecting to SQL databases and performing common operations using PDO.
38: *
39: * @category Zend
40: * @package Zend_Db
41: * @subpackage Adapter
42: * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
43: * @license http://framework.zend.com/license/new-bsd New BSD License
44: */
45: abstract class Zend_Db_Adapter_Pdo_Abstract extends Zend_Db_Adapter_Abstract
46: {
47:
48: /**
49: * Default class name for a DB statement.
50: *
51: * @var string
52: */
53: protected $_defaultStmtClass = 'Zend_Db_Statement_Pdo';
54:
55: /**
56: * Creates a PDO DSN for the adapter from $this->_config settings.
57: *
58: * @return string
59: */
60: protected function _dsn()
61: {
62: // baseline of DSN parts
63: $dsn = $this->_config;
64:
65: // don't pass the username, password, charset, persistent and driver_options in the DSN
66: unset($dsn['username']);
67: unset($dsn['password']);
68: unset($dsn['options']);
69: unset($dsn['charset']);
70: unset($dsn['persistent']);
71: unset($dsn['driver_options']);
72:
73: // use all remaining parts in the DSN
74: foreach ($dsn as $key => $val) {
75: $dsn[$key] = "$key=$val";
76: }
77:
78: return $this->_pdoType . ':' . implode(';', $dsn);
79: }
80:
81: /**
82: * Creates a PDO object and connects to the database.
83: *
84: * @return void
85: * @throws Zend_Db_Adapter_Exception
86: */
87: protected function _connect()
88: {
89: // if we already have a PDO object, no need to re-connect.
90: if ($this->_connection) {
91: return;
92: }
93:
94: // get the dsn first, because some adapters alter the $_pdoType
95: $dsn = $this->_dsn();
96:
97: // check for PDO extension
98: if (!extension_loaded('pdo')) {
99: /**
100: * @see Zend_Db_Adapter_Exception
101: */
102: require_once 'Zend/Db/Adapter/Exception.php';
103: throw new Zend_Db_Adapter_Exception('The PDO extension is required for this adapter but the extension is not loaded');
104: }
105:
106: // check the PDO driver is available
107: if (!in_array($this->_pdoType, PDO::getAvailableDrivers())) {
108: /**
109: * @see Zend_Db_Adapter_Exception
110: */
111: require_once 'Zend/Db/Adapter/Exception.php';
112: throw new Zend_Db_Adapter_Exception('The ' . $this->_pdoType . ' driver is not currently installed');
113: }
114:
115: // create PDO connection
116: $q = $this->_profiler->queryStart('connect', Zend_Db_Profiler::CONNECT);
117:
118: // add the persistence flag if we find it in our config array
119: if (isset($this->_config['persistent']) && ($this->_config['persistent'] == true)) {
120: $this->_config['driver_options'][PDO::ATTR_PERSISTENT] = true;
121: }
122:
123: try {
124: $this->_connection = new PDO(
125: $dsn,
126: $this->_config['username'],
127: $this->_config['password'],
128: $this->_config['driver_options']
129: );
130:
131: $this->_profiler->queryEnd($q);
132:
133: // set the PDO connection to perform case-folding on array keys, or not
134: $this->_connection->setAttribute(PDO::ATTR_CASE, $this->_caseFolding);
135:
136: // always use exceptions.
137: $this->_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
138:
139: } catch (PDOException $e) {
140: /**
141: * @see Zend_Db_Adapter_Exception
142: */
143: require_once 'Zend/Db/Adapter/Exception.php';
144: throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e);
145: }
146:
147: }
148:
149: /**
150: * Test if a connection is active
151: *
152: * @return boolean
153: */
154: public function isConnected()
155: {
156: return ((bool) ($this->_connection instanceof PDO));
157: }
158:
159: /**
160: * Force the connection to close.
161: *
162: * @return void
163: */
164: public function closeConnection()
165: {
166: $this->_connection = null;
167: }
168:
169: /**
170: * Prepares an SQL statement.
171: *
172: * @param string $sql The SQL statement with placeholders.
173: * @param array $bind An array of data to bind to the placeholders.
174: * @return PDOStatement
175: */
176: public function prepare($sql)
177: {
178: $this->_connect();
179: $stmtClass = $this->_defaultStmtClass;
180: if (!class_exists($stmtClass)) {
181: require_once 'Zend/Loader.php';
182: Zend_Loader::loadClass($stmtClass);
183: }
184: $stmt = new $stmtClass($this, $sql);
185: $stmt->setFetchMode($this->_fetchMode);
186: return $stmt;
187: }
188:
189: /**
190: * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
191: *
192: * As a convention, on RDBMS brands that support sequences
193: * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
194: * from the arguments and returns the last id generated by that sequence.
195: * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
196: * returns the last value generated for such a column, and the table name
197: * argument is disregarded.
198: *
199: * On RDBMS brands that don't support sequences, $tableName and $primaryKey
200: * are ignored.
201: *
202: * @param string $tableName OPTIONAL Name of table.
203: * @param string $primaryKey OPTIONAL Name of primary key column.
204: * @return string
205: */
206: public function lastInsertId($tableName = null, $primaryKey = null)
207: {
208: $this->_connect();
209: return $this->_connection->lastInsertId();
210: }
211:
212: /**
213: * Special handling for PDO query().
214: * All bind parameter names must begin with ':'
215: *
216: * @param string|Zend_Db_Select $sql The SQL statement with placeholders.
217: * @param array $bind An array of data to bind to the placeholders.
218: * @return Zend_Db_Statement_Pdo
219: * @throws Zend_Db_Adapter_Exception To re-throw PDOException.
220: */
221: public function query($sql, $bind = array())
222: {
223: if (empty($bind) && $sql instanceof Zend_Db_Select) {
224: $bind = $sql->getBind();
225: }
226:
227: if (is_array($bind)) {
228: foreach ($bind as $name => $value) {
229: if (!is_int($name) && !preg_match('/^:/', $name)) {
230: $newName = ":$name";
231: unset($bind[$name]);
232: $bind[$newName] = $value;
233: }
234: }
235: }
236:
237: try {
238: return parent::query($sql, $bind);
239: } catch (PDOException $e) {
240: /**
241: * @see Zend_Db_Statement_Exception
242: */
243: require_once 'Zend/Db/Statement/Exception.php';
244: throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
245: }
246: }
247:
248: /**
249: * Executes an SQL statement and return the number of affected rows
250: *
251: * @param mixed $sql The SQL statement with placeholders.
252: * May be a string or Zend_Db_Select.
253: * @return integer Number of rows that were modified
254: * or deleted by the SQL statement
255: */
256: public function exec($sql)
257: {
258: if ($sql instanceof Zend_Db_Select) {
259: $sql = $sql->assemble();
260: }
261:
262: try {
263: $affected = $this->getConnection()->exec($sql);
264:
265: if ($affected === false) {
266: $errorInfo = $this->getConnection()->errorInfo();
267: /**
268: * @see Zend_Db_Adapter_Exception
269: */
270: require_once 'Zend/Db/Adapter/Exception.php';
271: throw new Zend_Db_Adapter_Exception($errorInfo[2]);
272: }
273:
274: return $affected;
275: } catch (PDOException $e) {
276: /**
277: * @see Zend_Db_Adapter_Exception
278: */
279: require_once 'Zend/Db/Adapter/Exception.php';
280: throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e);
281: }
282: }
283:
284: /**
285: * Quote a raw string.
286: *
287: * @param string $value Raw string
288: * @return string Quoted string
289: */
290: protected function _quote($value)
291: {
292: if (is_int($value) || is_float($value)) {
293: return $value;
294: }
295: $this->_connect();
296: return $this->_connection->quote($value);
297: }
298:
299: /**
300: * Begin a transaction.
301: */
302: protected function _beginTransaction()
303: {
304: $this->_connect();
305: $this->_connection->beginTransaction();
306: }
307:
308: /**
309: * Commit a transaction.
310: */
311: protected function _commit()
312: {
313: $this->_connect();
314: $this->_connection->commit();
315: }
316:
317: /**
318: * Roll-back a transaction.
319: */
320: protected function _rollBack() {
321: $this->_connect();
322: $this->_connection->rollBack();
323: }
324:
325: /**
326: * Set the PDO fetch mode.
327: *
328: * @todo Support FETCH_CLASS and FETCH_INTO.
329: *
330: * @param int $mode A PDO fetch mode.
331: * @return void
332: * @throws Zend_Db_Adapter_Exception
333: */
334: public function setFetchMode($mode)
335: {
336: //check for PDO extension
337: if (!extension_loaded('pdo')) {
338: /**
339: * @see Zend_Db_Adapter_Exception
340: */
341: require_once 'Zend/Db/Adapter/Exception.php';
342: throw new Zend_Db_Adapter_Exception('The PDO extension is required for this adapter but the extension is not loaded');
343: }
344: switch ($mode) {
345: case PDO::FETCH_LAZY:
346: case PDO::FETCH_ASSOC:
347: case PDO::FETCH_NUM:
348: case PDO::FETCH_BOTH:
349: case PDO::FETCH_NAMED:
350: case PDO::FETCH_OBJ:
351: $this->_fetchMode = $mode;
352: break;
353: default:
354: /**
355: * @see Zend_Db_Adapter_Exception
356: */
357: require_once 'Zend/Db/Adapter/Exception.php';
358: throw new Zend_Db_Adapter_Exception("Invalid fetch mode '$mode' specified");
359: break;
360: }
361: }
362:
363: /**
364: * Check if the adapter supports real SQL parameters.
365: *
366: * @param string $type 'positional' or 'named'
367: * @return bool
368: */
369: public function supportsParameters($type)
370: {
371: switch ($type) {
372: case 'positional':
373: case 'named':
374: default:
375: return true;
376: }
377: }
378:
379: /**
380: * Retrieve server version in PHP style
381: *
382: * @return string
383: */
384: public function getServerVersion()
385: {
386: $this->_connect();
387: try {
388: $version = $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
389: } catch (PDOException $e) {
390: // In case of the driver doesn't support getting attributes
391: return null;
392: }
393: $matches = null;
394: if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) {
395: return $matches[1];
396: } else {
397: return null;
398: }
399: }
400: }
401:
402: