This article shows how to use the PEAR DB package for database abstraction and querying when using PHP
PEAR::DB, provides a uniform, cross platform, cross database method for connecting to databases, when writing PHP applications/scripts. Extensive documentation can be found online here
This article aims to show briefly, how queries and updates can be performed when using PEAR DB.
PEAR DB provides a uniform consistent way of connecting to a database, through a JDBC like URL, e.g.
mysql://user:password@host/database, pgsql://user:password@host/database, sqlite:///path/to/file?mode=0600 etc.
<?php
// import PEAR DB libraries.
require_once("DB.php");
/**
* Connects to database, returns PEAR DB Object.
*/
function connect() {
$db =& DB::connect("pgsql://user:password@host/database");
if(DB::isError($db)) {
die($db->getMessage());
}
return $db;
}
The following page PEAR - introduction - connect covers this in more detail if required
In order to query a database, we could do the following :
/**
* Executes given SQL on the database, returning a list of arrays
* with the data within. e.g.
* Array(
* Array('id' => 1, 'name' => 'john'),
* Array('id' => 2, 'name' => 'bob'),
* ....
* );
*/
function execute_query($sql) {
$conn = connect();
$resultset = $conn->query($sql);
if(PEAR::isError($resultset)) {
die("Error: " . $resultset->getError());
}
$results = Array();
while($row =& $resultset->fetchRow(DB_FETCHMODE_ASSOC)) {
$results[] = $row;
}
$conn->disconnect();
return $results;
}
And to use the above, you could undertake something like :
$results = execute_query("SELECT * FROM table WHERE x = '$y'");
The above is all very good, however, the callee is required to sanitise any strings they insert into the SQL statement (in our example '$y') before calling the function. This places an unnecessary complication on the code, and is one reason why there are continual SQL Injection attacks on PHP programs - namely the programmer has to remember to do something.
The PHP addslashes function can be used to stop SQL injection attacks, however it's apparently not infallible. For further information on SQL Injection attacks see this article at LWN.net
The best way to solve the above problems, is to use prepared statements, as shown below:
A prepared statement, is ideally compiled and stored in the database itself. Upon each call, placeholders are replaced with given parameter strings. This should lead to a performance improvement, as the database can avoid parsing the SQL on each execution.
Not all databases support prepared statements (notably older versions of MySQL. The PEAR DB library is thankfully intelligent enough to perform client side parsing, so we as a developer do not need to care about such limitations.
The below is an example of prepared statement usage
/**
* Prepared statement SQL query function.
* Expects $sql to have 0 or more placeholders ('?')
* which will be replaced by values given in the $value_array. Order is important.
* @return a list of arrays (see above). Where each array represents a database row in an associative array.
*/
function execute_query($sql, $value_array = Array()){
$conn = connect();
if(sizeof($value_array) >0) {
$stmt = $conn->prepare($sql);
$res = $conn->execute($stmt, $value_array);
}
else {
$res = $conn->query($sql);
}
if(PEAR::isError($res)) {
// handle error ($res->getMessage())
}
$results = array();
while ($row =& $res->fetchRow(DB_FETCHMODE_ASSOC)) {
$results[] = $row;
}
disconnect($conn);
return $results;
}
And a query might look like :
$results = execute_query("SELECT * FROM users WHERE name = ?", Array("david"));
Updating a database, is relatively similar to querying it, except that no results are returned (i.e. we're performing UPDATE, INSERT or DELETE).
function execute_update($sql,$value_array=Array()) {
$conn = connect();
if(sizeof($value_array) > 0) {
$stmt = $conn->prepare($sql);
$res = $conn->execute($stmt, $value_array);
}
else {
$res = $conn->query($sql);
}
if(PEAR::isError($res)) {
// handle error.
}
disconnect($conn);
}
And usage is just like :
execute_update("UPDATE users SET name = ? WHERE id = ?", Array("david", 2));
I use the above with PHP5 and exceptions, and wrap the code in try / catch blocks to detect error situations.
Comments
Pear DB is sooooo slooow
Using the Pear DB class is a bad idea. The overhead is just huge. Take a look at this example, creating csv output for a table containing 2427 records.
# Query once, so mysql query cache is not an issue
$DB->query("SELECT * FROM medewerker");
# ----------
#
$output1 = "";
$time = microtime(true);
$result_pear = $DB->query("SELECT * FROM medewerker");
while ($row = $result_pear->fetchRow(DB_FETCHMODE_ORDERED)) $output1 .= join(";", $row) . "\n";
echo "Pear result - " . $result_pear->numRows() . " rows: " . number_format((microtime(true) - $time) * 1000, 2, '.', '') . "\n";
# ----------
#
$output2 = "";
$time = microtime(true);
$result_values = $DB->getAll("SELECT * FROM medewerker");
foreach ($result_values as $row) $output2 .= join(";", $row) . "\n";
echo "Pear values - " . sizeof($result_values) . " rows: " . number_format((microtime(true) - $time) * 1000, 2, '.', '') . "\n";
# ----------
#
$output3 = "";
$time = microtime(true);
$result_mysql = mysql_query("SELECT * FROM medewerker");
while ($row = mysql_fetch_row($result_mysql)) $output3 .= join(";", $row) . "\n";
echo "Mysql result - " . mysql_num_rows($result_mysql) . " rows: " . number_format((microtime(true) - $time) * 1000, 2, '.', '') . "\n";
Result:
Pear result - 2427 rows: 3522.35
Pear values - 2427 rows: 3881.87
Mysql result - 2427 rows: 194.67
Do I need to say more.
I'm planning on comparing all common DB interfaces. So keep your eye's out for an article on www.phpit.net.
Good luck,
Arnold Daniels
http://www.helderhosting.nl
performance does suck
Hi
Thanks for the comment, I've never noticed performance problems with PEAR before - but normally because I avoid returning large result sets to the client. When using PostgreSQL I obtained the following when retreiving 1557 rows of arbitary data :
PEAR (1557) took : 1.08140993118
PG_ (1557) took : 0.231956005096
So, it seems that PEAR is 5 times slower than the 'native' pg_ PHP methods. I don't think this is a problem for the applications I develop - at least not at the moment - although it's certainly something to keep an eye on.
correct
Here I totally agree with !
sqlite
If you're using an SQLite database with PEAR::DB you will need to create the database using the command line tool sqlite and not sqlite3 the database format differs slightly and it seems PEAR::DB cannot read it and will fail with the message "DB Error: no database selected".
re: Performance
Regarding performance, I think you should read this blog post.
For a more detailed analysis
For a more detailed analysis have a look at:
http://sourceforge.net/mailarchive/message.php?msg_id=10269486
Also PEAR::DB has been deprecated in favor of PEAR::MDB2.
It is faster in some of the drivers, and about equal in performance with the rest.
However MDB2 pushes portability and flexibility even further.
For example you can switch your code from buffered to unbuffered queries (meaning you do not fetch the entire result set before being able to read the first row) with a single option etc.
MDB2
Hi,
I came across MDB2 a few weeks ago, but haven't had a chance to investigate it properly yet.
On the other hand, I might just get stuck using propel or PDO instead.
thanks
David.
pdo in php
For anyone looking for database layer with performance, php5 has pdo built into language.
Post new comment