A MySQL Class/Iterator for PHP5
After some discussion over at DevNetwork Forums regarding MySQL classes I was given an idea which changes the way I’ve always written MySQL wrappers. Previously there had always been just one class. That class returned result resources and provided the wrapper methods for working with these.
The inspiration I was given in this scenario however was to work with two objects. One which holds the connection, runs the queries, escapes data and checks the server status; and another which contains the resultant data (resultset) and methods for working with the data. What came out of a little bit of planning was a DB class as described, and a DB_Result class which contains properties such as the number of rows, the number of affected rows and the ID of the row (if the query was an insert). The DB_Result class also contains iterator methods for working through the resultset in forwards or backwards directions, and also contains a goto() method for quick jumping between rows.
Here’s how it looks (feel free to use free — I’m aware there’s no license attached to the code but you can use it like LGPL):
DB.php
/**
* A basic DB connection class returning
* resultset objects following an iterator pattern
* @author d11wtq
*/
class DB
{
/**
* The database connection resource
* @var resource db
*/
private $conn;
/**
* The database name itself
* @var string database
*/
private $db;
/**
* An instance of a singleton
* @var object DB
*/
private static $instance = null;
/**
* Constructor
* @param string server
* @param string username
* @param string password
* @param string db name
*/
public function __construct($host, $user, $pass, $db=false)
{
$this->connect($host, $user, $pass);
if ($this->conn && $db) $this->selectDb($db);
}
/**
* Used for retreiving an instance of a singleton if wanted
* @return object DB
*/
public static function getInstance($host, $user, $pass, $db)
{
if (self::$instance === null)
{
self::$instance = new DB($host, $user, $pass, $db);
}
return self::$instance;
}
/**
* Connect to database (stored internally)
* @param string server
* @param string username
* @param string password
*/
public function connect($host, $user, $pass)
{
$this->conn = @mysql_connect($host, $user, $pass);
}
/**
* Change databases
* @param string database
*/
public function selectDb($db)
{
@mysql_select_db($db, $this->conn);
$this->db = $db;
}
/**
* Check which db is currently used
* @return string database
*/
public function getDbName()
{
return $this->db;
}
/**
* Check if the connection is successful
* @return boolean
*/
public function isConnected()
{
return is_resource($this->conn);
}
/**
* Close the connection
*/
public function disconnect()
{
@mysql_close($this->conn);
}
/**
* Fetch the last error
* @return string error
*/
public function getError()
{
return mysql_error($this->conn);
}
/**
* Run a query against the database and return
* a resultset iterator object
* @return object DB_Result
*/
public function query($query)
{
$result = @mysql_query($query);
$insert = false;
if (strpos(trim(strtolower($query)), ‘insert’) === 0) $insert = true;
return new DB_Result($result, $this->conn, $insert);
}
/**
* Retreive info about the server
* @return string info
*/
public function info()
{
return mysql_get_server_info($this->conn);
}
/**
* Get details about the current system status
* @return array details
*/
public function status()
{
return explode(‘ ‘, mysql_stat($this->conn));
}
/**
* Escape a string to make it safe for mysql
* @return string escaped output
*/
public function escape($string)
{
return mysql_real_escape_string($string, $this->conn);
}
}
?>
DB_Result.php
/**
* DB_Result class. Provides an iterator wrapper
* for working with a MySQL result.
* @author d11wtq
*/
class DB_Result
{
/**
* The ID that was created as a result
* of inserting a row
* @var int id
*/
private $id;
/**
* The size of the resultset
* @var int length (num rows)
*/
private $length = 0;
/**
* The result itself
* @var result result
*/
private $result;
/**
* The row at our current position in the
* resultset
* @var array row
*/
private $currentRow = array();
/**
* Current position
* @var int position
*/
private $position = 0;
/**
* The last position we were at when we read from the resultset
* @var int last position
*/
private $lastPosition = 0;
/**
* If we have pulled out any rows or not yet
* @var boolean Got rows
*/
private $gotResult = false;
/**
* The affected number of rows from the query
* @var int num rows
*/
private $affectedRows = -1;
/**
* Constructor
* @param result result
* @param resource connection
* @param boolean insert query
*/
public function __construct(&$result, &$conn, $insert=false)
{
$this->result = $result;
$this->conn = $conn;
if ((@mysql_num_rows($this->result) >= 0 && $this->result !== false) || $insert)
{
if ($insert) $this->id = mysql_insert_id($conn);
$this->length = (int) @mysql_num_rows($this->result);
$this->affectedRows = mysql_affected_rows($conn);
}
}
/**
* Magic overloaded method.
* Returns data from the resultset
* @param string column
*/
public function __get($field)
{
if ($this->lastPosition != $this->position || !$this->gotResult)
{
mysql_data_seek($this->result, $this->position);
$this->currentRow = mysql_fetch_assoc($this->result);
$this->lastPosition = $this->position;
$this->gotResult = true;
}
return $this->currentRow[$field];
}
/**
* Get the insert id
*/
public function id()
{
return $this->id;
}
/**
* Size of the resultset
*/
public function length()
{
return $this->length;
}
/**
* Go to the first row of the resultset
* @return boolean
*/
public function first()
{
if ($this->length > 0)
{
$this->goto(0);
return true;
}
else return false;
}
/**
* Go to the last row of the resultset
* @return boolean
*/
public function last()
{
return $this->goto($this->length-1);
}
/**
* Check if we’ve reched the end of the resultset
* @return boolean
*/
public function end()
{
if ($this->position >= $this->length) return true;
else return false;
}
/**
* Check if we’re at the start of the resultset
* @return boolean
*/
public function start()
{
return ($this->position < 0);
}
/**
* Move to the next row of the resultset
* @return boolean
*/
public function next()
{
return $this->goto($this->position+1);
}
/**
* Move to the previous row in the resultset
* @return boolean
*/
public function prev()
{
return $this->goto($this->position-1);
}
/**
* Go to a specified row in the resultset
* Row numbering starts at zero
* @param int row
* @return boolean
*/
public function goto($position)
{
if ($position < -1 || $position > $this->length) return false;
else
{
$this->position = $position;
return true;
}
}
/**
* Get the affected number of rows
*/
public function affectedRows()
{
return $this->affectedRows;
}
/**
* Get the result resource itself
*/
public function &get()
{
return $this->result;
}
/**
* Get the current position
*/
public function position()
{
return $this->position;
}
}
?>
The DB_Result class is a fair amount larger than the DB class because that’s where most of the work is done in any case.
Now here’s how you’d use it…. there are several examples in this one script:
require_once(‘DB.php’);
require_once(‘DB_Result.php’);
$db = new DB(‘localhost’, ‘user’, ‘pass’, ‘db_test’);
/*
create table foo
(
id int auto_increment primary key,
one varchar(255),
two varchar(255)
)
*/
$result = $db->query("select * from foo");
//Go forwards through the resultset
for ($result->first(); !$result->end(); $result->next())
{
echo $result->one.‘ ‘.$result->two.‘<br />’;
}
//We can find the affected rows and the id of the insert row when running
// insert, update & delete queries
$result = $db->query("insert into foo (one, two) values (’zip’, ‘button’)");
echo $result->id(); //The ID of the record we just added
$result = $db->query("select * from foo");
//Go backwards through the resultset
for ($result->last(); !$result->start(); $result->prev())
{
echo $result->one.‘ ‘.$result->two.‘<br />’;
}
//Get the number of rows
echo $result->length();
//Jump to a certain row
$result->goto(9);
echo $result->one;
?>
I thought I’d share if nothing else :)
Matthijs [ 13Aug06]
Looks good Chris. Have seen examples of this separation of db and dbresult elsewere, think in the sitepoint book of Harry Fuechs. Seems a logical way to do it. Thanks for sharing.
Jcart [ 13Jun07]
A couple comments about your query:
1. For consistancy, use the connection resource on the mysql_query() call
2. There are other types of queries that we do not want a result set, I’ve whipped something up to demonstrate briefly
3. Incase we do not want a result set, I thought it would be a good idea to return a boolean if the query was succesful or not
Untested
{
$result = @mysql_query($query, $this->conn);
$tokens = explode(’ ‘, $query);
if (count($tokens) > 0)
{
if (!in_array(trim($token), array(’update’, ‘delete’, ‘insert’)) {
return new DB_Result($result, $this->conn, $insert);
}
}
return is_resource($result);
}
links for 2008-04-22 by chrispie.de - tech / fun / berlin [ 22Apr08]
[…] W3 Style » Blog Archive » A MySQL Class/Iterator for PHP5 (tags: php mysql) […]