DaniWeb IT Discussion Community

Code Snippets (http://www.daniweb.com/code/)
-   php (http://www.daniweb.com/code/php.html)
-   -   PHP Database Class with Caching (http://www.daniweb.com/code/snippet289.html)

Troy php syntax
Jun 11th, 2005
class_db.php is a database class that provides methods to work with mysql, postgres, and mssql databases. Other database types can be easily added. The class provides a common interface to the various database types. A powerful feature of the class is the ability to cache datasets to disk using a Time-To-Live parameter. This can eliminate a lot of unneccessary hits to your database! Also, a database connection is not created unless and until needed, thus saving precious database connection resources. Most current information and documentation found at http://www.troywolf.com/articles/php/class_db

Troy Wolf operates ShinySolutions Webhosting, and is the author of SnippetEdit--a PHP application providing browser-based website editing that even non-technical people can use. "Website editing as easy as it gets." Troy has been a professional Internet and database application developer for over 10 years. He has many years' experience with ASP, VBScript, PHP, Javascript, DHTML, CSS, SQL, and XML on Windows and Linux platforms.

  1. db_class.php:
  2. =======================================================
  3. <?php
  4. /*
  5. * Filename.......: class_db.php
  6. * Author.........: Troy Wolf [troy@troywolf.com]
  7. * Last Modified..: Date: 2005/06/19 16:42:00
  8. * Description....: A database class that provides methods to work with mysql,
  9.   postgres, and mssql databases. The class provides a common
  10.   interface to the various database types. A powerful
  11.   feature of the class is the ability to cache datasets to disk
  12.   using a Time-To-Live parameter. This can eliminate a lot of
  13.   unneccessary hits to your database! Also, a database
  14.   connection is not created unless and until needed.
  15. */
  16. class db {
  17. var $cnn_id;
  18. var $db_type;
  19. var $dir;
  20. var $name;
  21. var $filename;
  22. var $fso;
  23. var $data;
  24. var $sql;
  25. var $cnn;
  26. var $db;
  27. var $res;
  28. var $ttl;
  29. var $data_ts;
  30. var $server;
  31. var $log;
  32.  
  33. /*
  34.   The class constructor. You can set some defaults here if desired.
  35.   */
  36. function db($cnn_id=0) {
  37. $this->log = "initialize db() called<br />";
  38. $this->cnn_id = $cnn_id;
  39. $this->dir = realpath("./")."/"; //Default to current dir.
  40. $this->ttl = 0;
  41. $this->data_ts = 0;
  42. }
  43.  
  44. /*
  45.   connect() method makes the actual server connection and selects a database
  46.   only if needed. This saves database connections. Multiple database types are
  47.   supported. Enter your connection credentials in the switch statement below.
  48.  
  49.   This is a private function, but it is at the top of the class because you need
  50.   to enter your connections.
  51.   */
  52. function connect() {
  53. $this->log .= "connect() called<br />";
  54. switch($this->cnn_id) {
  55. /*
  56.   You can define all the database connections you need in this
  57.   switch statement.
  58.   */
  59. case 0:
  60. $this->db_type = "mysql";
  61. $this->server = "";
  62. $user = "";
  63. $pwd = "";
  64. $this->db = "";
  65. break;
  66. case 1:
  67. $this->db_type = "mysql";
  68. $this->server = "";
  69. $user = "";
  70. $pwd = "";
  71. $this->db = "";
  72. break;
  73. case 2:
  74. $this->db_type = "postgres";
  75. $this->server = "";
  76. $user = "";
  77. $pwd = "";
  78. $this->db = "";
  79. break;
  80. case 3:
  81. $this->db_type = "mssql";
  82. $this->server = "";
  83. $user = "";
  84. $pwd = "";
  85. $this->db = "";
  86. break;
  87. }
  88. switch($this->db_type) {
  89. case "mysql":
  90. if (!$this->cnn = mysql_connect($this->server,$user,$pwd )) {
  91. $this->log .= "mysql_connect() failed<br />";
  92. $this->log .= mysql_error()."<br />";
  93. return false;
  94. }
  95. if (!mysql_select_db($this->db,$this->cnn)) {
  96. $this->log .= "Could not select database named ".$this->db."<br />";
  97. $this->log .= mysql_error()."<br />";
  98. return false;
  99. }
  100. break;
  101. case "postgres":
  102. if (!$this->cnn = pg_connect("host=$this->server dbname=$this->dbuser=$user password=$pwd")) {
  103. $this->log .= "pg_connect() failed<br />";
  104. $this->log .= pg_last_error()."<br />";
  105. return false;
  106. }
  107. break;
  108. case "mssql":
  109. if (!$this->cnn = mssql_connect($this->server,$user,$pwd )) {
  110. $this->log .= "mssql_connect() failed<br />";
  111. $this->log .= mssql_error()."<br />";
  112. return false;
  113. }
  114. if (!mssql_select_db($this->db,$this->cnn)) {
  115. $this->log .= "Could not select database named ".$this->db."<br />";
  116. $this->log .= mssql_error()."<br />";
  117. return false;
  118. }
  119. break;
  120. }
  121. return true;
  122. }
  123.  
  124. /*
  125.   fetch() is used to retrieve a dataaset. fetch() determines whether to use the
  126.   cache or not, and queries either the database or the cache file accordingly.
  127.   */
  128. function fetch() {
  129. $this->log .= "---------------------------------<br />fetch() called<br />";
  130. $this->log .= "SQL: ".$this->sql."<br />";
  131. $this->data = "";
  132. if ($this->ttl == "0") {
  133. return $this->getFromDB();
  134. } else {
  135. $this->filename = $this->dir."db_".$this->name;
  136. $this->getFile_ts();
  137. if ($this->ttl == "daily") {
  138. if (date('Y-m-d',$this->data_ts) != date('Y-m-d',time())) {
  139. $this->log .= "cache has expired<br />";
  140. if ($this->getFromDB()) { return $this->saveToCache(); }
  141. } else {
  142. return $this->getFromCache();
  143. }
  144. } else {
  145. if ((time() - $this->data_ts) >= $this->ttl) {
  146. $this->log .= "cache has expired<br />";
  147. if ($this->getFromDB()) { return $this->saveToCache(); }
  148. } else {
  149. return $this->getFromCache();
  150. }
  151. }
  152. }
  153. }
  154.  
  155. /*
  156.   Use exec() to execute INSERT, UPDATE, DELETE statements.
  157.   */
  158. function exec() {
  159. $this->log .= "exec() called<br />";
  160. $this->log .= "SQL: ".$this->sql."<br />";
  161. if (!$this->cnn) { if (!$this->connect()) { return false; } }
  162. switch($this->db_type) {
  163. case "mysql":
  164. if (!$res = @mysql_query($this->sql, $this->cnn)) {
  165. $this->log .= "Query execution failed.<br />";
  166. $this->log .= mysql_error()."<br />";
  167. return false;
  168. }
  169. break;
  170. case "postgres":
  171. if (!$this->res = @pg_query($this->cnn, $this->sql)) {
  172. $this->log .= "Query execution failed.<br />";
  173. $this->log .= pg_last_error()."<br />";
  174. return false;
  175. }
  176. break;
  177. case "mssql":
  178. if (!$res = @mssql_query($this->sql, $this->cnn)) {
  179. $this->log .= "Query execution failed.<br />";
  180. $this->log .= mssql_error()."<br />";
  181. return false;
  182. }
  183. break;
  184. }
  185. return true;
  186. }
  187.  
  188. /*
  189.   rows_affected() returns number of rows affected by INSERT, UPDATE, DELETE.
  190.   $rows_affected = $objName->rows_affected();
  191.   */
  192. function rows_affected() {
  193. $this->log .= "rows_affected() called<br />";
  194. if (!$this->cnn) {
  195. $this->log .= "rows_affected(): database connection does not exist.<br />";
  196. return false;
  197. }
  198. switch($this->db_type) {
  199. case "mysql":
  200. return mysql_affected_rows($this->cnn);
  201. case "postgres":
  202. return pg_affected_rows($this->res);
  203. case "mssql":
  204. return mssql_rows_affected($this->cnn);
  205. }
  206. return false;
  207. }
  208.  
  209. /*
  210.   last_id() returns newly inserted identity or autonumber from last INSERT.
  211.   Of course, this is only applicable if your table has an autonumber column.
  212.   $last_id = $objName->last_id();
  213.   */
  214. function last_id() {
  215. $this->log .= "last_id() called<br />";
  216. if (!$this->cnn) {
  217. $this->log .= "last_id(): database connection does not exist.<br />";
  218. return false;
  219. }
  220. switch($this->db_type) {
  221. case "mysql":
  222. return mysql_insert_id();
  223. break;
  224. case "postgres":
  225. return pg_last_oid($this->res);
  226. break;
  227. case "mssql":
  228. if (!$res = @mysql_query("select SCOPE_IDENTITY()")) {
  229. $this->log .= "Failed to retrieve identity value.<br />";
  230. $this->log .= mssql_error()."<br />";
  231. return false;
  232. }
  233. if (!$identity = @mssql_result($res,0,0)) {
  234. $this->log .= "Failed to retrieve identity value.<br />";
  235. $this->log .= mssql_error()."<br />";
  236. return false;
  237. }
  238. return $identity;
  239. break;
  240. }
  241. return false;
  242. }
  243.  
  244. /*
  245.   fmt() is a helper function for formatting SQL statement strings.
  246.   For strings values, it will escape embedded single ticks, replace emptry
  247.   strings with 'NULL', and properly wrap the value in quotes. For numeric types,
  248.   it will replace empty values with zero.
  249.   val : value to format
  250.   dtype : 0 = string, 1 = numeric
  251.   */
  252. function fmt($val,$dtype) {
  253. switch($dtype) {
  254. case 0:
  255. if(! $val && $val != "0") {
  256. $tmp = "null";
  257. } else {
  258. $tmp = "'".str_replace("'","''",$val)."'";
  259. }
  260. break;
  261. case 1:
  262. if(! $val) {
  263. $tmp = "0";
  264. } else {
  265. $tmp = $val;
  266. }
  267. break;
  268. }
  269. return $tmp;
  270. }
  271.  
  272. /*
  273.   fmt2() is the same as fmt() except it inserts a comma at the beginning
  274.   of the return value and a space at the end. Useful in building SQL statements
  275.   with multiple values.
  276.   */
  277. function fmt2($val,$dtype) {
  278. switch($dtype) {
  279. case 0:
  280. if(! $val && $val != "0") {
  281. $tmp = ",null ";
  282. } else {
  283. $tmp = ",'".str_replace("'","''",$val)."' ";
  284. }
  285. break;
  286. case 1:
  287. if(! $val) {
  288. $tmp = ",0";
  289. } else {
  290. $tmp = ",".$val." ";
  291. }
  292. break;
  293. }
  294. return $tmp;
  295. }
  296.  
  297. /*
  298.   dump() produces an HTML table of the data. It is useful for debugging.
  299.   This is also a good example of how to work with the data array.
  300.   */
  301. function dump() {
  302. $this->log .= "dump() called<br />";
  303. if (!$this->data) {
  304. $this->log .= "dump(): no rows exist<br />";
  305. return false;
  306. }
  307. echo "<style>table.dump { font-family:Arial; font-size:8pt; }</style>";
  308. echo "<table class=\"dump\" border=\"1\" cellpadding=\"1\" cellspacing=\"0\">\n";
  309. echo "<tr>";
  310. echo "<th>#</th>";
  311. foreach($this->data[0] as $key=>$val) {
  312. echo "<th><b>";
  313. echo $key;
  314. echo "</b></th>";
  315. }
  316. echo "</tr>\n";
  317. $row_cnt = 0;
  318. foreach($this->data as $row) {
  319. $row_cnt++;
  320. echo "<tr align='center'>";
  321. echo "<td>".$row_cnt."</td>";
  322. foreach($row as $val) {
  323. echo "<td>";
  324. echo $val;
  325. echo "</td>";
  326. }
  327. echo"</tr>\n";
  328. }
  329. echo "</table>\n";
  330. }
  331.  
  332. /*
  333.   PRIVATE FUNCTIONS BELOW THIS POINT
  334.   ------------------------------------------------------------------------------
  335.   */
  336.  
  337. function getFromDB() {
  338. $this->log .= "getFromDB() called<br />";
  339. if (!$this->cnn) {
  340. if (!$this->connect()) {
  341. $this->log .= "Database connection failed.<br />";
  342. return false;
  343. }
  344. }
  345. switch($this->db_type) {
  346. case "mysql":
  347. if (!$res = @mysql_query($this->sql, $this->cnn)) {
  348. $this->log .= "Query execution failed.<br />";
  349. $this->log .= mysql_error()."<br />";
  350. return false;
  351. }
  352. while ($row = mysql_fetch_array($res, MYSQL_ASSOC)) {
  353. $this->data[] = $row;
  354. }
  355. break;
  356. case "postgres":
  357. if (!$res = @pg_query($this->cnn, $this->sql)) {
  358. $this->log .= "Query execution failed.<br />";
  359. $this->log .= pg_last_error()."<br />";
  360. return false;
  361. }
  362. if (!$this->data = @pg_fetch_all($res)) {
  363. $this->log .= "getFromDB() failed<br />";
  364. return false;
  365. }
  366. break;
  367. case "mssql":
  368. if (!$res = @mssql_query($this->sql, $this->cnn)) {
  369. $this->log .= "Query execution failed.<br />";
  370. $this->log .= mssql_error()."<br />";
  371. return false;
  372. }
  373. while ($row = mssql_fetch_array($res)) {
  374. $this->data[] = $row;
  375. }
  376. break;
  377. }
  378. return true;
  379. }
  380.  
  381. function getFromCache() {
  382. $this->log .= "getFromCache() called<br />";
  383. if (!$x = @file_get_contents($this->filename)) {
  384. $this->log .= "Could not read ".$this->filename."<br />";
  385. return false;
  386. }
  387. if (!$this->data = unserialize($x)) {
  388. $this->log .= "getFromCache() failed<br />";
  389. return false;
  390. }
  391. return true;
  392. }
  393.  
  394. function saveToCache() {
  395. $this->log .= "saveToCache() called<br />";
  396.  
  397. //create file pointer
  398. if (!$fp=@fopen($this->filename,"w")) {
  399. $this->log .= "Could not open ".$this->filename."<br />";
  400. return false;
  401. }
  402. //write to file
  403. if (!@fwrite($fp,serialize($this->data))) {
  404. $this->log .= "Could not write to ".$this->filename."<br />";
  405. fclose($fp);
  406. return false;
  407. }
  408. //close file pointer
  409. fclose($fp);
  410. return true;
  411. }
  412.  
  413. function getFile_ts() {
  414. $this->log .= "getFile_ts() called<br />";
  415. if (!file_exists($this->filename)) {
  416. $this->data_ts = 0;
  417. $this->log .= $this->filename." does not exist<br />";
  418. return false;
  419. }
  420. $this->data_ts = filemtime($this->filename);
  421. return true;
  422. }
  423.  
  424. }
  425.  
  426. ?>
  427.  
  428.  
  429. example.php:
  430. =======================================================
  431. <?php
  432. /*
  433. * example.php
  434. * class_db.php example usage
  435. * Author: Troy Wolf (troy@troywolf.com)
  436. */
  437. /*
  438. Include the class. Modify path according to where you put the class file.
  439. */
  440. require_once(dirname(__FILE__).'/class_db.php');
  441.  
  442. /*
  443. Instantiate a new db object. If you have multiple databases to connect to
  444. or need to work with multiple datasets at the same time, you can create
  445. more instances. Within the class, you can define your connections. You can
  446. then pass an index to the constructor to select a specific connection. You
  447. can define multiple databases of the same type or different types. The default id is zero, so if you pass in nothing, the zero case will be used.
  448. In this example, we use the default connection.
  449. */
  450. $d = new db();
  451.  
  452. /*
  453. Where do you want to store your cache files?
  454. Default is current dir.
  455. */
  456. $d->dir = "/home/foo/bar/";
  457.  
  458. /*
  459. Execute a basic query. In this example, we've decided not to use caching.
  460. */
  461. $d->ttl = 0; //Time to live in seconds.
  462. $d->sql = "select * from users order by last_name";
  463. $d->fetch();
  464.  
  465. /*
  466. Execute a query, but this time, cache the data using the name "cars", and
  467. consider the cached data good for 5 minutes.
  468. */
  469. $d->cache_filename = "cars_less_100000";
  470. $d->ttl = 300;
  471. $d->sql = "select year, make, model, mileage from cars where mileage < 100000"
  472. ." order by mileage";
  473. $d->fetch();
  474.  
  475. /*
  476. The dump() method outputs a basic table of the data. This is useful for
  477. testing and debugging. Review the dump() method for an example of how to
  478. work with the dataset returned in the data array.
  479. */
  480. $d->dump();
  481.  
  482. /*
  483. Iterate through the rows in the data[] array created by fetch().
  484. */
  485. foreach($d->data as $row) {
  486. echo "<hr />Year: ".$row['year']
  487. ."<br />Make: ".$row['make']
  488. ."<br />Model: ".$row['model']
  489. ."<br />Mileage: ".formatnumber($row['mileage'],0);
  490. }
  491.  
  492. /*
  493. Access a specific column in a specific row.
  494. */
  495. echo "<hr />Data in the 'model' column of the 5th row: ".$d->data[4]['model'];
  496.  
  497. /*
  498. Use the static methods fmt() and fmt2() to help create your SQL statements.
  499. Read the comments in the class file for more detail.
  500. */
  501. $d->sql = "insert into cars (year,make,model,mileage) VALUES ("
  502. .db::fmt($year,0)
  503. .db::fmt2($make,0)
  504. .db::fmt2($model,0)
  505. .db::fmt2($mileage,1)
  506. .")";
  507.  
  508. /*
  509. Execute the query. You use the exec() method for INSERT, UPDATE, and DELETE
  510. queries.
  511. */
  512. if (!$d->exec()) {
  513. /*
  514.   There was a problem with the query! The class has a 'log' property that
  515.   contains a log of events. This log is useful for testing and debugging.
  516.   */
  517. echo "<h2>Query execution failed!</h2>";
  518. echo $d->log;
  519. exit();
  520. }
  521.  
  522. /*
  523. For INSERT,UPDATE,DELETE, you can access the rows_affected() method to get a
  524. count of affected rows.
  525. */
  526. echo $d->rows_affected()." rows affected<br />";
  527.  
  528. /*
  529. For INSERTs, if your table has an identity column or autonumber column, you can
  530. use the last_id() method to return the new id.
  531. */
  532. echo "New ID: ".$d->last_id()."<br />";
  533.  
  534. /*
  535. The log property contains a log of the objects events. Very useful for
  536. testing and debugging. If there are problems, the log will tell you what
  537. is wrong. For example, if the cache dir specified does not have write privs,
  538. the log will tell you it could not open the cache file. If there is an error
  539. in your sql statement, the log will tell you what it is.
  540. */
  541. echo "<h1>Log</h1>";
  542. echo $d->log;
  543. ?>
  544.