PageRenderTime 12ms CodeModel.GetById 2ms app.highlight 6ms RepoModel.GetById 1ms app.codeStats 0ms

/pear/PDB/tags/0.0.4/PDB/Common.php

http://digg.googlecode.com/
PHP | 533 lines | 149 code | 36 blank | 348 comment | 11 complexity | 2f251241f3abb19101988739f1ef73e1 MD5 | raw file
  1<?php
  2
  3/**
  4 * Base PDB class
  5 *
  6 * PHP version 5.2+
  7 *
  8 * Copyright (c) 2007, 2008, Digg, Inc.
  9 * 
 10 * All rights reserved.
 11 * 
 12 * Redistribution and use in source and binary forms, with or without 
 13 * modification, are permitted provided that the following conditions are met:
 14 *
 15 *  - Redistributions of source code must retain the above copyright notice,
 16 *    this list of conditions and the following disclaimer.
 17 *  - Redistributions in binary form must reproduce the above copyright notice,
 18 *    this list of conditions and the following disclaimer in the documentation
 19 *    and/or other materials provided with the distribution.
 20 *  - Neither the name of the Digg, INc. nor the names of its contributors 
 21 *    may be used to endorse or promote products derived from this software 
 22 *    without specific prior written permission.
 23 *
 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 34 * POSSIBILITY OF SUCH DAMAGE.
 35 *
 36 * @category   DB
 37 * @package    PDB
 38 * @author     Joe Stump <joe@joestump.net> 
 39 * @copyright  2007-2008 (c) Digg.com 
 40 * @license    http://tinyurl.com/42zef New BSD License
 41 * @version    CVS: $Id:$
 42 * @link       http://www.php.net/pdo
 43 * @link       http://pear.php.net/package/PDB
 44 * @filesource
 45 */
 46
 47require_once 'PDB/Exception.php';
 48require_once 'PDB/Result.php';
 49
 50/**
 51 * Base PDB class
 52 *
 53 * @category   DB
 54 * @package    PDB
 55 * @author     Joe Stump <joe@joestump.net> 
 56 * @copyright  2007-2008 (c) Digg.com 
 57 * @license    http://tinyurl.com/42zef New BSD License
 58 * @version    Release: @package_version@
 59 * @link       http://pear.php.net/package/PDB
 60 */
 61abstract class PDB_Common 
 62{
 63    /**
 64     * The PDO connection
 65     * 
 66     * Due to various issues with PDO (e.g. the inability to disconnect)
 67     * we use the decorator pattern to envelope PDO with extra
 68     * functionality. 
 69     * 
 70     * @var object $pdo Instance of PDO
 71     * @link http://us.php.net/pdo
 72     * @see PDB_Common::__call()
 73     */
 74    protected $pdo = null;
 75
 76    /**
 77     * PDO DSN
 78     *
 79     * @access protected
 80     * @var string $dsn PDO DSN (e.g. mysql:host=127.0.0.1;dbname=foo)
 81     */
 82    protected $dsn = '';
 83    
 84    /**
 85     * DNS info as an stdClass
 86     *
 87     * @var stdClass
 88     */
 89    protected $dsnObject = null;    
 90
 91    /**
 92     * Username for DB connection
 93     *
 94     * @access protected
 95     * @var string $username DB username
 96     */
 97    protected $user = ''; 
 98
 99    /**
100     * Password for DB connection
101     *
102     * @access protected
103     * @var string $password DB password
104     */
105    protected $pass = '';
106
107    /**
108     * PDO/Driver options
109     *
110     * @access protected
111     * @var array $options PDO/Driver options
112     * @link http://us.php.net/manual/en/pdo.constants.php
113     * @link http://us.php.net/manual/en/pdo.drivers.php
114     */
115    protected $options = array();
116
117    /**
118     * Default fetch mode
119     *
120     * @access      private
121     * @var         int         $fetchMode
122     */
123    public $fetchMode = PDO::FETCH_NUM;
124
125    /**
126     * Constructor
127     *
128     * @param string $dsn      The PDO DSN
129     * @param string $username The DB's username
130     * @param string $password The DB's password
131     * @param array  $options  PDO/driver options array
132     *
133     * @return void
134     * @see PDB_Common::connect(), PDB_Common::$dsn, PDB_Common::$username
135     * @see PDB_Common::$password, PDB_Common::$options
136     */
137    public function __construct($dsn, 
138                                $username = '', 
139                                $password = '', 
140                                $options = array())
141    {
142        $this->dsn      = $dsn;
143        $this->user     = $username;
144        $this->pass     = $password;
145        $this->options  = $options;
146        $this->connect();
147    }
148
149    /**
150     * Connect to the database
151     *
152     * @return void
153     * @see PDB_Common::$dsn, PDB_Common::$username
154     * @see PDB_Common::$password, PDB_Common::$options
155     * @see PDB_Common::setAttribute
156     */
157    public function connect()
158    {
159        $this->pdo = new PDO($this->dsn, 
160                             $this->user, 
161                             $this->pass, 
162                             $this->options);
163
164        $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
165    }
166
167    /**
168     * Reconnect to the database
169     *
170     * This reconnects to the database with the given parameters from
171     * before we either disconnected or lost the connection. This is useful
172     * for when MySQL (and others probably) servers "go away".
173     * 
174     * @see PDB_Common::disconnect(), PDB_Common::connect()
175     * @return void
176     */
177    public function reconnect()
178    {
179        $this->disconnect();
180        $this->connect();
181    }
182
183    /**
184     * Disconnect from the DB
185     *
186     * @return void
187     */
188    public function disconnect()
189    {
190        $this->pdo = null;
191    }
192    
193   /**
194     * Get DSN as an stdClass
195     *
196     * @return stdClass The DNS info in an stdClass
197     */
198    public function getDSN() {
199        if ($this->dsnObject == null) {
200            list($type, $parseMe) = explode(':', $this->dsn);
201            $parseMe              = str_replace(';', '&', $parseMe);
202            
203            $dsnParts = array();
204            parse_str($parseMe, $dsnParts);
205
206            $dsnParts['name'] = $dsnParts['dbname'];
207            $dsnParts['user'] = $this->user;
208            $dsnParts['pass'] = $this->pass;
209            $dsnParts['type'] = $type;
210            
211            unset($dsnParts['dbname']);
212            $this->dsnObject = (object) $dsnParts;
213        }
214        
215        return $this->dsnObject;
216    }       
217
218    /**
219     * Implement decorator pattern
220     *
221     * Originally {@link PDB} was extended from PDO, but this kept us from
222     * implementing valid {@link PDB_Common::disconnect()} and 
223     * {@link PDB_Common::reconnect()} methods, which were needed for other
224     * nice functionality.
225     *
226     * As a result we use {@link PDB_Common::__call()} to implement the basic
227     * decorator pattern. Everything listed below should work without issues.
228     *
229     * @param string $function Name of function to run
230     * @param array  $args     Function's arguments
231     *
232     * @method bool beginTransaction()
233     * @method bool commit()
234     * @method string errorCode()
235     * @method array errorInfo()
236     * @method int exec(string $statement)
237     * @method mixed getAttribute(int $attribute)
238     * @method string lastInsertId([string $name])
239     * @method PDOStatement prepare(string $statement [, array $driver_options])
240     * @method string quote(string $string [, int $parameter_type])
241     * @method bool rollBack()
242     * @return mixed
243     */
244    public function __call($function, array $args = array()) 
245    {
246        if (is_null($this->pdo)) {
247            throw new PDB_Exception('Not connected to DB');
248        }
249
250        return call_user_func_array(array($this->pdo, $function), $args);
251    }
252
253    /**
254     * Query the database
255     *
256     * <code>
257     * <?php
258     * 
259     * require_once 'PDB.php';
260     *
261     * $db = PDB::connect('mysql:host=127.0.0.1;dbname=foo', 'user', 'pass'); 
262     * $db->setFetchMode(PDO::FETCH_OBJECT);
263     *
264     * $sql = 'SELECT * 
265     *         FROM items
266     *         WHERE promoted = ? AND
267     *               userid = ?';
268     * 
269     * $result = $db->query($sql, array(1, (int)$_GET['userid']));
270     *
271     * // Notice that {@link PDB_Result} supports object iteration just like
272     * // PDOStatement does since it extends from it.
273     * foreach ($result as $row) {
274     *     echo '<a href="' . $row->url . '">' . $row->title . '</a>' . "\n";
275     * }
276     *
277     * ?>
278     * </code> 
279     *
280     * @param string $sql  The query
281     * @param array  $args The query arguments
282     *
283     * @return object Instance of {@link PDB_Result}
284     * @throws {@link PDB_Exception} on failure
285     * @link http://us3.php.net/manual/en/class.pdostatement.php
286     * @link http://us3.php.net/manual/en/pdostatement.bindparam.php
287     */
288    public function query($sql, array $args = array())
289    {
290        try {
291            $stmt = $this->prepare($sql, array(
292                PDO::ATTR_STATEMENT_CLASS => array(
293                    'PDB_Result', array($this->pdo, $this->fetchMode)
294                )
295            ));
296
297            if (is_array($args)) {
298                $cnt = count($args);
299                if ($cnt > 0) {
300                    foreach ($args as $key => $value) {
301                        $param  = (is_int($key) ? ($key + 1) : $key);
302                        $result = $stmt->bindParam($param, $args[$key]);
303                    }
304                }
305            }
306
307            $stmt->execute();
308            return $stmt;
309        } catch (PDOException $error) {
310            throw new PDB_Exception($error->getMessage(), (int) $error->getCode());
311        }
312    }
313
314    /**
315     * Fetch a single row
316     *
317     * <code>
318     * <?php
319     * 
320     * require_once 'PDB.php';
321     *
322     * $db = PDB::connect('mysql:host=127.0.0.1;dbname=foo', 'user', 'pass'); 
323     * $db->setFetchMode(PDO::FETCH_OBJECT);
324     *
325     * $sql = 'SELECT * 
326     *         FROM users
327     *         WHERE userid = ?';
328     *
329     * $user = $db->getRow($sql, array((int)$_GET['userid']));
330     * echo 'Welcome back, ' . $user->username . '!';
331     *
332     * ?>
333     * </code>
334     * 
335     * @param string  $sql       The query to run
336     * @param array   $params    The query parameter values
337     * @param integer $fetchMode The fetch mode for query
338     *
339     * @see PDB_Common::query(), PDB_Result
340     * @return array
341     */
342    public function getRow($sql, 
343                           array $params = array(), 
344                           $fetchMode = null)
345    {
346        if (is_null($fetchMode)) {
347            $fetchMode = $this->fetchMode;
348        }
349
350        $result = $this->query($sql, $params);
351        return $result->fetchRow($fetchMode);
352    }
353
354    /**
355     * Fetch a single column
356     *
357     * <code>
358     * <?php
359     * 
360     * require_once 'PDB.php';
361     *
362     * $db  = PDB::connect('mysql:host=127.0.0.1;dbname=foo', 'user', 'pass'); 
363     * $sql = 'SELECT friendid 
364     *         FROM friends
365     *         WHERE userid = ?';
366     *
367     * $friends = $db->getCol($sql, 0, array((int)$_GET['userid']));
368     * if (in_array($_SESSION['userid'], $friends)) {
369     *    echo 'You are friends with this user!';
370     * }
371     *
372     * ?>
373     * </code>
374     * 
375     * @param string  $sql    The query to run
376     * @param integer $col    The column number to fetch (zero-based)
377     * @param array   $params The query parameter values
378     *
379     * @see PDB_Common::query(), PDB_Result
380     * @return array
381     */
382    public function getCol($sql, $col = 0, array $params = array())
383    {
384        $result = $this->query($sql, $params);
385        $ret    = array();
386        while ($row = $result->fetchRow(PDO::FETCH_NUM)) {
387            $ret[] = $row[$col];
388        }
389
390        return $ret;
391    }
392
393    /**
394     * Fetch all records in query as array
395     *
396     * This method will fetch all records from a given query into a 
397     * numerically indexed array (e.g. $result[0] is the first record).
398     *
399     * <code>
400     * <?php
401     * 
402     * require_once 'PDB.php';
403     *
404     * $db = PDB::connect('mysql:host=127.0.0.1;dbname=foo', 'user', 'pass'); 
405     * $db->setFetchMode(PDO::FETCH_OBJECT);
406     * 
407     * $sql = 'SELECT * 
408     *         FROM users
409     *         WHERE type = ?';
410     *
411     * $students = $db->getAll($sql, array('student'));
412     * foreach ($students as $student) {
413     *     echo $student->firstname . "\n";
414     * }
415     *
416     * ?>
417     * </code>
418     *
419     * @param string  $sql       The query to run
420     * @param array   $params    The query parameter values
421     * @param integer $fetchMode The fetch mode for query
422     *
423     * @return array
424     * @see PDB_Result, PDB_Common::query()
425     */
426    public function getAll($sql, 
427                           array $params = array(), 
428                           $fetchMode = null) 
429    {
430        if (is_null($fetchMode)) {
431            $fetchMode = $this->fetchMode;
432        }
433
434        $result = $this->query($sql, $params);
435        $ret    = array();
436        while ($row = $result->fetchRow($fetchMode)) {
437            $ret[] = $row;
438        }
439
440        return $ret;
441    }
442
443    /**
444     * Get a single field
445     *
446     * This will fetch a single value from the first row's first
447     * column. 
448     *
449     * <code>
450     * <?php
451     * 
452     * require_once 'PDB.php';
453     *
454     * $db  = PDB::connect('mysql:host=127.0.0.1;dbname=foo', 'user', 'pass'); 
455     * $sql = 'SELECT COUNT(*) AS total
456     *         FROM users
457     *         WHERE type = ?';
458     *
459     * $total = $db->getOne($sql, array('student'));
460     * if (!$total) {
461     *     echo 'No students!';
462     * }
463     *
464     * ?>
465     * </code>
466     *
467     * @param string $sql    The query to run
468     * @param array  $params The query parameter values
469     *
470     * @see PDB_Common::query(), PDB_Result::fetchRow()
471     * @return mixed The value of the first row/column
472     */
473    public function getOne($sql, array $params = array()) 
474    {
475        $result = $this->query($sql, $params);
476        $row    = $result->fetchRow(PDO::FETCH_NUM);
477        return $row[0];
478    }
479
480    /**
481     * Set the fetch mode for all queries
482     *
483     * This should be set to one of PDO's fetch modes. Valid values include:
484     *  - PDO::FETCH_LAZY
485     *  - PDO::FETCH_ASSOC
486     *  - PDO::FETCH_NAMED
487     *  - PDO::FETCH_NUM
488     *  - PDO::FETCH_BOTH
489     *  - PDO::FETCH_OBJ
490     *
491     * @param integer $mode The DB fetch mode
492     *
493     * @throws UnexpectedArgumentException on invalid modes
494     * @access public
495     * @return void
496     */
497    public function setFetchMode($mode)
498    {
499        switch ($mode) {
500        case PDO::FETCH_LAZY:
501        case PDO::FETCH_ASSOC:
502        case PDO::FETCH_NAMED:
503        case PDO::FETCH_NUM:
504        case PDO::FETCH_BOTH:
505        case PDO::FETCH_OBJ:
506            $this->fetchMode = $mode;
507            break;
508        default:
509            throw UnexpectedArgumentException('Invalid mode');
510        }
511    }
512
513    /**
514     * Set an attribute
515     *
516     * @param integer $attribute The attribute to set
517     * @param mixed   $value     The attribute's value
518     *
519     * @link http://us.php.net/manual/en/pdo.setattribute.php
520     * @return true False if something failed to set
521     */
522    public function setAttribute($attribute, $value)
523    {
524        if ($this->pdo->setAttribute($attribute, $value)) {
525            $this->options[$attribute] = $value;
526            return true;
527        }
528
529        return false;
530    }
531}
532
533?>