PageRenderTime 86ms CodeModel.GetById 30ms app.highlight 13ms RepoModel.GetById 37ms app.codeStats 1ms

/PDB/Common.php

http://github.com/digg/pdb
PHP | 586 lines | 186 code | 41 blank | 359 comment | 15 complexity | ac1e640b9a4a69035f86429d553ccf4d 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';
 49require_once 'Pattern/Acceptor/Common.php';
 50
 51/**
 52 * Base PDB class
 53 *
 54 * @category   DB
 55 * @package    PDB
 56 * @author     Joe Stump <joe@joestump.net> 
 57 * @copyright  2007-2008 (c) Digg.com 
 58 * @license    http://tinyurl.com/42zef New BSD License
 59 * @version    Release: @package_version@
 60 * @link       http://pear.php.net/package/PDB
 61 */
 62abstract class PDB_Common extends Pattern_Acceptor_Common
 63{
 64    /**
 65     * Objects we accept
 66     *
 67     * @var array
 68     */
 69    protected $acceptable = array('PDO' => 'PDO');
 70
 71    /**
 72     * PDO DSN
 73     *
 74     * @access protected
 75     * @var string $dsn PDO DSN (e.g. mysql:host=127.0.0.1;dbname=foo)
 76     */
 77    protected $dsn = '';
 78    
 79    /**
 80     * DNS info as an stdClass
 81     *
 82     * @var stdClass
 83     */
 84    protected $dsnObject = null;    
 85
 86    /**
 87     * Username for DB connection
 88     *
 89     * @access protected
 90     * @var string $username DB username
 91     */
 92    protected $user = ''; 
 93
 94    /**
 95     * Password for DB connection
 96     *
 97     * @access protected
 98     * @var string $password DB password
 99     */
100    protected $pass = '';
101
102    /**
103     * PDO/Driver options
104     *
105     * @access protected
106     * @var array $options PDO/Driver options
107     * @link http://us.php.net/manual/en/pdo.constants.php
108     * @link http://us.php.net/manual/en/pdo.drivers.php
109     */
110    protected $options = array();
111
112    /**
113     * Default fetch mode
114     *
115     * @access      private
116     * @var         int         $fetchMode
117     */
118    public $fetchMode = PDO::FETCH_NUM;
119
120    /**
121     * Constructor
122     *
123     * @param string $dsn      The PDO DSN
124     * @param string $username The DB's username
125     * @param string $password The DB's password
126     * @param array  $options  PDO/driver options array
127     *
128     * @return void
129     * @see PDB_Common::connect(), PDB_Common::$dsn, PDB_Common::$username
130     * @see PDB_Common::$password, PDB_Common::$options
131     */
132    public function __construct($dsn, 
133                                $username = '', 
134                                $password = '', 
135                                $options = array())
136    {
137        $this->dsn      = $dsn;
138        $this->user     = $username;
139        $this->pass     = $password;
140        $this->options  = $options;
141        $this->connect();
142    }
143
144    /**
145     * Connect to the database
146     *
147     * @return void
148     * @see PDB_Common::$dsn, PDB_Common::$username
149     * @see PDB_Common::$password, PDB_Common::$options
150     * @see PDB_Common::setAttribute
151     */
152    public function connect()
153    {
154        $this->acceptDefault('PDO');
155        $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
156    }
157
158    /**
159     * Get the default connetion
160     *
161     * @return PDO
162     */
163    public function getDefaultPDO()
164    {
165        return new PDO($this->dsn, $this->user, $this->pass, $this->options);
166    }
167
168    /**
169     * We accepted a new PDO instance
170     *
171     * @return void
172     */
173    public function acceptedPDO($pdo)
174    {
175        return;
176    }
177
178    /**
179     * Reconnect to the database
180     *
181     * This reconnects to the database with the given parameters from
182     * before we either disconnected or lost the connection. This is useful
183     * for when MySQL (and others probably) servers "go away".
184     * 
185     * @see PDB_Common::disconnect(), PDB_Common::connect()
186     * @return void
187     */
188    public function reconnect()
189    {
190        $this->disconnect();
191        $this->connect();
192        foreach ($this->options as $attr => $val) {
193            $this->setAttribute($attr, $val);
194        }
195    }
196
197    /**
198     * Disconnect from the DB
199     *
200     * @return void
201     */
202    public function disconnect()
203    {
204        $pdo = $this->getPDO();
205        $pdo = null;
206    }
207    
208   /**
209     * Get DSN as an stdClass
210     *
211     * @return stdClass The DNS info in an stdClass
212     */
213    public function getDSN() {
214        if ($this->dsnObject == null) {
215            list($type, $parseMe) = explode(':', $this->dsn);
216            $parseMe              = str_replace(';', '&', $parseMe);
217            
218            $dsnParts = array();
219            parse_str($parseMe, $dsnParts);
220
221            $dsnParts['name'] = $dsnParts['dbname'];
222            $dsnParts['user'] = $this->user;
223            $dsnParts['pass'] = $this->pass;
224            $dsnParts['type'] = $type;
225            
226            unset($dsnParts['dbname']);
227            $this->dsnObject = (object) $dsnParts;
228        }
229        
230        return $this->dsnObject;
231    }       
232
233    /**
234     * Implement decorator pattern
235     *
236     * Originally {@link PDB} was extended from PDO, but this kept us from
237     * implementing valid {@link PDB_Common::disconnect()} and 
238     * {@link PDB_Common::reconnect()} methods, which were needed for other
239     * nice functionality.
240     *
241     * As a result we use {@link PDB_Common::__call()} to implement the basic
242     * decorator pattern. Everything listed below should work without issues.
243     *
244     * @param string $function Name of function to run
245     * @param array  $args     Function's arguments
246     *
247     * @method bool beginTransaction()
248     * @method bool commit()
249     * @method string errorCode()
250     * @method array errorInfo()
251     * @method int exec(string $statement)
252     * @method mixed getAttribute(int $attribute)
253     * @method string lastInsertId([string $name])
254     * @method PDOStatement prepare(string $statement [, array $driver_options])
255     * @method string quote(string $string [, int $parameter_type])
256     * @method bool rollBack()
257     * @return mixed
258     */
259    public function __call($function, array $args = array()) 
260    {
261        static $whitelist;
262
263        if (!isset($whitelist)) {
264            $rc = new ReflectionClass('PDO');
265            foreach ($rc->getMethods() as $method) {
266                if ($method->isPublic()) {
267                    $whitelist[] = $method->getName();
268                }
269            }
270        }
271
272        if (in_array($function, $whitelist)) {
273            return call_user_func_array(array($this->getPDO(), $function),
274                                        $args);
275        }
276
277        return parent::__call($function, $args);
278    }
279
280    /**
281     * Query the database
282     *
283     * <code>
284     * <?php
285     * 
286     * require_once 'PDB.php';
287     *
288     * $db = PDB::connect('mysql:host=127.0.0.1;dbname=foo', 'user', 'pass'); 
289     * $db->setFetchMode(PDO::FETCH_OBJECT);
290     *
291     * $sql = 'SELECT * 
292     *         FROM items
293     *         WHERE promoted = ? AND
294     *               userid = ?';
295     * 
296     * $result = $db->query($sql, array(1, (int)$_GET['userid']));
297     *
298     * // Notice that {@link PDB_Result} supports object iteration just like
299     * // PDOStatement does since it extends from it.
300     * foreach ($result as $row) {
301     *     echo '<a href="' . $row->url . '">' . $row->title . '</a>' . "\n";
302     * }
303     *
304     * ?>
305     * </code> 
306     *
307     * @param string $sql  The query
308     * @param array  $args The query arguments
309     *
310     * @return object Instance of {@link PDB_Result}
311     * @throws {@link PDB_Exception} on failure
312     * @link http://us3.php.net/manual/en/class.pdostatement.php
313     * @link http://us3.php.net/manual/en/pdostatement.bindparam.php
314     */
315    public function query($sql, array $args = array())
316    {
317        try {
318            $stmt = $this->prepare($sql, array(
319                PDO::ATTR_STATEMENT_CLASS => array(
320                    'PDB_Result', array($this->getPDO(), $this->fetchMode)
321                )
322            ));
323
324            if (is_array($args)) {
325                $cnt = count($args);
326                if ($cnt > 0) {
327                    foreach ($args as $key => $value) {
328                        $param  = (is_int($key) ? ($key + 1) : $key);
329                        $result = $stmt->bindParam($param, $args[$key]);
330                    }
331                }
332            }
333
334            $stmt->execute();
335            return $stmt;
336        } catch (PDOException $error) {
337            throw new PDB_Exception($error->getMessage(),
338                                    (int) $error->getCode());
339        }
340    }
341
342    /**
343     * Fetch a single row
344     *
345     * <code>
346     * <?php
347     * 
348     * require_once 'PDB.php';
349     *
350     * $db = PDB::connect('mysql:host=127.0.0.1;dbname=foo', 'user', 'pass'); 
351     * $db->setFetchMode(PDO::FETCH_OBJECT);
352     *
353     * $sql = 'SELECT * 
354     *         FROM users
355     *         WHERE userid = ?';
356     *
357     * $user = $db->getRow($sql, array((int)$_GET['userid']));
358     * echo 'Welcome back, ' . $user->username . '!';
359     *
360     * ?>
361     * </code>
362     * 
363     * @param string  $sql       The query to run
364     * @param array   $params    The query parameter values
365     * @param integer $fetchMode The fetch mode for query
366     *
367     * @see PDB_Common::query(), PDB_Result
368     * @return array
369     */
370    public function getRow($sql, 
371                           array $params = array(), 
372                           $fetchMode = null)
373    {
374        if (is_null($fetchMode)) {
375            $fetchMode = $this->fetchMode;
376        }
377
378        $result = $this->query($sql, $params);
379        $res = $result->fetchRow($fetchMode);
380        $result->closeCursor();
381        return $res;
382    }
383
384    /**
385     * Fetch a single column
386     *
387     * <code>
388     * <?php
389     * 
390     * require_once 'PDB.php';
391     *
392     * $db  = PDB::connect('mysql:host=127.0.0.1;dbname=foo', 'user', 'pass'); 
393     * $sql = 'SELECT friendid 
394     *         FROM friends
395     *         WHERE userid = ?';
396     *
397     * $friends = $db->getCol($sql, 0, array((int)$_GET['userid']));
398     * if (in_array($_SESSION['userid'], $friends)) {
399     *    echo 'You are friends with this user!';
400     * }
401     *
402     * ?>
403     * </code>
404     * 
405     * @param string  $sql    The query to run
406     * @param integer $col    The column number to fetch (zero-based)
407     * @param array   $params The query parameter values
408     *
409     * @see PDB_Common::query(), PDB_Result
410     * @return array
411     */
412    public function getCol($sql, $col = 0, array $params = array())
413    {
414        $result = $this->query($sql, $params);
415        $ret    = array();
416        while ($row = $result->fetchRow(PDO::FETCH_NUM)) {
417            $ret[] = $row[$col];
418        }
419
420        $result->closeCursor();
421        return $ret;
422    }
423
424    /**
425     * Fetch all records in query as array
426     *
427     * This method will fetch all records from a given query into a 
428     * numerically indexed array (e.g. $result[0] is the first record).
429     *
430     * <code>
431     * <?php
432     * 
433     * require_once 'PDB.php';
434     *
435     * $db = PDB::connect('mysql:host=127.0.0.1;dbname=foo', 'user', 'pass'); 
436     * $db->setFetchMode(PDO::FETCH_OBJECT);
437     * 
438     * $sql = 'SELECT * 
439     *         FROM users
440     *         WHERE type = ?';
441     *
442     * $students = $db->getAll($sql, array('student'));
443     * foreach ($students as $student) {
444     *     echo $student->firstname . "\n";
445     * }
446     *
447     * ?>
448     * </code>
449     *
450     * @param string  $sql       The query to run
451     * @param array   $params    The query parameter values
452     * @param integer $fetchMode The fetch mode for query
453     *
454     * @return array
455     * @see PDB_Result, PDB_Common::query()
456     */
457    public function getAll($sql, 
458                           array $params = array(), 
459                           $fetchMode = null) 
460    {
461        if (is_null($fetchMode)) {
462            $fetchMode = $this->fetchMode;
463        }
464
465        $result = $this->query($sql, $params);
466        $ret    = array();
467        while ($row = $result->fetchRow($fetchMode)) {
468            $ret[] = $row;
469        }
470        $result->closeCursor();
471
472        return $ret;
473    }
474
475    /**
476     * Get a single field
477     *
478     * This will fetch a single value from the first row's first
479     * column. 
480     *
481     * <code>
482     * <?php
483     * 
484     * require_once 'PDB.php';
485     *
486     * $db  = PDB::connect('mysql:host=127.0.0.1;dbname=foo', 'user', 'pass'); 
487     * $sql = 'SELECT COUNT(*) AS total
488     *         FROM users
489     *         WHERE type = ?';
490     *
491     * $total = $db->getOne($sql, array('student'));
492     * if (!$total) {
493     *     echo 'No students!';
494     * }
495     *
496     * ?>
497     * </code>
498     *
499     * @param string $sql    The query to run
500     * @param array  $params The query parameter values
501     *
502     * @see PDB_Common::query(), PDB_Result::fetchRow()
503     * @return mixed The value of the first row/column
504     */
505    public function getOne($sql, array $params = array()) 
506    {
507        $result = $this->query($sql, $params);
508        $row    = $result->fetchRow(PDO::FETCH_NUM);
509        $result->closeCursor();
510        return $row[0];
511    }
512
513    /**
514     * Set the fetch mode for all queries
515     *
516     * This should be set to one of PDO's fetch modes. Valid values include:
517     *  - PDO::FETCH_LAZY
518     *  - PDO::FETCH_ASSOC
519     *  - PDO::FETCH_NAMED
520     *  - PDO::FETCH_NUM
521     *  - PDO::FETCH_BOTH
522     *  - PDO::FETCH_OBJ
523     *
524     * @param integer $mode The DB fetch mode
525     *
526     * @throws UnexpectedArgumentException on invalid modes
527     * @access public
528     * @return void
529     */
530    public function setFetchMode($mode)
531    {
532        switch ($mode) {
533        case PDO::FETCH_LAZY:
534        case PDO::FETCH_ASSOC:
535        case PDO::FETCH_NAMED:
536        case PDO::FETCH_NUM:
537        case PDO::FETCH_BOTH:
538        case PDO::FETCH_OBJ:
539            $this->fetchMode = $mode;
540            break;
541        default:
542            throw new InvalidArgumentException('Invalid mode');
543        }
544    }
545
546    /**
547     * Set an attribute
548     *
549     * @param integer $attribute The attribute to set
550     * @param mixed   $value     The attribute's value
551     *
552     * @link http://us.php.net/manual/en/pdo.setattribute.php
553     * @return true False if something failed to set
554     */
555    public function setAttribute($attribute, $value)
556    {
557        if ($attribute & PDB::PDB_ATTRS) {
558            $this->options[$attribute] = $value;
559            return true;
560        }
561
562        if ($this->getPDO()->setAttribute($attribute, $value)) {
563            $this->options[$attribute] = $value;
564            return true;
565        }
566
567        return false;
568    }
569
570    /**
571     * Get an attribute
572     *
573     * @param int $attr Attribute to get
574     *
575     * @return mixed Attribute value
576     */
577    public function getAttribute($attr)
578    {
579        if ($attr & PDB::PDB_ATTRS) {
580            return isset($this->options[$attr]) ? $this->options[$attr] : null;
581        }
582        return $this->getPDO()->getAttribute($attr);
583    }
584}
585
586?>