![Introduction to PHP PDO (PHP Data Objects)](/rs/icerik/normal/0219/introduction_to_php_pdo_php_data_objects__5659_s2kk7_mzsdf.png)
- What is PDO
- What Databases does PDO support
- Where do I begin?
- Connect to a Database
- There is no five
- Close a Database Connection
- Query a Database
- Fetch Modes
- Error Handling
- Prepared Statements
- Transactions
- Get Last Insert ID
- Global Instance
- Conclusions
PDO is a PHP extension to formalise PHP's database connections by creating a uniform interface. This allows developers to create code which is portable across many databases and platforms. PDO is _not_ just another abstraction layer like PearDB although PearDB may use PDO as a backend. Those of you familiar with Perls DBI may find the syntax disturbingly familiar.
NOTE: YOUR MUST READ THE SECTION ON ERROR HANDLINGTO BENIFIT FROM THIS TUTORIAL
During this tutorial we will be using a database called animals, which, as you might have guessed, is a database of animals, genius! The animals table is described here.
CREATE TABLE animals ( animal_id MEDIUMINT(8) NOT NULL AUTO_INCREMENT PRIMARY KEY,
animal_type VARCHAR(25) NOT NULL,
animal_name VARCHAR(25) NOT NULL
) ENGINE = MYISAM ;
INSERT INTO `animals` (`animal_id`, `animal_type`, `animal_name`) VALUES
(1, 'kookaburra', 'bruce'),
(2, 'emu', 'bruce'),
(3, 'goanna', 'bruce'),
(4, 'dingo', 'bruce'),
(5, 'kangaroo', 'bruce'),
(6, 'wallaby', 'bruce'),
(7, 'wombat', 'bruce'),
(8, 'koala', 'bruce');
PDO supports many of the popular databases as seen on the list below.
- DBLIB: FreeTDS / Microsoft SQL Server / Sybase
- Firebird (http://firebird.sourceforge.net/): Firebird/Interbase 6
- IBM (IBM DB2)
- INFORMIX - IBM Informix Dynamic Server
- MYSQL (http://www.mysql.com/): MySQL 3.x/4.0
- OCI (http://www.oracle.com): Oracle Call Interface
- ODBC: ODBC v3 (IBM DB2 and unixODBC)
- PGSQL (http://www.postgresql.org/): PostgreSQL
- SQLITE (http://sqlite.org/): SQLite 3.x
To see if the PDO driver is available for your database, check phpinfo() and you should have a section named PDO and another pdo_mysql or pdo_sqlite depending on your choice of database. You may also check the available drivers with the static method PDO::getAvailableDrivers().
<?php
foreach(PDO::getAvailableDrivers() as $driver)
{
echo $driver.'<br />';
}
?>
To enable PDO simply configure --enable-pdo and --with-pdo_sqlite --with_pdo_mysql or whatever database needs supporting by PDO.
Windows users will need to un-comment the appropriate line in php.ini and restart the web server.
If you are reading this you are more than likely to have connected to a database using PHP before using a database specific function such as mysql_connect() or pg_connect or, for the truely evolved coder, SQLite. To use PDO with your database you need to have the correct PDO driver installed for it. For the SQLite PDO driver you need to configure PHP --with-pdo-sqlite. If you are using a RPM based system there are pdo-sqlite.rpm's available. Before we go any further, lets connect to a database and see what all the fuss is about.
Every interaction with a database begins with a connection. Regardless of the database you use, you must connect first and establish a database handler. After connecting you your database of choice, much of the PDO methods are similar. This is why PDO is such a powerful and useful tool for PHP. Here we show how to connect to various databases and establish a database handler object that we can use for further interaction with the database.
As mentioned above, you may have previously tried to connect to a PgSQL database using pg_connect. Here we connect with PDO.
<?php
try {
$db = new PDO("pgsql:dbname=pdo;host=localhost", "username", "password" );
echo "PDO connection object created";
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
So that was a bit of a no-brainer to start with, we see the use of "new" to create the PDO object followed by the database type username and password. This should be familiar to most people who have connected to a database before using traditional methods.. As we have used try{} and catch(){} we see upon failure that an exception is thrown with the error message "could not find driver". This tells us the PDO_PGSQL driver is not present and needs to be loaded. As noted, an exception is thrown. PDO can handle errors in several ways, more on this later.
How did it connect to the database?
The database connection is handled internally by PDO's __construct() and this represents our database connection.
Lets see what happens if we try to connect to database as we did above without catching the exception and see what happens..
<?php
$db = new PDO("pgsql:dbname=no_database;host=localhost", "username", "password" );
?>
From the above snippet you will get a result something like this below
Fatal error: Uncaught exception 'PDOException' with message 'could not find driver' in /www/pdo.php:2 Stack trace: #0 /www/pdo.php(2): PDO->__construct('pgsql:dbname=pd...', 'username', 'password') #1 {main} thrown in /www/pdo.php on line 2
This is the default behaviour when an exception is not caught, a backtrace is generated and the script is terminated. As you can see, all the information is dumped including the file path and the database username and password. It is the responsibility of the coder to catch exceptions or to deal with the errors using set_exception_handler() function to prevent this happening. More about handling errors and exceptions later.
When PDO is used with SQLite, database creation becomes even easier. Simply specify the path to the database file and it will be loaded. If the database file does not exist, PDO will attempt to create it. Lets see how we go with the same code but change the database to SQLite.
<?php
try {
/*** connect to SQLite database ***/
$dbh = new PDO("sqlite:/path/to/database.sdb");
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
Because the database path does not exist and cannot be created, an exception is thrown, the exception is caught in the catch block and the error message is displayed with $e->Message(). Now that we know how to create a database, we can create tables and INSERT some data.
Another feature of SQLite is the ability to create tables in memory. This can be amazingly helpful if you wish to create tempory databases or tables or even for development code.
<?php
try {
/*** connect to SQLite database ***/
$db = new PDO("sqlite::memory");
/*** a little message to say we did it ***/
echo 'database created in memory';
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
We see above that a database is created in memory and a message is displayed to let us know. If the creation of the database failed, a PDO exception would be thrown and the script terminated at that point, passing control to the catch block.
MySQL is the choice of many web developers and will be used as the database of choice for much of this tutorial. Here we see how to connect to a MySQL database.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=mysql", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database';
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
Often used by developers using windows, Firebird is a good database and connection is just as simple as the examples above.
<?php
try {
$dbh = new PDO("firebird:dbname=localhost:C:ProgramsFirebirdDATABASE.FDB", "SYSDBA", "masterkey");
}
catch (PDOException $e)
{
echo $e->getMessage();
}
?>
Informix is popular with many windows users also, this example shows how to connect to an informix database cataloged as InformixDB in odbc.ini:
<?php
try {
$dbh = new PDO("informix:DSN=InformixDB", "username", "password");
}
catch (PDOException $e)
{
echo $e->getMessage();
}
?>
The Oracle database is used by many 'enterprise' companies but these days there are sleeker options. Lets see a simple connection to Oracle
<?php
try {
$dbh = new PDO("OCI:", "username", "password")
}
catch (PDOException $e)
{
echo $e->getMessage();
}
?>
This works fine for a simple Oracle connection. The oracle driver may take two optional parameters, The database name, and the character set. To connect with a database name of "accounts" and a charset of UTF-8 the following code should be used.
<?php
try {
$dbh = new PDO("OCI:dbname=accounts;charset=UTF-8", "username", "password");
}
catch (PDOException $e)
{ echo $e->getMessage(); } ?>
There are many connections ODBC can create, here we show how to connect to a MS Access database named accounts. The specified path is c:accounts.mdb.
<?php
try {
$dbh = new PDO("odbc:Driver={Microsoft Access Driver (*.mdb)};Dbq=C:accounts.mdb;Uid=Admin");
}
catch (PDOException $e)
{
echo $e->getMessage();
}
?>
Once again a Windows specific database, DBLIB can be used as follows
<?php
try {
$hostname = "localhost";
$port = 10060;
$dbname = "my_database";
$username = "username";
$password = "password";
$dbh = new PDO ("dblib:host=$hostname:$port;dbname=$dbname","$username","$password");
}
catch (PDOException $e)
{
echo $e->getMessage();
}
?>
This example shows connecting to an IBM DB2 database named accounts
<?php
try {
$db = new PDO("ibm:DRIVER={IBM DB2 ODBC DRIVER};DATABASE=accounts; HOSTNAME=1.2.3,4;PORT=56789;PROTOCOL=TCPIP;", "username", "password");
}
catch (PDOException $e)
{
echo $e->getMessage();
}
?>
Up to this point we have seen how to connect to a database using PDO. But of course, we also need to disconnect when we have finished. To close the connection the object needs to be destroyed so that no reference to it remains. This is normally done at the end of a script where PHP will automatically close the connection. However, the connection may be close implicitly by assigning the value of null to the object as seen below.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=mysql", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database';
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
If the database connection fails, the code to assign a null value is never called as the exception throws control to the catch block.
Now that we can open and close a connection to the database with PDO, we can make use of it for what databases are made for, storing and retrieving information. The simplest form of query is the PDO query method. As the name suggests, this is used to perform database queries. Before we begin to query a database, lets create a small database with a table for animals. This will be a MySQL database for use throughout much of this tutorial. Remember, because PDO provides a common set of tools for databases, once we have the correct connection, the rest of the code is the same, regardless of the database you choose. When using PDO to query a database, the function used to do so depends on the statement you wish to send to the database. Below we will see three queries on how to INSERT, SELECT and UPDATE.
To gather information from a database, we first need to put some info into it. We use the same code from above to connect and disconnect from the database and the INSERT query is accomplished using the PDO::exec method.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** INSERT data ***/
$count = $dbh->exec("INSERT INTO animals(animal_type, animal_name) VALUES ('kiwi', 'troy')");
/*** echo the number of affected rows ***/
echo $count;
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
The output of the script above will look like this:
Connected to database
1
This shows us that we connected successfully to the database and then we have displayed the number of affected rows. PDO::exec returns the number of affected rows if successful, or zero (0) if no rows are affected. This may cause issues if you are checking for a boolean value and why it is recommended using === when to check for type also, as zero (0) may evaluate to boolean FALSE.
The PDO::exec method should be used for SQL statements that do not return a result set. We could use this same method to INSERT many more animals to our database, but a more effecient method would be to use a transaction. This is covered in the section on Transactions.
Unlike PDO::exec the PDO::query method returns a result set, that is, a group of information from the database in the form of a PDOStatement object. Our database should look a little like the example in the What is PDO section. Using this we can SELECT information.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** The SQL SELECT statement ***/
$sql = "SELECT * FROM animals";
foreach ($dbh->query($sql) as $row)
{
print $row['animal_type'] .' - '. $row['animal_name'] . '<br />';
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
From the script above, we can expect the results to look like this:
Connected to database
emu - bruce
funnel web - bruce
lizard - bruce
dingo - bruce
kangaroo - bruce
wallaby - bruce
wombat - bruce
koala - bruce
kiwi - troy
You will have noticed that we can iterate over the result set directly with foreach. This is because internally the PDO statement implements the SPL traversble iterator, thus giving all the benifits of using SPL. For more on SPL refer to the Introduction to SPL page. The greatest benifit of this is that SPL iterators know only one element at a time and thus large result sets become manageable without hogging memory.
To update a field in a database with PDO we once again use the PDO::exec method in the same manner as we did with the INSERT
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** INSERT data ***/
$count = $dbh->exec("UPDATE animals SET animal_name='bruce' WHERE animal_name='troy'");
/*** echo the number of affected rows ***/
echo $count;
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
Once again we see that the connection is made to the database and one row is affected as now the kiwi has become a true Australian like the rest of the creatures. PDO::exec should be used for all database queries where no result set is required.
The section above showed how using PDO::query we can fetch information from the database. The PDO::query method returns a PDOStatement object that can be utilized in much the same was as mysql_fetch_object() or pg_fetch_object(). Of course there are times when an numerical index is needed or an associative index. PDO::query provides for this also by allowing the coder to set the fetch mode for via the PDOStatement object or via PDOStatement::setFetchMode().
To fetch an associative array from our results the constant PDO::FETCH_ASSOC is used and returns the column names as indexes or keys of the resulting array.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** The SQL SELECT statement ***/
$sql = "SELECT * FROM animals";
/*** fetch into an PDOStatement object ***/
$stmt = $dbh->query($sql);
/*** echo number of columns ***/
$result = $stmt->fetch(PDO::FETCH_ASSOC);
/*** loop over the object directly ***/
foreach($result as $key=>$val)
{
echo $key.' - '.$val.'<br />';
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
The above code will give a result like this:
Connected to database
animal_id - 1
animal_type - emu
animal_name - bruce
PDO has returned the results as a PDOStatement object that we can iterate over directly. The resulting indexes are the names of the fields within the animals database.
Like PDO::FETCH_ASSOC, the PDO::FETCH_NUM produces a numerical index of the result set rather than the field names.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** The SQL SELECT statement ***/
$sql = "SELECT * FROM animals";
/*** fetch into an PDOStatement object ***/
$stmt = $dbh->query($sql);
/*** echo number of columns ***/
$result = $stmt->fetch(PDO::FETCH_NUM);
/*** loop over the object directly ***/
foreach($result as $key=>$val)
{
echo $key.' - '.$val.'<br />';
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
The above code will give a result like this:
Connected to database
0 - 1
1 - emu
2 - bruce
As you can see above the indexes are now numeric in the result set
There may be times you need to fetch both numerical and associative indexes. PDO::FETCH_BOTH produces a numerical and associative index of the result set so you can use either, or both.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** The SQL SELECT statement ***/
$sql = "SELECT * FROM animals";
/*** fetch into an PDOStatement object ***/
$stmt = $dbh->query($sql);
/*** echo number of columns ***/
$result = $stmt->fetch(PDO::FETCH_BOTH);
/*** loop over the object directly ***/
foreach($result as $key=>$val)
{
echo $key.' - '.$val.'<br />';
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
Now we see the results have included both indexes.
Connected to database
animal_id - 1
0 - 1
animal_type - emu
1 - emu
animal_name - bruce
2 - bruce
This little gem takes the result set and returns it as an anonymous object or stdClass and maps the field names from the database as object properties with the values the values of stored in the database.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** The SQL SELECT statement ***/
$sql = "SELECT * FROM animals";
/*** fetch into an PDOStatement object ***/
$stmt = $dbh->query($sql);
/*** echo number of columns ***/
$obj = $stmt->fetch(PDO::FETCH_OBJ);
/*** loop over the object directly ***/
echo $obj->animal_id.'<br />';
echo $obj->animal_type.'<br />';
echo $obj->animal_name;
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
The above code gives the results like this:
Connected to database
1
emu
bruce
The use of the field names as class properties makes integrating results into an Object Oriented envioronment simple.
PDO::FETCH_LAZY is odd as it combines PDO::FETCH_BOTH and PDO::FETCH_OBJ. I am unsure why you would want to do this, but it must have been important enough for somebody to create it. The code below is that of PDO::FETCH_BOTH and is reproduced here for examples sake.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** The SQL SELECT statement ***/
$sql = "SELECT * FROM animals";
/*** fetch into an PDOStatement object ***/
$stmt = $dbh->query($sql);
/*** echo number of columns ***/
$result = $stmt->fetch(PDO::FETCH_BOTH);
/*** loop over the object directly ***/
foreach($result as $key=>$val)
{
echo $key.' - '.$val.'<br />';
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
The above code will give a result the same as that of PDO::FETCH_BOTH. Genius!
PDO::FETCH_CLASS instantiates a new instance of the specified class. The field names are mapped to properties (variables) within the class called. This saves quite a bit of code and speed is enhanced as the mappings are dealt with internally.
<?php
class animals{
public $animal_id;
public $animal_type;
public $animal_name;
/***
*
* @capitalize first words
*
* @access public
*
* @return string
*
*/
public function capitalizeType(){
return ucwords($this->animal_type);
}
} /*** end of class ***/
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** The SQL SELECT statement ***/
$sql = "SELECT * FROM animals";
/*** fetch into an PDOStatement object ***/
$stmt = $dbh->query($sql);
/*** fetch into the animals class ***/
$obj = $stmt->fetchALL(PDO::FETCH_CLASS, 'animals');
/*** loop of the object directly ***/
foreach($obj as $animals)
{
/*** call the capitalizeType method ***/
echo $animals->capitalizeType().'<br />';
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
The code above produces a list of animal types, with the first letter capitalized like this:
Connected to database
Emu
Funnel Web
Lizard
Dingo
Kangaroo
Wallaby
Wombat
Koala
Kiwi
The PDO::FETCH_CLASS constant has fetched the results directly into the animals class where we were able to directly manipulate the results, nifty.
PDO provides an alternative to PDO::fetch and PDO::FETCH_CLASS. PDOStatement::fetchObject() will bundle them together to give the same result as shown here.
<?php
class animals{
public $animal_id;
public $animal_type;
public $animal_name;
/***
*
* @capitalize first words
*
* @access public
*
* @return string
*
*/
public function capitalizeType(){
return ucwords($this->animal_type);
}
} /*** end of class ***/
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** The SQL SELECT statement ***/
$sql = "SELECT * FROM animals";
/*** fetch into an PDOStatement object ***/
$stmt = $dbh->query($sql);
/*** fetch into the animals class ***/
$animals = $stmt->fetchObject('animals');
/*** echo the class properties ***/
echo $animals->animal_id.'<br />';
echo $animals->capitalizeType().'<br />';
echo $animals->animal_name;
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
The above code gives the results like this:
Connected to database
1
Emu
bruce
Note that we have called the animals::capitalizeType() method to show that we are in fact working with an instance of the animals class. PDO::fetchObject() will also work as a substitute for PDO::FETCH_OBJ.
The PDO::FETCH_INTO constant allows us to fetch the data into an existing instance of a class. Like PDO::FETCH_CLASS the field names are mapped to the class properties. With this in mind, we should be able to replicate the behaviour of PDO::FETCH_CLASS by instantiating the new object when setting the fetch mode. In this instance, the fetch mode is set using PDO::setFetchMode() method.
<?php
class animals{
public $animal_id;
public $animal_type;
public $animal_name;
public function capitalizeType(){
return ucwords($this->animal_type);
}
} /*** end of class ***/
/*** instantiate a new animals instance ***/
$animals = new animals;
$animals->animal_id = 10;
$animals->animal_type = 'crocodile';
$animals->animal_name = 'bruce';
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** The SQL SELECT statement ***/
$sql = "SELECT * FROM animals";
/*** fetch into an PDOStatement object ***/
$stmt = $dbh->query($sql);
/*** set the fetch mode with PDO::setFetchMode() ***/
$stmt->setFetchMode(PDO::FETCH_INTO, new animals);
/*** loop over the PDOStatement directly ***/
foreach($stmt as $animals)
{
echo $animals->capitalizeType().'<br />';
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
Once again, the above code produces a list of animal types, with the first letter capitalized like this:
Connected to database
Emu
Funnel Web
Lizard
Dingo
Kangaroo
Wallaby
Wombat
Koala
Kiwi
PDO error handling is comes in several flavours. Previously in this tutorial we have have only used the simplest of try{} catch(){} blocks to catch an error in the database connection, but what of other errors? perhaps a field name does not exist? Lets see how we go with a simple error with the previous code.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** The SQL SELECT statement with incorrect fieldname ***/
$sql = "SELECT username FROM animals";
foreach ($dbh->query($sql) as $row)
{
print $row['animal_type'] .' - '. $row['animal_name'] . '<br />';
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
The above script will produce an error such as this:
Connected to database
Warning: Invalid argument supplied for foreach() in /www/pdo.php on line 18
This is because there is no error handling. The SELECT statement has a field name 'username' which does not exist and an error is generated by the database. The only default error handling is done with the initial connection. Unless we deal with the error, we have a problem with displaying full path to the world. To deal with this we need to set an attribute to the type of error handling we wish to utilize. The types of error handling are
Exception
Warning
Silent
Lets begin with exception as we have the try{} catch(){} blocks in place already.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** set the error reporting attribute ***/
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
/*** The SQL SELECT statement ***/
$sql = "SELECT username FROM animals";
foreach ($dbh->query($sql) as $row)
{
print $row['animal_type'] .' - '. $row['animal_name'] . '<br />';
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
Now with the error mode set to Exception the error generated looks like this:
Connected to database
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'username' in 'field list'
Normally we would not show this type of error to the end user, and the exception would be handled perhaps with message saying No Results Found or something vague, but this does show how we can set the error mode as we wish. To set the error mode to Warning should look easy from here.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** set the error reporting attribute ***/
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
/*** The SQL SELECT statement ***/
$sql = "SELECT username FROM animals";
foreach ($dbh->query($sql) as $row)
{
print $row['animal_type'] .' - '. $row['animal_name'] . '<br />';
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
Now a different error is displayed.
Connected to database
Warning: PDO::query() [function.PDO-query]: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'username' in 'field list' in /www/pdo.php on line 21
Warning: Invalid argument supplied for foreach() in /www/pdo.php on line 21
Here and E_WARNING has been generated and if display_errors is on the error would be seen by an end user. It is hoped that if you are in a production environment this is not the case.
Lastly, there is the Silent mode. As the name suggests, this mode silences the errors so no output is sent from the error. However, it does not stop the code at the point of error and any further errors are still sent.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** set the error reporting attribute ***/
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
/*** The SQL SELECT statement ***/
$sql = "SELECT username FROM animals";
foreach ($dbh->query($sql) as $row)
{
print $row['animal_type'] .' - '. $row['animal_name'] . '<br />';
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
Now we see the script above produces the following output.
Connected to database
Warning: Invalid argument supplied for foreach() in /www/pdo.php on line 21
As you can see, the error has been silenced, but the following error has not been attended to, and would need further checks to ensure the value passed to the foreach is a valid arguement.
As we saw with the exception code, the SQLSTATE code was part of the error message. This error code is also available with the PDO::errorCode() method.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
}
catch(PDOException $e)
{
echo $e->getMessage();
}
/*** an invalide fieldname ***/
$sql = "SELECT username FROM animals";
/*** run the query ***/
$result = $dbh->query($sql);
/*** show the error code ***/
echo $dbh->errorCode();
?>
The code above shows the error code relevant to the SQLSTATE. This is a five character string as defined by the ANSI SQL standard.
Connected to database
42S22
Further information about an error may be gained from the PDO::errorInfo() method. This returns an array containing the SQLSTATE, the error code, and the error message.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
}
catch(PDOException $e)
{
echo $e->getMessage();
}
/*** an invalid table name ***/
$sql = "SELECT animal_id FROM users";
/*** run the query ***/
$result = $dbh->query($sql);
/*** show the error info ***/
foreach($dbh->errorInfo() as $error)
{
echo $error.'<br />';
}
?>
With this code, the error information looks like this:
Connected to database
42S02
1146
Table 'animals.users' doesn't exist
If there is no error, the SQLSTATE will be the only value shown, with a value of 00000.
What is a prepared statement? A prepared statement is a pre-compiled SQL statement that accepts zero or more named parameters. Ok, so thats my attempt at describing what it is, if you have a better description, let us know.
The SQL is prepared for execution. This is especially useful when using the same statement or query multiple times with different parameters, or field values. The boost in speed is hidden from userland code as the PDO driver allows client and server side caching of the query and meta data. It also helps prevent SQL injection by calling the PDO::quote() method internally.
PDO accepts two kinds of parameter markers.
named - :name
question mark - ?
You must choose one or the other, they cannot be mixed.
Lets dive in and have a look at how PDO::prepare and PDOStatement::execute work together.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** set the error reporting attribute ***/
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
/*** some variables ***/
$animal_id = 6;
$animal_name = 'bruce';
/*** prepare the SQL statement ***/
$stmt = $dbh->prepare("SELECT * FROM animals WHERE animal_id = :animal_id AND animal_name = :animal_name");
/*** bind the paramaters ***/
$stmt->bindParam(':animal_id', $animal_id, PDO::PARAM_INT);
$stmt->bindParam(':animal_name', $animal_name, PDO::PARAM_STR, 5);
/*** execute the prepared statement ***/
$stmt->execute();
/*** fetch the results ***/
$result = $stmt->fetchAll();
/*** loop of the results ***/
foreach($result as $row)
{
echo $row['animal_id'].'<br />';
echo $row['animal_type'].'<br />';
echo $row['animal_name'];
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
The above code will produce the following:
Connected to database
6
wallaby
bruce
Errr, what was that?
What is this name = :variable business
What we have done is bind the variable named $animal_id and $animal_name to the statement. Remember this as many find it difficult to grasp. You are not binding the value of the variable, you are binding the variable itself. Lets change the value of the animal_id after the variable is bound and see what happens..
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** set the error reporting attribute ***/
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
/*** some variables ***/
$animal_id = 6;
$animal_name = 'bruce';
/*** prepare the SQL statement ***/
$stmt = $dbh->prepare("SELECT * FROM animals WHERE animal_id = :animal_id AND animal_name = :animal_name");
/*** bind the paramaters ***/
$stmt->bindParam(':animal_id', $animal_id, PDO::PARAM_INT);
$stmt->bindParam(':animal_name', $animal_name, PDO::PARAM_STR, 5);
/*** reassign the animal_id ***/
$animal_id = 3;
/*** execute the prepared statement ***/
$stmt->execute();
/*** loop over the results ***/
while($row = $stmt->fetch())
{
echo $row['animal_id'].'<br />';
echo $row['animal_type'].'<br />';
echo $row['animal_name'];
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
Now see the results have changed
Connected to database
3
lizard
bruce
Because we have bound the variable $animal_id to the $stmt object any change to the value of that varible will be reflected in the statement. This format can be used for both SELECT and INSERT statements. But this is a bit cumbersome for a single query and the above PDO query could have done the job equally as well, so lets run the query multiple times. Ssimply by changing the animal_id and animal_name variables we can run the query over and over without re-writing as it is already 'prepared'.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** set the error reporting attribute ***/
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
/*** some variables ***/
$animal_id = 6;
$animal_name = 'bruce';
/*** prepare the SQL statement ***/
$stmt = $dbh->prepare("SELECT * FROM animals WHERE animal_id = :animal_id AND animal_name = :animal_name");
/*** bind the paramaters ***/
$stmt->bindParam(':animal_id', $animal_id, PDO::PARAM_INT);
$stmt->bindParam(':animal_name', $animal_name, PDO::PARAM_STR, 5);
/*** reassign the animal_id ***/
$animal_id = 3;
$animal_name = 'kevin';
/*** execute the prepared statement ***/
$stmt->execute();
/*** loop over the results ***/
while($row = $stmt->fetch())
{
echo $row['animal_id'].'<br />';
echo $row['animal_type'].'<br />';
echo $row['animal_name'].'<br />';
}
/*** reassign the animal_id ***/
$animal_id = 7;
$animal_name = 'bruce';
/*** execute the prepared statement ***/
$stmt->execute();
/*** loop over the results ***/
while($row = $stmt->fetch())
{
echo $row['animal_id'].'<br />';
echo $row['animal_type'].'<br />';
echo $row['animal_name'].'<br />';
}
/*** reassign the animal_id ***/
$animal_id = 4;
/*** execute the prepared statement ***/
$stmt->execute();
/*** loop over the results ***/
while($row = $stmt->fetch())
{
echo $row['animal_id'].'<br />';
echo $row['animal_type'].'<br />';
echo $row['animal_name'];
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
Now we have run the query 3 times using the same prepared statement. The results look like this:
Connected to database
7
wombat
bruce
4
dingo
bruce
The second result set is missing as there is no animal named 'kevin', all Australians are named 'bruce'. Note also in the above code we have changed the loop from foreach and PDOStatement::fetchAll() to a while loop using PDOStatement::fetch()As has been mentioned we can run this over and over, but while it is shorter than coding the query over and over, we can also use an array of values!
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** set the error reporting attribute ***/
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
/*** some variables ***/
$data = array('animal_id'=>6, 'animal_name'=>'bruce');
/*** prepare the SQL statement ***/
$stmt = $dbh->prepare("SELECT * FROM animals WHERE animal_id = :animal_id AND animal_name = :animal_name");
/*** bind the paramaters ***/
$stmt->bindParam(':animal_id', $animal_id, PDO::PARAM_INT);
$stmt->bindParam(':animal_name', $animal_name, PDO::PARAM_STR, 5);
/*** reassign the variables ***/
$data = array('animal_id'=>3, 'animal_name' => 'bruce');
/*** execute the prepared statement ***/
$stmt->execute($data);
/*** loop over the results ***/
while($row = $stmt->fetch())
{
echo $row['animal_id'].'<br />';
echo $row['animal_type'].'<br />';
echo $row['animal_name'].'<br />';
}
/*** reassign the variables again ***/
$data = array('animal_id'=>4, 'animal_name' => 'bruce');
/*** execute the prepared statement ***/
$stmt->execute($data);
/*** loop over the results ***/
while($row = $stmt->fetch())
{
echo $row['animal_id'].'<br />';
echo $row['animal_type'].'<br />';
echo $row['animal_name'].'<br />';
}
/*** reassign the variables ***/
$data = array('animal_id'=>9, 'animal_name' => 'bruce');
/*** execute the prepared statement ***/
$stmt->execute($data);
/*** loop over the results ***/
while($row = $stmt->fetch())
{
echo $row['animal_id'].'<br />';
echo $row['animal_type'].'<br />';
echo $row['animal_name'];
}
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
At the beginning of this tutorial was saw multiple INSERT statements to set up the initial database. This works fine but is code intensive and with a database like SQLite a problem arises with file locking for each access. The process can be bundled into a single access by using a transaction. Transactions are quite simple and have the benifit of rolling back changes should an error occur, perhaps a system crash.
A PDO transaction begins with the with PDO::beginTransaction() method. This method turns off auto-commit and any database statements or queries are not committed to the database until the transaction is committed with PDO::commit. When PDO::commit is called, all statements/queries are enacted and the database connection is returned to auto-commit mode.
This example shows how we might set up the animals database used in this tutorial.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
/*** database name ***/
$dbname = 'animals';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=$dbname", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** set the PDO error mode to exception ***/
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
/*** begin the transaction ***/
$dbh->beginTransaction();
/*** CREATE table statements ***/
$table = "CREATE TABLE animals ( animal_id MEDIUMINT(8) NOT NULL AUTO_INCREMENT PRIMARY KEY,
animal_type VARCHAR(25) NOT NULL,
animal_name VARCHAR(25) NOT NULL
)";
$dbh->exec($table);
/*** INSERT statements ***/
$dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('emu', 'bruce')");
$dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('funnel web', 'bruce')");
$dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('lizard', 'bruce')");
$dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('dingo', 'bruce')");
$dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('kangaroo', 'bruce')");
$dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('wallaby', 'bruce')");
$dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('wombat', 'bruce')");
$dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('koala', 'bruce')");
$dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('kiwi', 'bruce')");
/*** commit the transaction ***/
$dbh->commit();
/*** echo a message to say the database was created ***/
echo 'Data entered successfully<br />';
}
catch(PDOException $e)
{
/*** roll back the transaction if we fail ***/
$dbh->rollback();
/*** echo the sql statement and error message ***/
echo $sql . '<br />' . $e->getMessage();
}
?>
This is a common task required when you need to get the id of the last INSERT. This is done with PDO::lastInserId() method as shown here.
<?php
/*** mysql hostname ***/
$hostname = 'localhost';
/*** mysql username ***/
$username = 'username';
/*** mysql password ***/
$password = 'password';
try {
$dbh = new PDO("mysql:host=$hostname;dbname=animals", $username, $password);
/*** echo a message saying we have connected ***/
echo 'Connected to database<br />';
/*** set the error reporting attribute ***/
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
/*** INSERT a new row ***/
$dbh->exec("INSERT INTO animals(animal_type, animal_name) VALUES ('galah', 'polly')");
/*** display the id of the last INSERT ***/
echo $dbh->lastInsertId();
/*** close the database connection ***/
$dbh = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
Ever need a global instance of your database connection? Here we achieve this with the use of the Singleton design patern. The goal of a singleton is to ensure the class has only a single instance and provide a global point of access to it. Here we use the getInstance() method to achieve this. A new instance is only created the first time it is accessed and all subsequent accesses are simply returned the existing instance.
<?php
class db{
/*** Declare instance ***/
private static $instance = NULL;
/**
*
* the constructor is set to private so
* so nobody can create a new instance using new
*
*/
private function __construct() {
/*** maybe set the db name here later ***/
}
/**
*
* Return DB instance or create intitial connection
*
* @return object (PDO)
*
* @access public
*
*/
public static function getInstance() {
if (!self::$instance)
{
self::$instance = new PDO("mysql:host=localhost;dbname=animals", 'username', 'password');
self::$instance-> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return self::$instance;
}
/**
*
* Like the constructor, we make __clone private
* so nobody can clone the instance
*
*/
private function __clone(){
}
} /*** end of class ***/
try {
/*** query the database ***/
$result = DB::getInstance()->query("SELECT * FROM animals");
/*** loop over the results ***/
foreach($result as $row)
{
print $row['animal_type'] .' - '. $row['animal_name'] . '<br />';
}
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
The above code will produce a result like this:
emu - bruce
funnel web - bruce
lizard - bruce
dingo - bruce
kangaroo - bruce
wallaby - bruce
wombat - bruce
koala - bruce
This method of access saves the overhead created when a new instance of an object is called each time it is referenced, so you have have few references to it. Also, if you wish to pass the objects state from one reference to another there is no need to create from the initial state.
Note that the constructor and clone methods have been made private to ensure that an instance of the class cannot be instantiated or cloned.
If you have got this far you will have seen how to create a connection, prepare a statement and exceute, and to bind Params using bindParam(). This is what most folks will be using to begin with and shows the effectiveness of using PDO to make code more portable. We highly recommend you visit http://www.php.net/manual/en/ref.pdo.php and read up on all that PDO has to offer.
Kaynak