/src/Resource/Db.php

https://github.com/andyburton/Sonic-Framework · PHP · 1411 lines · 548 code · 506 blank · 357 comment · 98 complexity · d8f8c5ce47402f633d18a30c0f060400 MD5 · raw file

  1. <?php
  2. // Define namespace
  3. namespace Sonic\Resource;
  4. // Start Db Class
  5. class Db extends \PDO
  6. {
  7. /**
  8. * Valid query comparison operators
  9. * @var array
  10. */
  11. private static $validComparison = array ('=', '>=', '>', '<=', '<', '<>', '!=', 'LIKE', 'IN', 'NOT IN', 'IS', 'IS NOT', '&', '|', 'BETWEEN');
  12. /**
  13. * Valid query clause seperators
  14. * @var array
  15. */
  16. private static $validSplit = array ('AND', 'OR', '||', '&&');
  17. /**
  18. * Valid query clause seperators
  19. * @var array
  20. */
  21. private static $validOrder = array ('ASC','DESC');
  22. /**
  23. * Keeps track of the number of transactions
  24. * @var int
  25. */
  26. private $transactionCount = 0;
  27. /**
  28. * Connection status
  29. * @var boolean
  30. */
  31. private $connected = FALSE;
  32. /**
  33. * Data Source Name (DSN)
  34. * @var string
  35. */
  36. private $_dsn = FALSE;
  37. /**
  38. * Database Host
  39. * @var string
  40. */
  41. private $_host = '127.0.0.1';
  42. /**
  43. * Database Username
  44. * @var string
  45. */
  46. private $_user = FALSE;
  47. /**
  48. * Database Password
  49. * @var string
  50. */
  51. private $_pass = FALSE;
  52. /**
  53. * Database Name
  54. * @var string
  55. */
  56. private $_db_name = FALSE;
  57. /**
  58. * Database PDO Options
  59. * @var array
  60. */
  61. private $_options = array ();
  62. /**
  63. * Other databases that are commited and rolled back at the same time
  64. * @var type
  65. */
  66. private $_transaction_hooks = array ();
  67. /**
  68. * Instantiate class
  69. * @param string $dsn Data Source Name (DSN)
  70. * @param string $user Username
  71. * @param string $pass Password
  72. * @param array $options Connection Options
  73. * @return @return \Sonic\Resource\Db
  74. */
  75. public function __construct ($dsn, $user, $pass, $options)
  76. {
  77. $this->Connect ($dsn, $user, $pass, $options);
  78. }
  79. /**
  80. * Connect to the database if not already done
  81. * Will throw a PDOException is connection fails
  82. * @param string $dsn Data Source Name (DSN)
  83. * @param string $user Username
  84. * @param string $pass Password
  85. * @param array $options Connection Options
  86. * @return void
  87. */
  88. public function Connect ($dsn = FALSE, $user = FALSE, $pass = FALSE, $options = FALSE)
  89. {
  90. if (!$this->connected)
  91. {
  92. $this->_dsn = $dsn?: $this->_dsn;
  93. $this->_user = $user?: $this->_user;
  94. $this->_pass = $pass?: $this->_pass;
  95. $this->_options = $options?: $this->_options;
  96. parent::__construct ($this->_dsn, $this->_user, $this->_pass, $this->_options);
  97. $this->connected = TRUE;
  98. }
  99. }
  100. /**
  101. * Set connection details to the database
  102. * Will throw a PDOException is connection fails
  103. * @param string $dsn Data Source Name (DSN)
  104. * @param string $user Username
  105. * @param string $pass Password
  106. * @param array $options Connection Options
  107. * @return void
  108. */
  109. public function setConnection ($dsn = FALSE, $user = FALSE, $pass = FALSE, $options = FALSE)
  110. {
  111. $this->_dsn = $dsn?: $this->_dsn;
  112. $this->_user = $user?: $this->_user;
  113. $this->_pass = $pass?: $this->_pass;
  114. $this->_options = $options?: $this->_options;
  115. }
  116. /**
  117. * Return database Data Source Name (DSN)
  118. * @return type string
  119. */
  120. public function getDSN ()
  121. {
  122. return $this->_dsn;
  123. }
  124. /**
  125. * Generate a where clause from an array
  126. * @param array $arrWHERE Where clause array
  127. * @return string
  128. */
  129. public static function genWHERE ($arrWHERE)
  130. {
  131. // Set counter variables
  132. $intCount = count ($arrWHERE);
  133. $intNum = 1;
  134. // Set where clause
  135. $strWHERE = '';
  136. // If the where condition is not an array
  137. if (!is_array ($arrWHERE))
  138. {
  139. // Set it as a single array item
  140. $arrWHERE = array ($arrWHERE);
  141. }
  142. // For each where clause
  143. foreach ($arrWHERE as $key => $arrClause)
  144. {
  145. // If the first clause is an array
  146. if (is_array ($arrClause[0]))
  147. {
  148. // Generate clause where
  149. $strWHERE .= '(' . self::genWHERE ($arrClause) . ')';
  150. // If not the last clause
  151. if ($intNum != $intCount)
  152. {
  153. // Get split
  154. if (isset ($arrClause[count($arrClause)-1][3]))
  155. {
  156. $strSplit = $arrClause[count($arrClause)-1][3];
  157. }
  158. else
  159. {
  160. $strSplit = 'AND';
  161. }
  162. // If the last split is valid
  163. if (in_array (strtoupper ($strSplit), self::$validSplit))
  164. {
  165. // Set it to the comparison used
  166. $strWHERE .= strtoupper ($strSplit) . ' ';
  167. }
  168. // Else use default
  169. else
  170. {
  171. // Append clause
  172. $strWHERE .= 'AND ';
  173. }
  174. }
  175. }
  176. // Else the first clause is not an array
  177. else
  178. {
  179. // If the clause is an array
  180. if (is_array ($arrClause))
  181. {
  182. // If there is a comparison type and it is valid
  183. if (isset ($arrClause[2]) && in_array ($arrClause[2], self::$validComparison))
  184. {
  185. // Set it to the comparison used
  186. $strComparison = $arrClause[2];
  187. }
  188. // Else set to default
  189. else
  190. {
  191. // Set default comparison
  192. $strComparison = '=';
  193. }
  194. // Append clause
  195. $strWHERE .= $arrClause[0] . " " . $strComparison . " ";
  196. // If the comparison is IN or NOT IN
  197. if ($strComparison == 'IN' || $strComparison == 'NOT IN')
  198. {
  199. // Append
  200. if (is_array ($arrClause[1]))
  201. {
  202. $strWHERE .= '(' . implode (',', $arrClause[1]) . ') ';
  203. }
  204. else
  205. {
  206. $strWHERE .= '(' . $arrClause[1] . ') ';
  207. }
  208. }
  209. // Else if the comparison is IS or IS NOT
  210. else if ($strComparison == 'IS' || $strComparison == 'IS NOT')
  211. {
  212. // Append
  213. $strWHERE .= $arrClause[1] . ' ';
  214. }
  215. // Else if the comparison is BETWEEN
  216. else if ($strComparison == 'BETWEEN')
  217. {
  218. // If the value is a string add marks around comparison
  219. if (is_string ($arrClause[1][0]))
  220. {
  221. $strWHERE .= '\'' . $arrClause[1][0] . '\' AND \'' . $arrClause[1][1] . '\' ';
  222. }
  223. // Else not a string, just compare as is
  224. else
  225. {
  226. $strWHERE .= $arrClause[1][0] . ' AND ' . $arrClause[1][1] . ' ';
  227. }
  228. }
  229. // Else the comparison is not IN or NOT IN
  230. else
  231. {
  232. // Append
  233. $strWHERE .= ':' . md5 ($arrClause[0]) . '_' . md5 ($arrClause[1]) . ' ';
  234. }
  235. }
  236. // Clause is not an array
  237. else
  238. {
  239. // Add clause as it is
  240. $strWHERE .= $arrClause . ' ';
  241. }
  242. // If not the last clause
  243. if ($intNum != $intCount)
  244. {
  245. // If there is a split type and it is valid
  246. if (isset ($arrClause[3]) && in_array (strtoupper ($arrClause[3]), self::$validSplit))
  247. {
  248. // Set it to the comparison used
  249. $strWHERE .= strtoupper ($arrClause[3]) . ' ';
  250. }
  251. // Else use default
  252. else
  253. {
  254. // Append clause
  255. $strWHERE .= 'AND ';
  256. }
  257. }
  258. }
  259. // Increment the count
  260. $intNum++;
  261. }
  262. // Return WHERE
  263. return $strWHERE;
  264. }
  265. /**
  266. * Bind query values from where array
  267. * @param \PDOStatement $query PDO query
  268. * @param array $arrWHERE Where clause array
  269. * @return void
  270. */
  271. public static function genBindValues (&$query, $arrWHERE)
  272. {
  273. // Foreach where clause
  274. foreach ($arrWHERE as $arrClause)
  275. {
  276. // If the first clause is an array
  277. if (is_array ($arrClause[0]))
  278. {
  279. // Bind params
  280. self::genBindValues ($query, $arrClause);
  281. }
  282. // Else the first clause is not an array
  283. else
  284. {
  285. // If clause is an array
  286. if (is_array ($arrClause))
  287. {
  288. // If there no comparison or there is one, its valid and not IN, NOT IN, IS, IS NOT or BETWEEN
  289. if (!isset ($arrClause[2]) || (
  290. !in_array ($arrClause[2], array ('IN', 'NOT IN', 'IS', 'IS NOT', 'BETWEEN')) &&
  291. in_array ($arrClause[2], self::$validComparison)
  292. ))
  293. {
  294. // Bind value
  295. $query->bindValue (':' . md5 ($arrClause[0]) . '_' . md5 ($arrClause[1]), $arrClause[1]);
  296. }
  297. }
  298. }
  299. }
  300. }
  301. /**
  302. * Bind an array of parameters to the query
  303. * @param \PDOStatement $query PDO query
  304. * @param array $params Parameters to bind
  305. * @return boolean
  306. */
  307. public function bindValues (&$query, $params)
  308. {
  309. foreach ($params as $key => $value)
  310. {
  311. if (is_array ($value))
  312. {
  313. if (!$query->bindValue ($key, $value[0], $value[1]))
  314. {
  315. return FALSE;
  316. }
  317. }
  318. else
  319. {
  320. if (!$query->bindValue ($key, $value))
  321. {
  322. return FALSE;
  323. }
  324. }
  325. }
  326. return TRUE;
  327. }
  328. /**
  329. * Generate an SQL statement from an array of clauses
  330. *
  331. * $arrParams can have the following keys:
  332. * select - array|string of fields to SELECT (defaults to '*')
  333. * from - array|string of tables to select FROM (defaults to db_table of class)
  334. * where - array of WHERE clauses that get's passed to self::genWHERE
  335. * groupby - array|string of GROUP BY clauses
  336. * having - array of HAVING clauses that gets passed to self::genWHERE and can only be used if 'groupby' is set
  337. * orderby - array|string of ORDER BY clause or array of clauses array (column, ASC|DESC)
  338. * limit - array|integer with the return limit number or array (start, limit)
  339. *
  340. * @param array $arrParams Array of clauses to add to the SQL
  341. * @return string
  342. */
  343. public function genSQL ($arrParams = array ())
  344. {
  345. // SELECT clause
  346. if (!isset ($arrParams['select']))
  347. {
  348. $arrParams['select'] = NULL;
  349. }
  350. $strSELECT = $this->genClause ('SELECT', $arrParams['select'], '*');
  351. // If there is no from clause
  352. if (!isset ($arrParams['from']))
  353. {
  354. // Error
  355. throw new Exception ('You must specify a from clause!');
  356. }
  357. // FROM clause
  358. $strFROM = $this->genClause ('FROM', $arrParams['from'], FALSE, ',');
  359. // WHERE clause
  360. if (isset ($arrParams['where']) && is_array ($arrParams['where']))
  361. {
  362. // Generate WHERE clause
  363. $strWHERE = $this->genClause ('WHERE', self::genWHERE ($arrParams['where']));
  364. }
  365. else
  366. {
  367. $strWHERE = NULL;
  368. }
  369. // GROUP BY clause
  370. if (!isset ($arrParams['groupby']))
  371. {
  372. $arrParams['groupby'] = NULL;
  373. }
  374. $strGROUPBY = $this->genClause ('GROUP BY', $arrParams['groupby']);
  375. // If there is a group by clause and a having clause
  376. $strHAVING = NULL;
  377. if (NULL !== $strGROUPBY && isset ($arrParams['having']))
  378. {
  379. // Generate HAVING clause
  380. $strHAVING = $this->genClause ('HAVING', self::genWHERE ($arrParams['having']));
  381. }
  382. // ORDER BY clause
  383. if (!isset ($arrParams['orderby']))
  384. {
  385. $arrParams['orderby'] = NULL;
  386. }
  387. else if (is_array ($arrParams['orderby']))
  388. {
  389. // If just a single clause add into correct structure for validation
  390. if (count ($arrParams['orderby']) == 2 &&
  391. !is_array ($arrParams['orderby'][0]) &&
  392. !is_array ($arrParams['orderby'][1]))
  393. {
  394. $arrParams['orderby'] = array ($arrParams['orderby']);
  395. }
  396. // Make sure the clauses are safe
  397. foreach ($arrParams['orderby'] as &$val)
  398. {
  399. if (is_array ($val))
  400. {
  401. foreach ($val as &$clause)
  402. {
  403. $clause = preg_replace ('/[^\w]/', '', $clause);
  404. }
  405. $val = implode (' ', $val);
  406. }
  407. else
  408. {
  409. $arr = explode (' ', $val);
  410. if (count ($arr) == 2)
  411. {
  412. $arr[0] = preg_replace ('/[^\w]/', '', $arr[0]);
  413. if (!in_array ($arr[1], self::$validOrder))
  414. {
  415. unset ($arr[1]);
  416. }
  417. $val = implode (' ', $arr);
  418. }
  419. else
  420. {
  421. $val = preg_replace ('/[^\w]/', '', $val);
  422. }
  423. }
  424. }
  425. }
  426. $strORDERBY = $this->genClause ('ORDER BY', $arrParams['orderby']);
  427. // LIMIT clause
  428. if (!isset ($arrParams['limit']))
  429. {
  430. $arrParams['limit'] = NULL;
  431. }
  432. else if (is_array ($arrParams['limit']))
  433. {
  434. foreach ($arrParams['limit'] as &$val)
  435. {
  436. $val = (int) $val;
  437. }
  438. }
  439. else
  440. {
  441. $arrParams['limit'] = (int) $arrParams['limit'];
  442. }
  443. $strLIMIT = $this->genClause ('LIMIT', $arrParams['limit']);
  444. // Put query together
  445. $strSQL = $strSELECT . $strFROM . $strWHERE . $strGROUPBY . $strHAVING . $strORDERBY . $strLIMIT;
  446. // Return query
  447. return $strSQL;
  448. }
  449. /**
  450. * Generates a clause in an SQL statement
  451. * @param string $strName The name of the clause
  452. * @param string|array $mixOptions Options to add to the clause
  453. * @param string $strDefault Default option to use if $mixOptions is not set
  454. * @param string $strSeparator Separator to use between options
  455. * @return string
  456. */
  457. public function genClause ($strName, $mixOptions, $strDefault = FALSE, $strSeparator = ', ')
  458. {
  459. // Set clause
  460. $strClause = NULL;
  461. // If there are options
  462. if (isset ($mixOptions) && $mixOptions)
  463. {
  464. // Start the param
  465. $strClause = $strName . ' ';
  466. // If array
  467. if (is_array ($mixOptions) && count ($mixOptions) > 0)
  468. {
  469. // join all order by clauses together
  470. $strClause .= implode ($strSeparator, $mixOptions);
  471. }
  472. // Else not array
  473. else
  474. {
  475. // Append clause
  476. $strClause .= $mixOptions;
  477. }
  478. }
  479. // No options set but there is a default
  480. else if (FALSE !== $strDefault)
  481. {
  482. $strClause = $strName . ' ' . $strDefault;
  483. }
  484. // If there is a clause
  485. if (NULL !== $strClause)
  486. {
  487. // Add space to the end of the clause
  488. $strClause .= ' ';
  489. }
  490. // Return clause
  491. return $strClause;
  492. }
  493. /**
  494. * Returns a PDO query resource ready for execution
  495. * @param array $arrParams Array of clauses to add to the SQL
  496. * @return \PDOStatement
  497. */
  498. public function genQuery ($arrParams = array ())
  499. {
  500. // Generate sql query
  501. $strSQL = $this->genSQL ($arrParams);
  502. // prepare database query
  503. $query = $this->prepare ($strSQL);
  504. // If there are some WHERE clauses
  505. if (isset ($arrParams['where']) && is_array ($arrParams['where']))
  506. {
  507. // Bind WHERE clauses
  508. $this->genBindValues ($query, $arrParams['where']);
  509. }
  510. // If there are some HAVING clauses
  511. if (isset ($arrParams['having']) && is_array ($arrParams['having']))
  512. {
  513. // Bind HAVING clauses
  514. $this->genBindValues ($query, $arrParams['having']);
  515. }
  516. // If there are some parameters to bind
  517. if (isset ($arrParams['bind']) && is_array ($arrParams['bind']))
  518. {
  519. if (!$this->bindValues ($query, $arrParams['bind']))
  520. {
  521. return FALSE;
  522. }
  523. }
  524. // Return query
  525. return $query;
  526. }
  527. /**
  528. * Returns a PDO query resource ready for execution for an SQL union
  529. * @param array $arrParam SQL Parameter Array
  530. * @param array $arrParam SQL Parameter Array
  531. * ... etc
  532. * @return \PDOStatement
  533. */
  534. public function genUnionQuery ()
  535. {
  536. $sql = $this->genUnionSQL (func_get_args (), 'UNION ALL');
  537. // Prepare database query
  538. $query = $this->prepare ($sql);
  539. // Bind values
  540. $this->genUnionBindValues ($query, func_get_args ());
  541. // Return query
  542. return $query;
  543. }
  544. /**
  545. * Returns a PDO query resource ready for execution for an SQL union
  546. * @param array $arrParam SQL Parameter Array
  547. * @param array $arrParam SQL Parameter Array
  548. * ... etc
  549. * @return \PDOStatement
  550. */
  551. public function genDistinctUnionQuery ()
  552. {
  553. $sql = $this->genUnionSQL (func_get_args (), 'UNION DISTINCT');
  554. // Prepare database query
  555. $query = $this->prepare ($sql);
  556. // Bind values
  557. $this->genUnionBindValues ($query, func_get_args ());
  558. // Return query
  559. return $query;
  560. }
  561. /*
  562. * Generate SQL union query from arrays of query params
  563. * @param array $args Array of query param arrays
  564. * @param string $union Union join SQL, default UNION
  565. * @return string
  566. */
  567. public function genUnionSQL ($args, $union = 'UNION')
  568. {
  569. // Set SQL string
  570. $sql = '';
  571. // Foreach argument
  572. foreach ($args as $key => $arg)
  573. {
  574. // If not the first argument add UNION
  575. if ($key > 0)
  576. {
  577. $sql .= ' ' . $union . ' ';
  578. }
  579. // Generate SQL and append to query
  580. $sql .= $this->genSQL ($arg);
  581. }
  582. return $sql;
  583. }
  584. /**
  585. * Bind values to a union query
  586. * @param \PDOStatement $query PDO query
  587. * @param array $args Array of parameters
  588. * @return \PDOStatement
  589. */
  590. public function genUnionBindValues (&$query, $args)
  591. {
  592. foreach ($args as $key => $arg)
  593. {
  594. if (isset ($arg['where']) && is_array ($arg['where']))
  595. {
  596. $this->genBindValues ($query, $arg['where']);
  597. }
  598. if (isset ($arg['having']) && is_array ($arg['having']))
  599. {
  600. $this->genBindValues ($query, $arg['having']);
  601. }
  602. }
  603. }
  604. /**
  605. * Return a single value or row
  606. * @param array $arrParams Query Parameters
  607. * @param int $fetchMode PDO fetch mode, default to both
  608. * @return mixed
  609. */
  610. public function getValue ($arrParams, $fetchMode = \PDO::FETCH_BOTH)
  611. {
  612. // If the params are not valid return FALSE
  613. if (!$this->validateParams ($arrParams))
  614. {
  615. return FALSE;
  616. }
  617. // Limit to 1 result
  618. $arrParams['limit'] = '1';
  619. // Query
  620. $query = $this->genQuery ($arrParams);
  621. // Execute
  622. $query->execute ();
  623. // Fetch the first set of results
  624. $arrResult = $query->fetch ($fetchMode);
  625. // If there is only 1 column
  626. if (is_array ($arrResult) && count ($arrResult) === 1)
  627. {
  628. // Return the first column only
  629. return array_shift ($arrResult);
  630. }
  631. // else
  632. else
  633. {
  634. // Return result
  635. return $arrResult;
  636. }
  637. }
  638. /**
  639. * Return all rows or array of single values
  640. * @param array $arrParams Query Parameters
  641. * @return mixed
  642. */
  643. public function getValues ($arrParams)
  644. {
  645. // If the params are not valid return FALSE
  646. if (!$this->validateParams ($arrParams))
  647. {
  648. return FALSE;
  649. }
  650. // Query
  651. $query = $this->genQuery ($arrParams);
  652. // Execute
  653. $query->execute ();
  654. // If there is only 1 select param and it does not have *
  655. $arrSelect = is_array ($arrParams['select'])? $arrParams['select'] : explode (',', $arrParams['select']);
  656. if (count ($arrSelect) === 1 && strpos ($arrSelect[0], '*') === FALSE)
  657. {
  658. // Set fetch mode to return only the first column of each row
  659. $query->setFetchMode (\PDO::FETCH_COLUMN, 0);
  660. }
  661. else
  662. {
  663. // Set fetch mode to return all rows
  664. $query->setFetchMode (\PDO::FETCH_ASSOC);
  665. }
  666. // Fetch the first set of results
  667. $arrResult = $query->fetchAll ();
  668. // Return result
  669. return $arrResult;
  670. }
  671. /**
  672. * Validates the SQL generation clauses
  673. * @param array $arrParams Array of SQL clauses
  674. * @return boolean
  675. */
  676. private function validateParams ($arrParams)
  677. {
  678. // Validate
  679. if (!isset ($arrParams['select']) || !isset ($arrParams['from']))
  680. {
  681. // Return FALSE
  682. return FALSE;
  683. }
  684. // Return TRUE
  685. return TRUE;
  686. }
  687. /**
  688. * Executes an SQL statement
  689. * @param string $strSQL The SQL statement
  690. * @param array $arrBindParams Array of parameters to bind
  691. * @return boolean
  692. */
  693. public function executeQuery ($strSQL, $arrBindParams = array ())
  694. {
  695. // Prepare query
  696. $query = $this->prepare ($strSQL);
  697. // Bind values
  698. if (!$this->bindValues ($query, $arrBindParams))
  699. {
  700. return FALSE;
  701. }
  702. // Return query execution status
  703. return $query->execute ();
  704. }
  705. /**
  706. * Get the query associate resultset with sql and bind params
  707. * @param type $strSQL
  708. * @param array $arrBindParams Query Parameters
  709. * @return mixed
  710. */
  711. public function fetchAssocBySql ($strSQL, $arrBindParams = array ())
  712. {
  713. // Prepare query
  714. $query = $this->prepare ($strSQL);
  715. // Bind values
  716. if (!$this->bindValues ($query, $arrBindParams))
  717. {
  718. return FALSE;
  719. }
  720. // query execution status
  721. $success = $query->execute ();
  722. if (!$success)
  723. {
  724. return FALSE;
  725. }
  726. return $query->fetchAll (2);
  727. }
  728. /**
  729. * Execute an SQL statement
  730. * @param string $strSQL The SQL statement
  731. * @return int|boolean
  732. */
  733. public function exec ($strSQL)
  734. {
  735. // Make sure we're connected
  736. $this->Connect ();
  737. // Execute statement
  738. return parent::exec ($strSQL);
  739. }
  740. /**
  741. * Execute an SQL statement returning the PDOStatement object
  742. * See PDO::query documentation for more details on the fetch modes
  743. * @param string $strSQL The SQL statement
  744. * @param integer $intMode Fetch mode
  745. * @param mixed $v1 Fetch variable 1
  746. * @param mixed $v2 Fetch variable 2
  747. * @return PDOStatement|boolean
  748. */
  749. public function query ($strSQL, $intMode = FALSE, $v1 = FALSE, $v2 = FALSE)
  750. {
  751. // Make sure we're connected
  752. $this->Connect ();
  753. // Execute statement
  754. switch ($intMode)
  755. {
  756. case \PDO::FETCH_COLUMN:
  757. case \PDO::FETCH_INTO:
  758. $stmt = parent::query ($strSQL, $intMode, $v1);
  759. break;
  760. case \PDO::FETCH_CLASS:
  761. $stmt = parent::query ($strSQL, $intMode, $v1, $v2);
  762. break;
  763. default:
  764. $stmt = parent::query ($strSQL);
  765. break;
  766. }
  767. // Return statement
  768. return $stmt;
  769. }
  770. /**
  771. * Prepare an SQL statement
  772. * @param string $strSQL The SQL statement
  773. * @param array $arrOptions Statement Options
  774. * @return PDOStatement
  775. */
  776. public function prepare ($strSQL, $arrOptions = array ())
  777. {
  778. // Make sure we're connected
  779. $this->Connect ();
  780. // Return prepared statement
  781. return parent::prepare ($strSQL, $arrOptions);
  782. }
  783. /**
  784. * Add a transaction hook to commit and rollback another database at the same time as this database
  785. * @param \Sonic\Resource\Db $db
  786. */
  787. public function addTransactionHook (Db &$db)
  788. {
  789. // Get argument database DSN
  790. $dsn = $db->getDSN ();
  791. // Set if the databases are not the same and the the database isn't already hooked
  792. if ($dsn != $this->getDSN () && !isset ($this->_transaction_hooks[$dsn]))
  793. {
  794. $this->_transaction_hooks[$dsn] =& $db;
  795. return TRUE;
  796. }
  797. return FALSE;
  798. }
  799. /**
  800. * Begins a transaction only if there is not already a transaction in progress
  801. * @return boolean
  802. */
  803. public function beginTransaction ()
  804. {
  805. // Make sure we're connected
  806. $this->Connect ();
  807. // Increment transaction count and begin if necessary
  808. if ($this->transactionCount++ === 0)
  809. {
  810. return parent::beginTransaction ();
  811. }
  812. else
  813. {
  814. return TRUE;
  815. }
  816. }
  817. /**
  818. * Commits a transaction if the passed parameter is true, otherwise rolls it back
  819. * @param boolean $status
  820. * @return boolean
  821. */
  822. public function commitIf ($status)
  823. {
  824. if ($status)
  825. {
  826. $this->commit ();
  827. }
  828. else
  829. {
  830. $this->rollBack ();
  831. }
  832. return $status;
  833. }
  834. /**
  835. * Commits a transaction as long as there are no other active transactions
  836. * @return boolean
  837. */
  838. public function commit ()
  839. {
  840. if (--$this->transactionCount === 0)
  841. {
  842. $status = parent::commit ();
  843. // Commit any hooked databases
  844. if ($this->_transaction_hooks)
  845. {
  846. if ($status)
  847. {
  848. foreach ($this->_transaction_hooks as $dsn => &$db)
  849. {
  850. $db->commit ();
  851. }
  852. }
  853. $this->_transaction_hooks = array ();
  854. }
  855. return $status;
  856. }
  857. else
  858. {
  859. return FALSE;
  860. }
  861. }
  862. /**
  863. * Rolls back a transaction and allows new transactions to begin
  864. * @return boolean
  865. */
  866. public function rollBack ()
  867. {
  868. if (--$this->transactionCount === 0)
  869. {
  870. $status = parent::rollBack ();
  871. // Rollback any hooked databases
  872. if ($this->_transaction_hooks)
  873. {
  874. foreach ($this->_transaction_hooks as &$db)
  875. {
  876. $db->rollBack ();
  877. }
  878. $this->_transaction_hooks = array ();
  879. }
  880. return $status;
  881. }
  882. else
  883. {
  884. return FALSE;
  885. }
  886. }
  887. /**
  888. * Return the currently selected database
  889. * @return string
  890. */
  891. public function getDatabaseName ()
  892. {
  893. $query = $this->query ('SELECT database()');
  894. return $query->fetchColumn ();
  895. }
  896. /**
  897. * Return depth of transactions
  898. * @return integer
  899. */
  900. public function getTransactionCount ()
  901. {
  902. return $this->transactionCount;
  903. }
  904. }
  905. // End Db Class