Просмотр файла app/classes/DBM.php

Размер файла: 24.15Kb
  1. <?php
  2.  
  3. class DBM {
  4.  
  5. /**
  6. * Automatically add/update created/updated fields
  7. *
  8. * @var boolean
  9. */
  10. public static $timestamp_writes = false;
  11.  
  12. /**
  13. * Dynamic config creds
  14. *
  15. * @var Array - representing config details
  16. */
  17. protected $config;
  18.  
  19. /**
  20. * The PDO objects for the connection
  21. *
  22. * @var PDO - the Pear Data Object
  23. */
  24. protected $pdo;
  25.  
  26. /**
  27. * A reference to the singleton instance
  28. *
  29. * @var DBM
  30. */
  31. protected static $instance = null;
  32.  
  33. /**
  34. * method instance.
  35. * - static, for singleton, for creating a global instance of this object
  36. * @return DBM - DBM Object
  37. */
  38. public static function run() {
  39. if (! isset(self::$instance)) {
  40. self::$instance = new DBM();
  41. }
  42. return self::$instance;
  43. }
  44.  
  45. /**
  46. * Constructor.
  47. * - make protected so only subclasses and self can create this object (singleton)
  48. */
  49. protected function __construct() {}
  50.  
  51. /**
  52. * method config
  53. * - configure connection credentials to the db server
  54. *
  55. * @param $host
  56. * @param $name
  57. * @param $user
  58. * @param $password
  59. * @param null $port
  60. * @param string $driver
  61. * @throws Exception
  62. * @internal param $host - the host name of the db to connect to
  63. * @internal param $name - the database name
  64. * @internal param $user - the user name
  65. * @internal param $password - the users password
  66. * @internal param $port (optional) - the port to connect using, default to 3306
  67. * @internal param $driver - the dsn prefix
  68. */
  69. public function config($host, $name, $user, $password, $port=null, $driver='mysql') {
  70. if (!$this->validateDriver($driver)) {
  71. throw new Exception('DATABASE WRAPPER::error, the database you wish to connect to is not supported by your install of PHP.');
  72. }
  73.  
  74. if (isset($this->pdo)) {
  75. error_log('DATABASE WRAPPER::warning, attempting to config after connection exists');
  76. }
  77.  
  78. $this->config = [
  79. 'driver' => $driver,
  80. 'host' => $host,
  81. 'name' => $name,
  82. 'user' => $user,
  83. 'password' => $password,
  84. 'port' => $port
  85. ];
  86. }
  87.  
  88. /**
  89. * method createConnection.
  90. * - create a PDO connection using the credentials provided
  91. *
  92. * @param $driver
  93. * @param $host
  94. * @param $name
  95. * @param $user
  96. * @param $password
  97. * @param null $port
  98. * @return PDO object with a connection to the database specified
  99. * @throws Exception
  100. * @internal param $driver - the dsn prefix
  101. * @internal param $host - the host name of the db to connect to
  102. * @internal param $name - the database name
  103. * @internal param $user - the user name
  104. * @internal param $password - the users password
  105. * @internal param $port (optional) - the port to connect using, default to 3306
  106. */
  107. protected function createConnection($driver, $host, $name, $user, $password, $port=null) {
  108. if (!$this->validateDriver($driver)) {
  109. throw new Exception('DATABASE WRAPPER::error, the database you wish to connect to is not supported by your install of PHP.');
  110. }
  111.  
  112. // attempt to create pdo object and connect to the database
  113. try {
  114. //@TODO the following drivers are NOT supported yet: odbc, ibm, informix, 4D
  115. // build the connection string from static constants based on the selected PDO Driver.
  116. if ($driver == "sqlite" || $driver == "sqlite2") {
  117. $connection_string = $driver.':'.$host;
  118. } elseif ($driver == "sqlsrv") {
  119. $connection_string = "sqlsrv:Server=".$host.";Database=".$name;
  120. } elseif ($driver == "firebird" || $driver == "oci") {
  121. $connection_string = $driver.":dbname=".$name;
  122. } else {
  123. $connection_string = $driver.':host='.$host.';dbname='.$name;
  124. }
  125.  
  126. // add the port if one was specified
  127. if (!empty($port)) {
  128. $connection_string .= ";port=$port";
  129. }
  130.  
  131. // initialize the PDO object
  132. $new_connection = new PDO($connection_string, $user, $password);
  133.  
  134. // set the error mode
  135. $new_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  136. $new_connection->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
  137. $new_connection->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
  138.  
  139. $new_connection->exec('SET CHARACTER SET utf8');
  140. $new_connection->exec('SET NAMES utf8mb4');
  141.  
  142. // return the new connection
  143. return $new_connection;
  144. }
  145.  
  146. // handle any exceptions by catching them and returning false
  147. catch (PDOException $e) {
  148. throw $e;
  149. }
  150. catch(Exception $e) {
  151. throw $e;
  152. }
  153. }
  154.  
  155. /**
  156. * method get.
  157. * - grab the PDO connection to the DB
  158. */
  159. protected function get() {
  160.  
  161. // if we have not created the db connection yet, create it now
  162. if (!isset($this->pdo)) {
  163. $this->pdo = $this->createConnection(
  164. $this->config['driver'],
  165. $this->config['host'],
  166. $this->config['name'],
  167. $this->config['user'],
  168. $this->config['password'],
  169. $this->config['port']
  170. );
  171. }
  172.  
  173. return $this->pdo;
  174. }
  175.  
  176. /**
  177. * Получение количества элементов в таблице
  178. * @param string $table имя таблицы
  179. * @param array $params массив условий
  180. * @return int количество записей
  181. * @throws Exception
  182. */
  183. public function count($table, $params = null) {
  184. $sql_str = "SELECT count(*) FROM $table";
  185.  
  186. $sql_str .= ( count($params)>0 ? ' WHERE ' : '' );
  187.  
  188. $add_and = false;
  189. // add each clause using parameter array
  190. if (empty($params)) {
  191. $params = [];
  192. }
  193. foreach ($params as $key=>$val) {
  194. // only add AND after the first clause item has been appended
  195. if ($add_and) {
  196. $sql_str .= ' AND ';
  197. } else {
  198. $add_and = true;
  199. }
  200.  
  201. // append clause item
  202. if (is_array($val)) {
  203. $sql_str .= $key.current($val).":$key";
  204. } else {
  205. $sql_str .= "$key = :$key";
  206. }
  207. }
  208.  
  209. // now we attempt to retrieve the row using the sql string
  210. try {
  211.  
  212. $pstmt = $this->get()->prepare($sql_str);
  213.  
  214. // bind each parameter in the array
  215. foreach ($params as $key=>$val) {
  216. if (is_array($val)) {
  217. $val = end($val);
  218. }
  219. $pstmt->bindValue(':'.$key, $val);
  220. }
  221.  
  222. $pstmt->execute();
  223.  
  224. return $pstmt->fetchColumn(0);
  225. }
  226. catch(PDOException $e) {
  227. throw $e;
  228. }
  229. catch(Exception $e) {
  230. throw $e;
  231. }
  232. }
  233.  
  234. /**
  235. * method select.
  236. * - retrieve information from the database, as an array
  237. *
  238. * @param string $table - the name of the db table we are retreiving the rows from
  239. * @param array $params - associative array representing the WHERE clause filters
  240. * @param int $limit (optional) - the amount of rows to return
  241. * @param int $start (optional) - the row to start on, indexed by zero
  242. * @param array $order_by (optional) - an array with order by clause
  243. * @return mixed - associate representing the fetched table row, false on failure
  244. * @throws Exception
  245. */
  246. public function select($table, $params = null, $limit = null, $start = null, $order_by = null) {
  247. // building query string
  248. $sql_str = "SELECT * FROM $table";
  249. // append WHERE if necessary
  250. $sql_str .= ( count($params)>0 ? ' WHERE ' : '' );
  251.  
  252. $add_and = false;
  253. // add each clause using parameter array
  254. if (empty($params)) {
  255. $params = [];
  256. }
  257. foreach ($params as $key=>$val) {
  258. // only add AND after the first clause item has been appended
  259. if ($add_and) {
  260. $sql_str .= ' AND ';
  261. } else {
  262. $add_and = true;
  263. }
  264.  
  265. // append clause item
  266. if (is_array($val)) {
  267. $sql_str .= $key.current($val).":$key";
  268. } else {
  269. $sql_str .= "$key=:$key";
  270. }
  271. }
  272.  
  273. // add the order by clause if we have one
  274. if (!empty($order_by)) {
  275. $sql_str .= ' ORDER BY';
  276. $add_comma = false;
  277. foreach ($order_by as $column => $order) {
  278. if ($add_comma) {
  279. $sql_str .= ', ';
  280. }
  281. else {
  282. $add_comma = true;
  283. }
  284. $sql_str .= " $column $order";
  285. }
  286. }
  287.  
  288. // now we attempt to retrieve the row using the sql string
  289. try {
  290. // decide which database we are selecting from
  291. $pdoDriver = $this->get()->getAttribute(PDO::ATTR_DRIVER_NAME);
  292.  
  293. //@TODO MS SQL Server & Oracle handle LIMITs differently, for now its disabled but we should address it later.
  294. $disableLimit = ["sqlsrv", "mssql", "oci"];
  295.  
  296. // add the limit clause if we have one
  297. if (!is_null($limit) && !in_array($pdoDriver, $disableLimit)) {
  298. $sql_str .= ' LIMIT '.(!is_null($start) ? "$start, ": '')."$limit";
  299. }
  300.  
  301. $pstmt = $this->get()->prepare($sql_str);
  302.  
  303. // bind each parameter in the array
  304. foreach ($params as $key=>$val) {
  305. if (is_array($val)) {
  306. $val = end($val);
  307. }
  308. $pstmt->bindValue(':'.$key, $val);
  309. }
  310.  
  311. $pstmt->execute();
  312.  
  313. // now return the results, depending on if we want all or first row only
  314. if (! is_null($limit) && $limit == 1) {
  315. return $pstmt->fetch(PDO::FETCH_ASSOC);
  316. } else {
  317. return $pstmt->fetchAll(PDO::FETCH_ASSOC);
  318. }
  319. }
  320. catch(PDOException $e) {
  321. throw $e;
  322. }
  323. catch(Exception $e) {
  324. throw $e;
  325. }
  326. }
  327.  
  328. /**
  329. * method selectFirst.
  330. * - retrieve the first row returned from a select statement
  331. *
  332. * @param table - the name of the db table we are retreiving the rows from
  333. * @param array $params
  334. * @param array $order_by (optional) - an array with order by clause
  335. * @return mixed - associate representing the fetched table row, false on failure
  336. */
  337. public function selectFirst($table, $params = [], $order_by = null) {
  338. return $this->select($table, $params, 1, null, $order_by);
  339. }
  340.  
  341. /**
  342. * method delete.
  343. * - deletes rows from a table based on the parameters
  344. *
  345. * @param table - the name of the db table we are deleting the rows from
  346. * @param array $params
  347. * @return bool - associate representing the fetched table row, false on failure
  348. * @throws Exception
  349. */
  350. public function delete($table, $params = []) {
  351. // building query string
  352. $sql_str = "DELETE FROM $table";
  353. // append WHERE if necessary
  354. $sql_str .= count($params)>0 ? ' WHERE ' : '';
  355.  
  356. $add_and = false;
  357. // add each clause using parameter array
  358. foreach ($params as $key=>$val) {
  359. // only add AND after the first clause item has been appended
  360. if ($add_and) {
  361. $sql_str .= ' AND ';
  362. } else {
  363. $add_and = true;
  364. }
  365.  
  366. // append clause item
  367. if (is_array($val)) {
  368. $sql_str .= "$key".current($val).":$key";
  369. } else {
  370. $sql_str .= "$key=:$key";
  371. }
  372. }
  373.  
  374. // now we attempt to retrieve the row using the sql string
  375. try {
  376. $pstmt = $this->get()->prepare($sql_str);
  377.  
  378. // bind each parameter in the array
  379. foreach ($params as $key=>$val) {
  380. if (is_array($val)) {
  381. $val = end($val);
  382. }
  383. $pstmt->bindValue(':'.$key, $val);
  384. }
  385.  
  386. // execute the delete query
  387. $successful_delete = $pstmt->execute();
  388.  
  389. // if we were successful, return the amount of rows updated, otherwise return false
  390. return ($successful_delete == true) ? $pstmt->rowCount() : false;
  391. }
  392. catch(PDOException $e) {
  393. throw $e;
  394. }
  395. catch(Exception $e) {
  396. throw $e;
  397. }
  398. }
  399.  
  400. /**
  401. * method update.
  402. * - updates a row to the specified table
  403. *
  404. * @param string $table - the name of the db table we are adding row to
  405. * @param array $params - associative array representing the columns and their respective values to update
  406. * @param array $wheres (Optional) - the where clause of the query
  407. * @param bool $timestamp_this (Optional) - if true we set created and modified values to now
  408. * @return bool|int - the amount of rows updated, false on failure
  409. * @throws Exception
  410. */
  411. public function update($table, $params, $wheres = [], $timestamp_this = false) {
  412. if (! $timestamp_this) {
  413. $timestamp_this = self::$timestamp_writes;
  414. }
  415. // build the set part of the update query by
  416. // adding each parameter into the set query string
  417. $add_comma = false;
  418. $set_string = '';
  419. foreach ($params as $key=>$val) {
  420. // only add comma after the first parameter has been appended
  421. if ($add_comma) {
  422. $set_string .= ', ';
  423. } else {
  424. $add_comma = true;
  425. }
  426.  
  427. // now append the parameter
  428. if (is_array($val)) {
  429. $set_string .= "$key=$key".implode($val);
  430. } else {
  431. $set_string .= "$key=:param_$key";
  432. }
  433. }
  434.  
  435. // add the timestamp columns if neccessary
  436. if ($timestamp_this) {
  437. $set_string .= ($add_comma ? ', ' : '') . 'modified='.time();
  438. }
  439.  
  440. // lets add our where clause if we have one
  441. $where_string = '';
  442. if (!empty($wheres)) {
  443. // load each key value pair, and implode them with an AND
  444. $where_array = [];
  445. foreach($wheres as $key => $val) {
  446.  
  447. // append clause item
  448. if (is_array($val)) {
  449. $where_array[] = $key.current($val).":where_$key";
  450. } else {
  451. $where_array[] = "$key=:where_$key";
  452. }
  453. }
  454. // build the final where string
  455. $where_string = 'WHERE '.implode(' AND ', $where_array);
  456. }
  457.  
  458. // build final update string
  459. $sql_str = "UPDATE $table SET $set_string $where_string";
  460.  
  461. // now we attempt to write this row into the database
  462. try {
  463. $pstmt = $this->get()->prepare($sql_str);
  464.  
  465. // bind each parameter in the array
  466. foreach ($params as $key=>$val) {
  467. if (is_array($val)) continue;
  468. $pstmt->bindValue(':param_'.$key, $val);
  469. }
  470.  
  471. // bind each where item in the array
  472. foreach ($wheres as $key=>$val) {
  473. if (is_array($val)) {
  474. $val = end($val);
  475. }
  476. $pstmt->bindValue(':where_'.$key, $val);
  477. }
  478.  
  479. // execute the update query
  480. $successful_update = $pstmt->execute();
  481.  
  482. // if we were successful, return the amount of rows updated, otherwise return false
  483. return ($successful_update == true) ? $pstmt->rowCount() : false;
  484. }
  485. catch(PDOException $e) {
  486. throw $e;
  487. }
  488. catch(Exception $e) {
  489. throw $e;
  490. }
  491. }
  492.  
  493. /**
  494. * method insert.
  495. * - adds a row to the specified table
  496. *
  497. * @param string $table - the name of the db table we are adding row to
  498. * @param array $params - associative array representing the columns and their respective values
  499. * @param bool $timestamp_this (Optional), if true we set created and modified values to now
  500. * @return mixed - new primary key of inserted table, false on failure
  501. * @throws Exception
  502. */
  503. public function insert($table, $params = [], $timestamp_this = false) {
  504. if (! $timestamp_this) {
  505. $timestamp_this = self::$timestamp_writes;
  506. }
  507.  
  508. // first we build the sql query string
  509. $columns_str = '(';
  510. $values_str = 'VALUES (';
  511. $add_comma = false;
  512.  
  513. // add each parameter into the query string
  514. foreach ($params as $key=>$val) {
  515. // only add comma after the first parameter has been appended
  516. if ($add_comma) {
  517. $columns_str .= ', ';
  518. $values_str .= ', ';
  519. } else {
  520. $add_comma = true;
  521. }
  522.  
  523. // now append the parameter
  524. $columns_str .= "$key";
  525. $values_str .= ":$key";
  526. }
  527.  
  528. // add the timestamp columns if neccessary
  529. if ($timestamp_this) {
  530. $columns_str .= ($add_comma ? ', ' : '') . 'created, modified';
  531. $values_str .= ($add_comma ? ', ' : '') . time().', '.time();
  532. }
  533.  
  534. // close the builder strings
  535. $columns_str .= ') ';
  536. $values_str .= ')';
  537.  
  538. // build final insert string
  539. $sql_str = "INSERT INTO $table $columns_str $values_str";
  540.  
  541. // now we attempt to write this row into the database
  542. try {
  543. $pstmt = $this->get()->prepare($sql_str);
  544.  
  545. // bind each parameter in the array
  546. foreach ($params as $key=>$val) {
  547. $pstmt->bindValue(':'.$key, $val);
  548. }
  549.  
  550. $pstmt->execute();
  551. $newID = $this->get()->lastInsertId();
  552.  
  553. // return the new id
  554. return $newID;
  555. }
  556. catch(PDOException $e) {
  557. throw $e;
  558. }
  559. catch(Exception $e) {
  560. throw $e;
  561. }
  562. }
  563.  
  564. /**
  565. * method insertMultiple.
  566. * - adds multiple rows to a table with a single query
  567. *
  568. * @param string $table - the name of the db table we are adding row to
  569. * @param array $columns - contains the column names
  570. * @param array $rows
  571. * @param bool $timestamp_these (Optional), if true we set created and modified values to NOW() for each row
  572. * @return mixed - new primary key of inserted table, false on failure
  573. * @throws Exception
  574. */
  575. public function insertMultiple($table, $columns = [], $rows = [], $timestamp_these = false) {
  576. if (! $timestamp_these) {
  577. $timestamp_these = self::$timestamp_writes;
  578. }
  579.  
  580. // generate the columns portion of the insert statment
  581. // adding the timestamp fields if needs be
  582. if ($timestamp_these) {
  583. $columns[] = 'created';
  584. $columns[] = 'modified';
  585. }
  586. $columns_str = '(' . implode(',', $columns) . ') ';
  587.  
  588. // generate the values portions of the string
  589. $values_str = 'VALUES ';
  590. $add_comma = false;
  591.  
  592. foreach ($rows as $row_index => $row_values) {
  593. // only add comma after the first row has been added
  594. if ($add_comma) {
  595. $values_str .= ', ';
  596. } else {
  597. $add_comma = true;
  598. }
  599.  
  600. // here we will create the values string for a single row
  601. $values_str .= '(';
  602. $add_comma_forvalue = false;
  603. foreach ($row_values as $value_index => $value) {
  604. if ($add_comma_forvalue) {
  605. $values_str .= ', ';
  606. } else {
  607. $add_comma_forvalue = true;
  608. }
  609. // generate the bind variable name based on the row and column index
  610. $values_str .= ':'.$row_index.'_'.$value_index;
  611. }
  612. // append timestamps if necessary
  613. if ($timestamp_these) {
  614. $values_str .= ($add_comma_forvalue ? ', ' : '') . time().', '.time();
  615. }
  616. $values_str .= ')';
  617. }
  618.  
  619. // build final insert string
  620. $sql_str = "INSERT INTO $table $columns_str $values_str";
  621.  
  622. // now we attempt to write this multi inster query to the database using a transaction
  623. try {
  624. $this->get()->beginTransaction();
  625. $pstmt = $this->get()->prepare($sql_str);
  626.  
  627. // traverse the 2d array of rows and values to bind all parameters
  628. foreach ($rows as $row_index => $row_values) {
  629. foreach ($row_values as $value_index => $value) {
  630. $pstmt->bindValue(':'.$row_index.'_'.$value_index, $value);
  631. }
  632. }
  633.  
  634. // now lets execute the statement, commit the transaction and return
  635. $pstmt->execute();
  636. $this->get()->commit();
  637. return true;
  638. }
  639. catch(PDOException $e) {
  640. $this->get()->rollBack();
  641. throw $e;
  642. }
  643. catch(Exception $e) {
  644. $this->get()->rollBack();
  645. throw $e;
  646. }
  647. }
  648.  
  649. /**
  650. * method execute.
  651. * - executes a query that modifies the database
  652. *
  653. * @param string $query - the SQL query we are executing
  654. * @param array $params
  655. * @return mixed - the affected rows, false on failure
  656. * @throws Exception
  657. */
  658. public function execute($query, $params = []) {
  659. try {
  660. // prepare the statement
  661. $pstmt = $this->get()->prepare($query);
  662.  
  663. // bind each parameter in the array
  664. foreach ((array)$params as $key=>$val) {
  665. $pstmt->bindValue(':'.$key, $val, is_int($val) ? PDO::PARAM_INT : PDO::PARAM_STR);
  666. }
  667.  
  668. // execute the query
  669. $result = $pstmt->execute();
  670.  
  671. // only if return value is false did this query fail
  672. return ($result == true) ? $pstmt->rowCount() : false;
  673. }
  674. catch(PDOException $e) {
  675. throw $e;
  676. }
  677. catch(Exception $e) {
  678. throw $e;
  679. }
  680. }
  681.  
  682. /**
  683. * method query.
  684. * - returns data from a free form select query
  685. *
  686. * @param string $query - the SQL query we are executing
  687. * @param array $params - a list of bind parameters
  688. * @return mixed - the affected rows, false on failure
  689. * @throws Exception
  690. */
  691. public function query($query, $params = []) {
  692. try {
  693.  
  694. $pstmt = $this->get()->prepare($query);
  695.  
  696. // bind each parameter in the array
  697. foreach ((array)$params as $key=>$val) {
  698. $pstmt->bindValue(':'.$key, $val, is_int($val) ? PDO::PARAM_INT : PDO::PARAM_STR);
  699. }
  700.  
  701. // execute the query
  702. $pstmt->execute();
  703.  
  704. // now return the results
  705. return $pstmt->fetchAll(PDO::FETCH_ASSOC);
  706. }
  707. catch(PDOException $e) {
  708. throw $e;
  709. }
  710. catch(Exception $e) {
  711. throw $e;
  712. }
  713. }
  714.  
  715. /**
  716. * method queryFirst.
  717. * - returns the first record from a free form select query
  718. *
  719. * @param string $query - the SQL query we are executing
  720. * @param array $params - a list of bind parameters
  721. * @return mixed - the affected rows, false on failure
  722. */
  723. public function queryFirst($query, $params = []) {
  724. $result = $this->query($query, $params);
  725. if (empty($result)) {
  726. return false;
  727. }
  728. else {
  729. return $result[0];
  730. }
  731. }
  732.  
  733. /**
  734. * Validate the database in question is supported by your installation of PHP.
  735. * @param string $driver The DSN prefix
  736. * @return boolean true, the database is supported; false, the database is not supported.
  737. */
  738. private function validateDriver($driver) {
  739. if (!in_array($driver, PDO::getAvailableDrivers())) {
  740. return false;
  741. } else {
  742. return true;
  743. }
  744. }
  745.  
  746. /**
  747. * Destructor.
  748. * - release the PDO db connections
  749. */
  750. function __destruct() {
  751. unset($this->pdo);
  752. }
  753. }