source: branches/eli_branch/lib/DB.inc.php @ 439

Last change on this file since 439 was 439, checked in by anonymous, 11 years ago

added public and private keywords to all properties and methods, changed old classname constructor function to construct, removed more ?> closing tags

File size: 13.9 KB
Line 
1<?php
2/**
3 * The Strangecode Codebase - a general application development framework for PHP
4 * For details visit the project site: <http://trac.strangecode.com/codebase/>
5 * Copyright 2001-2012 Strangecode, LLC
6 *
7 * This file is part of The Strangecode Codebase.
8 *
9 * The Strangecode Codebase is free software: you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation, either version 3 of the License, or (at your option)
12 * any later version.
13 *
14 * The Strangecode Codebase is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * details.
18 *
19 * You should have received a copy of the GNU General Public License along with
20 * The Strangecode Codebase. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23/**
24 * DB.inc.php
25 *
26 * Very lightweight DB semi-abstraction layer. Mainly to catch errors with mysql_query, with some goodies.
27 *
28 * @author  Quinn Comendant <quinn@strangecode.com>
29 * @version 2.1
30 */
31
32class DB {
33
34    // If $db->connect has successfully opened a db connection.
35    private $_connected = false;
36
37    // Database handle.
38    public $dbh;
39   
40    // Count how many queries run during the whole instance.
41    private $_query_count = 0;
42
43    // Hash of DB parameters.
44    private $_params = array();
45
46    // Default parameters.
47    private $_param_defaults = array(
48
49        // DB passwords should be set as apache environment variables in httpd.conf, readable only by root.
50        'db_server' => 'localhost',
51        'db_name' => null,
52        'db_user' => null,
53        'db_pass' => null,
54
55        // Display all SQL queries. FALSE recommended for production sites.
56        'db_always_debug' => false,
57
58        // Display db errors. FALSE recommended for production sites.
59        'db_debug' => false,
60       
61        // Script stops on db error. TRUE recommended for production sites.
62        'db_die_on_failure' => true,
63    );
64
65    // Translate between HTML and MySQL character set names.
66    public $mysql_character_sets = array(
67        'utf-8' => 'utf8',
68        'iso-8859-1' => 'latin1',
69    );
70
71    // Caches.
72    public $existing_tables;
73    public $table_columns;
74
75    /**
76     * This method enforces the singleton pattern for this class.
77     *
78     * @return  object  Reference to the global DB object.
79     * @access  public
80     * @static
81     */
82    public static function &getInstance()
83    {
84        static $instance = null;
85
86        if ($instance === null) {
87            $instance = new DB();
88        }
89
90        return $instance;
91    }
92
93    /**
94     * Set (or overwrite existing) parameters by passing an array of new parameters.
95     *
96     * @access public
97     *
98     * @param  array    $params     Array of parameters (key => val pairs).
99     */
100    public function setParam($params)
101    {
102        $app =& App::getInstance();
103   
104        if (isset($params) && is_array($params)) {
105            // Merge new parameters with old overriding only those passed.
106            $this->_params = array_merge($this->_params, $params);
107        } else {
108            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
109        }
110    }
111
112    /**
113     * Return the value of a parameter, if it exists.
114     *
115     * @access public
116     * @param string $param        Which parameter to return.
117     * @return mixed               Configured parameter value.
118     */
119    public function getParam($param)
120    {
121        $app =& App::getInstance();
122   
123        if (isset($this->_params[$param])) {
124            return $this->_params[$param];
125        } else {
126            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
127            return null;
128        }
129    }
130
131    /**
132     * Connect to database with credentials in params.
133     *
134     * @access  public
135     * @author  Quinn Comendant <quinn@strangecode.com>
136     * @since   28 Aug 2005 14:02:49
137     */
138    public function connect()
139    {
140        $app =& App::getInstance();
141   
142        if (!$this->getParam('db_name') || !$this->getParam('db_user') || !$this->getParam('db_pass')) {
143            $app->logMsg('Database credentials missing.', LOG_EMERG, __FILE__, __LINE__);
144            return false;
145        }
146
147        // Connect to database. Always create a new link to the server.
148        if ($this->dbh = mysql_connect($this->getParam('db_server'), $this->getParam('db_user'), $this->getParam('db_pass'), true)) {
149            // Select database
150            mysql_select_db($this->getParam('db_name'), $this->dbh);
151        }
152
153        // Test for connection errors.
154        if (!$this->dbh || mysql_error($this->dbh)) {
155            $mysql_error_msg = $this->dbh ? 'Codebase MySQL error: (' . mysql_errno($this->dbh) . ') ' . mysql_error($this->dbh) : 'Codebase MySQL error: Could not connect to server.';
156            $app->logMsg($mysql_error_msg, LOG_EMERG, __FILE__, __LINE__);
157
158            // Print helpful or pretty error?
159            if ($this->getParam('db_debug')) {
160                echo $mysql_error_msg . "\n";
161            }
162
163            // Die if db_die_on_failure = true, or just continue without connection.
164            return $this->_fail();
165        }
166
167        // DB connection success!
168        $this->_connected = true;
169
170        // Tell MySQL what character set we're using. Available only on MySQL versions > 4.01.01.
171        if ('' != $app->getParam('character_set') && isset($this->mysql_character_sets[mb_strtolower($app->getParam('character_set'))])) {
172            $this->query("/*!40101 SET NAMES '" . $this->mysql_character_sets[mb_strtolower($app->getParam('character_set'))] . "' */");
173        } else {
174            $app->logMsg(sprintf('%s is not a known character_set.', $app->getParam('character_set')), LOG_ERR, __FILE__, __LINE__);
175        }
176
177        return true;
178    }
179
180    /**
181     * Close db connection.
182     *
183     * @access  public
184     * @author  Quinn Comendant <quinn@strangecode.com>
185     * @since   28 Aug 2005 14:32:01
186     */
187    public function close()
188    {
189        if (!$this->_connected) {
190            return false;
191        }
192        $this->_connected = false;
193        return mysql_close($this->dbh);
194    }
195   
196    /*
197    *
198    *
199    * @access   public
200    * @param   
201    * @return   
202    * @author   Quinn Comendant <quinn@strangecode.com>
203    * @version  1.0
204    * @since    03 Jul 2013 14:50:23
205    */
206    public function reconnect()
207    {
208        $this->close();
209        $this->connect();
210    }
211   
212    /*
213    * Die only if db_die_on_failure is true. This will be set to false for some cases
214    * when a database is not required for web app functionality.
215    *
216    * @access   public
217    * @param    string  $msg Print $msg when dying.
218    * @return   bool    false If we don't die.
219    * @author   Quinn Comendant <quinn@strangecode.com>
220    * @version  1.0
221    * @since    15 Jan 2007 15:59:00
222    */
223    private function _fail()
224    {
225        if ($this->getParam('db_die_on_failure')) {
226            header(' ', true, 503);
227            echo _("This page is temporarily unavailable. Please try again in a few minutes.");
228            die;
229        } else {
230            return false;
231        }
232    }
233
234    /**
235     * Return the current database handler.
236     *
237     * @access  public
238     * @return  resource Current value of $this->dbh.
239     * @author  Quinn Comendant <quinn@strangecode.com>
240     * @since   20 Aug 2005 13:50:36
241     */
242    public function getDBH()
243    {
244        if (!$this->_connected) {
245            return false;
246        }
247
248        return $this->dbh;
249    }
250
251    /**
252     * Returns connection status
253     *
254     * @access  public
255     * @author  Quinn Comendant <quinn@strangecode.com>
256     * @since   28 Aug 2005 14:58:09
257     */
258    public function isConnected()
259    {
260        return (true === $this->_connected);
261    }
262   
263    /**
264     * Returns a properly escaped string using mysql_real_escape_string() with the current connection's charset.
265     *
266     * @access  public
267     * @param   string  $string     Input string to be sent as SQL query.
268     * @return  string              Escaped string from mysql_real_escape_string()
269     * @author  Quinn Comendant <quinn@strangecode.com>
270     * @since   06 Mar 2006 16:41:32
271     */
272    public function escapeString($string)
273    {
274        if (!$this->_connected) {
275            return false;
276        }
277
278        return mysql_real_escape_string($string, $this->dbh);
279    }
280
281    /**
282     * A wrapper for mysql_query. Allows us to set the database link_identifier,
283     * to trap errors and ease debugging.
284     *
285     * @param  string  $query   The SQL query to execute
286     * @param  bool    $debug   If true, prints debugging info
287     * @return resource         Query identifier
288     */
289    public function query($query, $debug=false)
290    {   
291        $app =& App::getInstance();
292
293        if (!$this->_connected) {
294           return false;
295        }
296
297        $this->_query_count++;
298
299        $debugqry = preg_replace("/\n[\t ]+/", "\n", $query);
300        if ($this->getParam('db_always_debug') || $debug) {
301            echo "<!-- ----------------- Query $this->_query_count ---------------------\n$debugqry\n-->\n";
302        }
303       
304        // Ensure we have an active connection.
305        // If we continue on a dead connection we might experience a "MySQL server has gone away" error.
306        // http://dev.mysql.com/doc/refman/5.0/en/gone-away.html
307        if (!mysql_ping($this->dbh)) {
308            $app->logMsg(sprintf('MySQL ping failed; reconnecting
 ("%s")', truncate(trim($debugqry), 150)), LOG_NOTICE, __FILE__, __LINE__);
309            $this->reconnect();
310        }
311
312        // Execute!
313        $qid = mysql_query($query, $this->dbh);
314
315        // Error checking.
316        if (!$qid || mysql_error($this->dbh)) {
317            $app->logMsg(sprintf('MySQL error %s: %s in query: %s', mysql_errno($this->dbh), mysql_error($this->dbh), $debugqry), LOG_EMERG, __FILE__, __LINE__);
318            if ($this->getParam('db_debug')) {
319                echo '<pre style="padding:2em; background:#ddd; font:9px monaco;">' . wordwrap(mysql_error($this->dbh)) . '<hr>' . htmlspecialchars($debugqry) . '</pre>';
320            }           
321            // Die if db_die_on_failure = true, or just continue without connection
322            return $this->_fail();
323        }
324
325        return $qid;
326    }
327
328    /**
329     * Loads a list of tables in the current database into an array, and returns
330     * true if the requested table is found. Use this function to enable/disable
331     * functionality based upon the current available db tables or to dynamically
332     * create tables if missing.
333     *
334     * @param  string $table                The name of the table to search.
335     * @param  bool   $use_cached_results   Get fresh table info (in case DB changed).
336     * @return bool                         true if given $table exists.
337     */
338    public function tableExists($table, $use_cached_results=true)
339    {
340        $app =& App::getInstance();
341   
342        if (!$this->_connected) {
343            return false;
344        }
345
346        if (!isset($this->existing_tables) || !$use_cached_results) {
347            $this->existing_tables = array();
348            $qid = $this->query("SHOW TABLES");
349            while (list($row) = mysql_fetch_row($qid)) {
350                $this->existing_tables[] = $row;
351            }
352        }
353        if (in_array($table, $this->existing_tables)) {
354            return true;
355        } else {
356            $app->logMsg(sprintf('Nonexistent DB table: %s.%s', $this->getParam('db_name'), $table), LOG_ALERT, __FILE__, __LINE__);
357            return false;
358        }
359    }
360
361    /**
362     * Tests if the given array of columns exists in the specified table.
363     *
364     * @param  string $table                The name of the table to search.
365     * @param  array  $columns              An array of column names.
366     * @param  bool   $strict               Exact schema match, or are additional fields in the table okay?
367     * @param  bool   $use_cached_results   Get fresh table info (in case DB changed).
368     * @return bool                         true if column(s) exist.
369     */
370    public function columnExists($table, $columns, $strict=true, $use_cached_results=true)
371    {
372        if (!$this->_connected) {
373            return false;
374        }
375
376        // Ensure the table exists.
377        if (!$this->tableExists($table, $use_cached_results)) {
378            return false;
379        }
380
381        // For single-value columns.
382        if (!is_array($columns)) {
383            $columns = array($columns);
384        }
385
386        if (!isset($this->table_columns[$table]) || !$use_cached_results) {
387            // Populate and cache array of current columns for this table.
388            $this->table_columns[$table] = array();
389            $qid = $this->query("DESCRIBE $table");
390            while ($row = mysql_fetch_row($qid)) {
391                $this->table_columns[$table][] = $row[0];
392            }
393        }
394
395        if ($strict) {
396            // Do an exact comparison of table schemas.
397            sort($columns);
398            sort($this->table_columns[$table]);
399            return $this->table_columns[$table] == $columns;
400        } else {
401            // Only check that the specified columns are available in the table.
402            $match_columns = array_intersect($this->table_columns[$table], $columns);
403            sort($columns);
404            sort($match_columns);
405            return $match_columns == $columns;
406        }
407    }
408   
409    /*
410    * Return the total number of queries executed thus far.
411    *
412    * @access   public
413    * @return   int Number of queries
414    * @author   Quinn Comendant <quinn@strangecode.com>
415    * @version  1.0
416    * @since    15 Jun 2006 11:46:05
417    */
418    public function numQueries()
419    {
420        return $this->_query_count;
421    }
422
423    /**
424     * Reset cached items.
425     *
426     * @access  public
427     * @author  Quinn Comendant <quinn@strangecode.com>
428     * @since   28 Aug 2005 22:10:50
429     */
430    public function resetCache()
431    {
432        $this->existing_tables = null;
433        $this->table_columns = null;
434    }
435
436} // End.
437
Note: See TracBrowser for help on using the repository browser.