PageRenderTime 64ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

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