MySQL database wrapper for PHP

Overdue update: March 6th, 2009: new version available, not backwards-compatible with earlier version

One of the challenges in building a website is keeping up with things that go wrong. How will you know when a database query fails? Or when the MySQL server is unavailable, but the webserver isn’t?

The functionality I needed was for the website to send me an e-mail automatically when something went wrong with the database. The complete code can be downloaded here (ZIP file). It will also be posted in this article, however, I will post it in parts.

To begin, the class requires a settings file called settings.database.php, in the same folder as the class file itself. This file contains the database connection details:

1
2
3
4
5
6
<?php
$sDbServerName = "localhost";
$sDbDatabase   = "test";
$sDbUsername   = "root";
$sDbPassword   = "";
?>

You will need to modify these four variables to be able to connect to your database. The variable names are plain enough. You set the server to connect to, the name of the database to use, and the username and password to use when logging in to the MySQL-server.

Now let’s define the class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Database
{
	private $sErrorMail = "webmaster@example.com";
	private $rDb;
	private $rResult;
	private $sServerUrl;
	private $sDatabase;
	private $sUsername;
	private $sPassword;
	public $sQuery = "";
	public $iRows = 0;
	public $sError = "";
	public $iErrNo = 0;
	public $iRowId = 0;
	public $aResult = array();

Here’s what all the variables are used for:

Error reporting:

$sErrorMail
When a query failes, or it is not possible to connect to the database, an error message with debugging information is automatically sent to the e-mail address entered here.

Internal resources:

$rDb
This variable will store a reference to the database connection.
$rResult
This variable will store a reference to the raw data returned when a query is executed.

Private database connection details (not accessible from outside the class):

$sServerUrl
MySQL server hostname to connect to.
$sDatabase
Name of the database to use.
$sUsername
Username to log in with when connecting to the server.
$sPassword
Password to log in with when connecting to the server.

Public variables (accessible from outside the class):

$sQuery
The last query that was executed.
$iRows
The number of rows resulting from (or affected by) the last executed query.
$sError
Error message (if any) returned by the last query.
$iErrNo
The error number (if any) returned by the last query.
$iRowId
The primary key ID of the row inserted by the last query (if that was an INSERT query).
$aResult
An associative array containing all rows returned by the last query.

The constructor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
	public function __construct(
		$bPersist   = false,
		$sServerUrl = false,
		$sDatabase  = false,
		$sUsername  = false,
		$sPassword  = false
	)
	{
		if ($sServerUrl) $this->sServerUrl = $sServerUrl;
		if ($sDatabase)  $this->sDatabase  = $sDatabase;
		if ($sUsername)  $this->sUsername  = $sUsername;
		if ($sPassword)  $this->sPassword  = $sPassword;
 
		if (empty($this->sServerUrl))
		{
			include(dirname(__FILE__)."/settings.database.php");
			$this->sServerUrl = $sDbServerName;
			$this->sDatabase  = $sDbDatabase;
			$this->sUsername  = $sDbUsername;
			$this->sPassword  = $sDbPassword;
		}
 
		if ($bPersist)
		{
			$bIsConnected = @mysql_pconnect($this->sServerUrl, $this->sUsername, $this->sPassword);
 
			if (!$bIsConnected)
			{
				$bIsConnected = @mysql_connect($this->sServerUrl, $this->sUsername, $this->sPassword);
 
				if (!$bIsConnected)
				{
					$this->sError = @mysql_error();
					$this->iErrNo = @mysql_errno();
 
					@mail($this->sErrorMail, "[{$_SERVER['SERVER_NAME']}] Connection failure", "Failed to connect to the database:\n\n{$this->iErrNo}: {$this->sError}", "From: ErrorHandler <errors@example.com>\r\n");
					trigger_error("Failed to connect to the database (error #{$this->iErrNo})", E_USER_ERROR);
				}
				else
				{
					$this->rDb = $bIsConnected;
				}
			}
			else
			{
				$this->rDb = $bIsConnected;
			}
		}
		else
		{
			$bIsConnected = @mysql_connect($this->sServerUrl, $this->sUsername, $this->sPassword);
 
			if (!$bIsConnected)
			{
				$this->sError = @mysql_error();
				$this->iErrNo = @mysql_errno();
 
				@mail($this->sErrorMail, "[{$_SERVER['SERVER_NAME']}] Connection failure", "Failed to connect to the database:\n\n{$this->iErrNo}: {$this->sError}", "From: ErrorHandler <errors@example.com>\r\n");
				trigger_error("Failed to connect to the database (error #{$this->iErrNo})", E_USER_ERROR);
			}
			else
			{
				$this->rDb = $bIsConnected;
			}
		}
 
		$bIsReady = @mysql_select_db($this->sDatabase, $this->rDb);
 
		if (!$bIsReady)
		{
			// Set the errors
			$this->sError = @mysql_error();
			$this->iErrNo = @mysql_errno();
 
			@mail($this->sErrorMail, "[{$_SERVER['SERVER_NAME']}] Selection failure", "Failed to select the database:\n\n{$this->iErrNo}: {$this->sError}", "From: ErrorHandler <errors@example.com>\r\n");
			trigger_error("The database could not be opened (error #{$this->iErrNo})", E_USER_ERROR);
		}
	}

Here’s what it does. You call the class with all the arguments being optional. You can leave them out, if you want to connect to the database using the settings provided in the settings.database.php file. Or you can fill them in, as described above.

The function checks if a persistent connection is requested, and uses mysql_pconnect() or mysql_connect() accordingly. If that fails, and an e-mail address was given to send the error reports to, an e-mail message will be sent to that address containing the mysql_error() received. It will also terminate all script execution with the message "Failed to connect to the database."

If a connection was succesfully established, it will attempt to open the database using mysql_select_db(). If that fails, again, an error message is sent out and all script execution is terminated with the message "Failed to open the database."

That’s it. If you want something else, you’ll have to call a function on the database object. You could, for example, clean up a string so that it’s relatively safe to use in a query:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	function Clean(
		$sText = null
	)
	{
		if (ctype_digit($sText)) return "'{$sText}'";
		else if (empty($sText)) return "''";
		else if (!$sText || is_null($sText) || $sText == null) return "NULL";
 
		$bCouldEscape = @mysql_real_escape_string($sText, $this->rDb);
 
		if (!$bCouldEscape)
		{
			$sText = addslashes($sText);
		}
		else $sText = $bCouldEscape;
 
		return "'".$sText."'";
	}

If the string is numerical, it will be returned unchanged, as a string, including single quotes. This is to prevent 0 being returned as NULL or a blank string. Otherwise, if the string is in fact an empty string, an empty string, including single quotes, is returned. Or, if the string is a boolean false or a null value, the string NULL is returned, without quotes. MySQL should see that as a null value. For any other values, the string is escaped using mysql_real_escape_string(), which needs a MySQL-connection to work, so it could very well fail. If it does, the string will be escaped using addslashes(), which isn’t quite as good for database escaping. The escaped string will eventually be returned with a single quote in front and behind it, so if you use this function, be sure not to include those in your query.

Executing a query:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
	function Execute(
		$sQuery,
		$sFilename   = null,
		$iLineNumber = null,
		$bDebug      = false
	)
	{
		$this->rResult = null;
		$this->sQuery   = "";
		$this->iRows    = 0;
		$this->iRowId   = 0;
		$this->sError   = "";
		$this->iErrNo   = 0;
		$this->aResult  = array();
 
		if (empty($sFilename)) $sFilename     = $_SERVER['REQUEST_URI'];
		if (empty($iLineNumber)) $iLineNumber = "(??)";
 
		$this->sQuery = $sQuery;
 
		if ($bDebug) echo "<pre>{$sFilename}:{$iLineNumber}\n\n".__FILE__.":".(__LINE__ - 3)."\n\n{$sQuery}\n\n";
 
		$bIsExecuted = @mysql_query($sQuery, $this->rDb);
 
		if (!$bIsExecuted)
		{
			$this->sError = @mysql_error($this->rDb);
			$this->iErrNo = @mysql_errno($this->rDb);
 
			@mail($this->sErrorMail, "[{$_SERVER['SERVER_NAME']}] Query failure", "Failed to properly execute a query in {$sFilename} on line {$iLineNumber}.\n\n{$sQuery}\n\n{$this->iErrNo}: {$this->sError}", "From: ErrorHandler <errors@example.com>\r\n");
 
			if ($bDebug)
			{
				die($this->iErrNo.": ".$this->sError."\n\n</p"."re>");
			}
		}
		else
		{
			$this->rResult = $bIsExecuted;
			$this->iRows = 0;
			$type = 0;
 
			if (($this->iRows = @mysql_num_rows($this->rResult))) { $type = 1; }
			else if (($this->iRows = @mysql_affected_rows($this->rDb))) { $type = 2; }
 
			$this->iRowId = 0;
			if (($this->iRowId = @mysql_insert_id($this->rDb))) {}
 
			if ($this->iRows > 0 && $type == 1)
			{
				while ($aResult = @mysql_fetch_assoc($this->rResult))
				{
					$this->aResult[] = $aResult;
				}
			}
 
			if ($bDebug)
			{
				die(print_r($this->aResult, true)."\n\n</p"."re>");
			}
		}
	}

When calling this function, $sQuery is required, but $sFileName, $iLineNumber and $bDebug are optional. The latter three are very handy for debugging purposes, however, I recommend not bothering to actually keep up with the filename and line number, because you’ll probably edit the files a lot, and queries get shifted around. Fortunately, PHP comes with several built-in constants, including __FILE__ and __LINE__, which always refer to the filename and line number from which they were called. Use those.

This function first clears any previously stored results. Then it determines the current file from the requested URL. Next it stores the query, and outputs some details if the query is to be executed in debug mode. Note: In debug mode, queries ARE executed. Be careful when debugging database altering queries, like INSERT, UPDATE, DELETE, ALTER, CREATE and cohorts.

After trying to execute the query, if it failed, the error will be stored, mailed, and outputted (if in debug mode). If the query was succesfully executed, the number of rows and, if applicable, the auto incremented id will be stored. After that, the results will be stored in the array. In debug mode, that array will be printed out and script execution will be terminated.

Changing the error reporting e-mail address at runtime:

1
2
3
4
	public function setErrorMail($sEmail)
	{
		$this->sErrorMail = $sEmail;
	}

It is possible to change the error message recipient during runtime. This is useful when, for example, an application has been built by one programmer, and another one is jumping in to add or modify a feature. If a global database object is used in the project, this will allow the new developer to make sure his code will send errors to him, instead of to the original developer.

Finding out the current error reporting e-mail address:

1
2
3
4
5
6
	public function getErrorMail()
	{
		return $this->sErrorMail;
	}
}
?>

Using this function, it is possible to find out what e-mail address is currently being used to deliver error reports to. Very useful if the setErrorMail() function is used a lot.

At this time, those are all the functions available in the class. Here’s a few examples on how to implement it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
require_once("class.Database.php");
$oDb = new Database();
 
// Get the first 5 rows of mycolumn1 and mycolumn2 from mytable
$oDb->Execute("
	SELECT mycolumn1,
	       mycolumn2
	FROM   mytable
	LIMIT  0,5
", __FILE__, __LINE__);
 
foreach ($oDb->aResult as $iRowNo => $aRow)
{
	echo "<strong>Row ".$iRowNo.":</strong><br />n";
	echo "MyColumn1: ".$aRow['mycolumn1']."<br />n";
	echo "MyColumn2: ".$aRow['mycolumn2']."<br />n";
}
 
/* Outputs the following if only two rows were available:
Row 0:
MyColumn1: value
MyColumn2: value
 
Row 1:
MyColumn1: value
MyColumn2: value
*/
 
// Retrieve a value with a WHERE clause
$oDb->Execute("
	SELECT *
	FROM   mytable
	WHERE  mycolumn1 = ".$oDb->Clean($_GET['mycolumn1'])."
	LIMIT  0,1
", __FILE__, __LINE__);
 
// Single quotes are automatically put around the string by the Clean() function
?>

I’m sure many people will have questions. You can ask those in the blog post, and I will try to answer them so you can work with it.

Good luck!

blog comments powered by Disqus

Twitter

  • Er gaat een alarm af in de keuken! Dat betekent dat mijn pizza klaar is... :-) 21 mins ago
  • He he, het duurt even, maar mijn 45-dagen trial van gettorrents.org (aangevraagd in 2008) is eindelijk verlopen 25 mins ago
  • Geloof het of niet, mijn Facebook-account is weer actief. Was heel even nodig gisteren, ik laat hem maar actief. Misschien gebruik ik hem... 7 hrs ago
  • Heerlijk! De hele dag het kantoor voor mezelf! Voor de verandering weer eens @CityFMCR aan :-) 10 hrs ago
  • Vanaf vandaag heet mijn bureau "Scotty". Erg lang verhaal, maar er zit een logica achter waar Mr. Spock van flauw zou vallen. 1 day ago
  • More updates...

Posting tweet...

Recently on Last.fm