PHP Database Access: Are You Doing It Correctly?

PHP Database Access: Are You Doing It Correctly?

Tutorial Details
  • Topic - Database Access in PHP
  • Difficulty - Moderate

We've covered PHP's PDO API a couple of times here on Nettuts+, but, generally, those articles focused more on the theory, and less on the application. This article will fix that!

To put it plainly, if you're still using PHP's old mysql API to connect to your databases, read on!


What?

It's possible that, at this point, the only thought in your mind is, "What the heck is PDO?" Well, it's one of PHP's three available APIs for connecting to a MySQL database. "Three," you say? Yes; many folks don't know it, but there are three different APIs for connecting:

  • mysql
  • mysqli – MySQL Improved
  • pdo – PHP Data Objects

The traditional mysql API certainly gets the job done, and has become so popular largely due to the fact that it makes the process of retrieving some records from a database as easy as possible. For example:

/*
 * Anti-Pattern
 */

# Connect
mysql_connect('localhost', 'username', 'password') or die('Could not connect: ' . mysql_error());

# Choose a database
mysql_select_db('someDatabase') or die('Could not select database');

# Perform database query
$query = "SELECT * from someTable";
$result = mysql_query($query) or die('Query failed: ' . mysql_error());

# Filter through rows and echo desired information
while ($row = mysql_fetch_object($result)) {
    echo $row->name;
}

Yes, the code above is fairly simple, but it does come with its significant share of downsides.

  • Deprecated: Though it hasn't been officially deprecated – due to widespread use – in terms of best practice and education, it might as well be.
  • Escaping: The process of escaping user input is left to the developer – many of which don't understand or know how to sanitize the data.
  • Flexibility: The API isn't flexible; the code above is tailor-made for working with a MySQL database. What if you switch?

PDO, or PHP Data Objects, provides a more powerful API that doesn't care about the driver you use; it's database agnostic. Further, it offers the ability to use prepared statements, virtually eliminating any worry of SQL injection.


How?

When I was first learning about the PDO API, I must admit that it was slightly intimidating. This wasn't because the API was overly complicated (it's not) – it's just that the old myqsl API was so dang easy to use!

Don't worry, though; follow these simple steps, and you'll be up and running in no time.

Connect

So you already know the legacy way of connecting to a MySQL database:

# Connect
mysql_connect('localhost', 'username', 'password') or die('Could not connect: ' . mysql_error());

With PDO, we create a new instance of the class, and specify the driver, database name, username, and password – like so:

$conn = new PDO('mysql:host=localhost;dbname=myDatabase', $username, $password);

Don't let that long string confuse you; it's really very simple: we specify the name of the driver (mysql, in this case), followed by the required details (connection string) for connecting to it.

What's nice about this approach is that, if we instead wish to use a sqlite database, we simply update the DSN, or "Data Source Name," accordingly; we're not dependent upon MySQL in the way that we are when use functions, like mysql_connect.

Errors

But, what if there's an error, and we can't connect to the database? Well, let's wrap everything within a try/catch block:

try {
    $conn = new PDO('mysql:host=localhost;dbname=myDatabase', $username, $password);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
    echo 'ERROR: ' . $e->getMessage();
}

That's better! Please note that, by default, the default error mode for PDO is PDO::ERRMODE_SILENT. With this setting left unchanged, you'll need to manually fetch errors, after performing a query.

echo $conn->errorCode();
echo $conn->errorInfo();

Instead, a better choice, during development, is to update this setting to PDO::ERRMODE_EXCEPTION, which will fire exceptions as they occur. This way, any uncaught exceptions will halt the script.

For reference, the available options are:

  • PDO::ERRMODE_SILENT
  • PDO::ERRMODE_WARNING
  • PDO::ERRMODE_EXCEPTION

Fetch

At this point, we've created a connection to the database; let's fetch some information from it. There's two core ways to accomplish this task: query and execute. We'll review both.

Query

/*
 * The Query Method
 * Anti-Pattern
 */

$name = 'Joe'; # user-supplied data

try {
    $conn = new PDO('mysql:host=localhost;dbname=myDatabase', $username, $password);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $data = $conn->query('SELECT * FROM myTable WHERE name = ' . $conn->quote($name));

    foreach($data as $row) {
        print_r($row); 
    }
} catch(PDOException $e) {
    echo 'ERROR: ' . $e->getMessage();
}

Though this works, notice that we're still manually escaping the user's data with the PDO::quote method. Think of this method as, more or less, the PDO equivalent to use mysql_real_escape_string; it will both escape and quote the string that you pass to it. In situations, when you're binding user-supplied data to a SQL query, it's strongly advised that you instead use prepared statements. That said, if your SQL queries are not dependent upon form data, the query method is a helpful choice, and makes the process of looping through the results as easy as a foreach statement.

Prepared Statements

/*
 * The Prepared Statements Method
 * Best Practice
 */

$id = 5;
try {
    $conn = new PDO('mysql:host=localhost;dbname=myDatabase', $username, $password);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);    
    
    $stmt = $conn->prepare('SELECT * FROM myTable WHERE id = :id');
    $stmt->execute(array('id' => $id));

    while($row = $stmt->fetch()) {
        print_r($row);
    }
} catch(PDOException $e) {
    echo 'ERROR: ' . $e->getMessage();
}

In this example, we're using the prepare method to, literally, prepare the query, before the user's data has been attached. With this technique, SQL injection is virtually impossible, because the data doesn't ever get inserted into the SQL query, itself. Notice that, instead, we use named parameters (:id) to specify placeholders.

Alternatively, you could use ? parameters, however, it makes for a less-readable experience. Stick with named parameters.

Next, we execute the query, while passing an array, which contains the data that should be bound to those placeholders.

$stmt->execute(array('id' => $id));

An alternate, but perfectly acceptable, approach would be to use the bindParam method, like so:

$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();

Specifying the Ouput

After calling the execute method, there are a variety of different ways to receive the data: an array (the default), an object, etc. In the example above, the default response is used: PDO::FETCH_ASSOC; this can easily be overridden, though, if necessary:

while($row = $stmt->fetch(PDO::FETCH_OBJ)) {
    print_r($row);
}

Now, we've specified that we want to interact with the result set in a more object-oriented fashion. Available choices include, but not limited to:

  • PDO::FETCH_ASSOC: Returns an array.
  • PDO::FETCH_BOTH: Returns an array, indexed by both column-name, and 0-indexed.
  • PDO::FETCH_BOUND: Returns TRUE and assigns the values of the columns in your result set to the PHP variables to which they were bound.
  • PDO::FETCH_CLASS: Returns a new instance of the specified class.
  • PDO::FETCH_OBJ: Returns an anonymous object, with property names that correspond to the columns.

One problem with the code above is that we aren't providing any feedback, if no results are returned. Let's fix that:

$stmt->execute(array('id' => $id));

# Get array containing all of the result rows
$result = $stmt->fetchAll();

# If one or more rows were returned...
if ( count($result) ) {
    foreach($result as $row) {
        print_r($row);
    }
} else {
    echo "No rows returned.";
}

At this point, our full code should look like so:

 
  $id = 5;
  try {
    $conn = new PDO('mysql:host=localhost;dbname=someDatabase', $username, $password);
    $stmt = $conn->prepare('SELECT * FROM myTable WHERE id = :id');
    $stmt->execute(array('id' => $id));

    $result = $stmt->fetchAll();

    if ( count($result) ) { 
      foreach($result as $row) {
        print_r($row);
      }   
    } else {
      echo "No rows returned.";
    }
  } catch(PDOException $e) {
      echo 'ERROR: ' . $e->getMessage();
  }

Multiple Executions

The PDO extension becomes particularly powerful when executing the same SQL query multiple times, but with different parameters.

try {
  $conn = new PDO('mysql:host=localhost;dbname=someDatabase', $username, $password);
  $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

  # Prepare the query ONCE
  $stmt = $conn->prepare('INSERT INTO someTable VALUES(:name)');
  $stmt->bindParam(':name', $name);

  # First insertion
  $name = 'Keith';
  $stmt->execute();

  # Second insertion
  $name = 'Steven';
  $stmt->execute();
} catch(PDOException $e) {
  echo $e->getMessage();
}

Once the query has been prepared, it can be executed multiple times, with different parameters. The code above will insert two rows into the database: one with a name of “Kevin,” and the other, “Steven.”


CRUD

Now that you have the basic process in place, let’s quickly review the various CRUD tasks. As you’ll find, the required code for each is virtually identical.

Create (Insert)

try {
  $pdo = new PDO('mysql:host=localhost;dbname=someDatabase', $username, $password);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

  $stmt = $pdo->prepare('INSERT INTO someTable VALUES(:name)');
  $stmt->execute(array(
    ':name' => 'Justin Bieber'
  ));

  # Affected Rows?
  echo $stmt->rowCount(); // 1
} catch(PDOException $e) {
  echo 'Error: ' . $e->getMessage();

Update

$id = 5;
$name = "Joe the Plumber";

try {
  $pdo = new PDO('mysql:host=localhost;dbname=someDatabase', $username, $password);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

  $stmt = $pdo->prepare('UPDATE someTable SET name = :name WHERE id = :id');
  $stmt->execute(array(
    ':id'   => $id,
    ':name' => $name
  ));
  
  echo $stmt->rowCount(); // 1
} catch(PDOException $e) {
  echo 'Error: ' . $e->getMessage();
}

Delete

$id = 5; // From a form or something similar

try {
  $pdo = new PDO('mysql:host=localhost;dbname=someDatabase', $username, $password);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

  $stmt = $pdo->prepare('DELETE FROM someTable WHERE id = :id');
  $stmt->bindParam(':id', $id); // this time, we'll use the bindParam method
  $stmt->execute();
  
  echo $stmt->rowCount(); // 1
} catch(PDOException $e) {
  echo 'Error: ' . $e->getMessage();
}

Object Mapping

One of the neatest aspects of PDO (mysqli, as well) is that it gives us the ability to map the query results to a class instance, or object. Here’s an example:

class User {
  public $first_name;
  public $last_name;

  public function full_name()
  {
    return $this->first_name . ' ' . $this->last_name;
  }
}

try {
  $pdo = new PDO('mysql:host=localhost;dbname=someDatabase', $username, $password);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

  $result = $pdo->query('SELECT * FROM someTable');

  # Map results to object
  $result->setFetchMode(PDO::FETCH_CLASS, 'User');

  while($user = $result->fetch()) {
    # Call our custom full_name method
    echo $user->full_name();
  }
} catch(PDOException $e) {
  echo 'Error: ' . $e->getMessage();
}

Closing Thoughts

Bottom line: if you’re still using that old mysql API for connecting to your databases, stop. Though it hasn’t yet been deprecated, in terms of education and documentation, it might as well be. Your code will be significantly more secure and streamlined if you adopt the PDO extension.

Tags: Databases
Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • David

    Hi, Jeff. Could you tell me how to get transparency in full screen mode in iTerm?What color-theme you use in your terminal.Thanks in advance

  • Andres

    Again thanks so much Jeffrey for this precious stuff!! I hope soon we can read something about PDO transaction ;) Greetings Andres

  • http://www.elatio.hr/ PVC Stolarija Elatio

    After this post YES, thx for posting this tutorial :-)

  • http://www.devsign.co.za Devsign Web Design and Web Development

    Thanks Jeffrey! A nice morning read!

  • http://www.omarstewey.com Omar Stewey

    Awesome tutorial, as usual. Two Questions:

    1. Do you need to worry about closing connections with PDO ?

    2. Is there a way to pass an array to bindParam ?

  • http://www.devgarage.com/ DevGarage

    Great tutorial. I was thinking of updating one of my scripts to PDO and this works like a cheat sheet.

  • Nobody

    Why does everybody love justin bieber?!

    • http://dice.wordpress.com kanuj bhatnagar

      Maybe because authors like to make an example out of him? Back to the topic, this is a great tutorial. Our current product runs legacy mysql_connect for connections and while PDO makes a lot of sense going forward, I truly dread converting all of those queries to make them PDO compatible. Converting the queries to prepared statements is truly going to be a long, arduous task.

      • http://rashidshafique.com rashid

        classic queries to PDO is definitely going to take time. I have a huge app built in PHP which I have to manage, it’s been a while since thinking to take it to prepared statements and update the code, seems like a nightmare, but I guess, I am going to create a DB class with convenience methods, and make the code neat too..

  • Neko

    Great tutorial, :)

  • Yas

    thank you! this is exactly what I was looking for! ;D
    I was just starting to learn about prepared statements thanks \(^-^)/ |(^-^)| \(^-^)/

  • Namal

    Another great tutorial I have read. Thanks Jef. u r awesome

  • Jock Allen

    Jeff does the object mapping example work with private properties.

    • Jock Allen

      nvm it does

  • http://www.citycent.com JoelM

    Thanks for this PDO tutorial – it is very helpful and has encouraged me to start using it.

    I think there’s a slight omission your INSERT statements that will cause them to fail – there is no field name to INSERT into.

    Those lines should be something like this:

    $stmt = $conn->prepare(‘INSERT INTO someTable (someField) VALUES(:name)’);

    • http://saiful.web.id/ Saiful

      I thought that there are no problems will be triggered with leaving the field name on the query. We only must to make sure that the values correspond correctly with the fields order in the database.

      • http://dev-tricks.com Vince

        Yeah, that would be awesome features. I love that!
        I just complete my task, and will use pdo the next.

  • dave

    Hi, How do you get a single result from a select query?

    I assumed it would be somelike $myvar = $result['mycolumn'];

    However this doesnt seem to be working?

  • Cameron

    Excellent tutorial! I’m gonna start using PDO immediately. I love the use of exceptions for error catching! Thanks!

  • http://www.vishalgkamath.totalh.net Vishal

    Nice tut jeff….. Liked it so much. Thanks ! :)

  • Kevin

    You didn’t mention MYSQLi at all after the first part of this article and once more when it comes to prepared statements; I use MySQLi and it has the same features but PDO allows more options using Prepared Statements.

    Outside of PDO allowing you to use any database driver, what are immediate reasons to choose PDO over MySqli when the database will be utilizing mySql?

    • Jamie R

      An old post, and I am assuming you would by now have figured it out. On the other hand there might be other people wondering the same thing. From what I have understood, mysql and mysqli are fairly similar, and neither protect against injections very well. I think the only real difference is that it is easier to develop with the improved version. PDO seems to be more secure as it splits the statement and the data. I might be wrong and please correct me if I am. Just how I have understood how it works.

  • Kevin

    What’s the advantage of using the execute array versus the direct bind_param method, i.e.

    $stmtt = $conn->prepare($sql);
    $stmt->bindParam(‘:title’, $_POST['title '], PDO::PARAM_STR);
    $stmt->execute();

  • Mittul Chauhan

    Hi, I m from India.

    you guys really rocks .. and always ..

    u don’t know how much i have learned from this website and still i m .. .

    keep up the good work guys .. blesses from india…

  • http://phppoet.blogspot.com jafar

    nice article brother…I heard about pdo_connnect(‘host’,'username’,'password’ ); ..method is there actually that method…?? And how can i make use of transaction in PDO..

  • http://www.appyaan.com Appyaan IT

    I really liked this article… For better development we need to use it…

  • jkns

    If you create a function/method that prepares, binds and executes a query, but want to call it multiple times from within a loop, is it best to split the function into two functions – one to prepare and one to execute? Calling the prepare function outside of the loop and then only the execute function within the loop? Otherwise you are not taking advantage of the ‘Multiple Executions’ you mentioned.

  • http://e-sushi.net/ Mike Edward Moras (e-sushi™)

    A somewhat rough but nevertheless nice PDO wrapup… I would recommend the docs at php.net though. ;)

  • claro

    Hello! your tuts is great! Thank you very much for this. But I hope there is a form for simulation.

  • Aldrin Butcon

    Hi Jeffrey,

    Do I have to put the database connection string every time or is it possible to have a one external file for configuring the database connection, and then include it in the page where you want to connect to the database? Just like what I do when I use MySQL API.

    Thanks.

    • http://www.sergeh.com Serge

      Why don’t you try and see what happens ;)

    • JD

      Yes, you can do that. It’s what I do.

  • http://silvanaweb.info Silvana

    Thank you for the tutorial, very helpful indeed.

    1. Just a question about “Flexibility”. It is true that with PDO you can ideally switch the DB, but I remember that some mysql functions are specific of mysql itself (at the point that at the exam as i have used one of those to quickly solve an issue, they blamed that I was cheating…).
    And so, is it concretely possible to switch DB?

    2. Well, I also would love to know about this passion for Justin Bieber. Is it a joke or is it true? :)

  • Chaya Cooper

    If only all tutorials were this easily understood :-D

    I had trouble connecting to the database with the syntax you suggested though, but it worked fine when I removed the ‘$’ before the username and password :-)

  • louisen

    There are actually more than 3 APIs for connecting to a mysql database, not just mysql, mysqli and PDO. There is also the ODBC API which is also database agnostic and allows prepared statements via the odbc_prepare() command. The ODBC API syntax is much simpler than PDO

    eg

    $stmt = odbc_prepare( $conn, ‘SELECT * FROM users WHERE email = ?’ );
    $success = odbc_execute( $stmt, array($email) );

    See http://php.net/manual/en/function.odbc-prepare.php

    For those of us already using odbc to connect to a database, it is a lot less work to implement parametrized queries using odbc_prepare than to switch to PDO. The only downside I see is that odbc_prepare does not allow named parameters. You must use anonymous ? placeholders.

  • D-man

    If i want this to be a class, how do I do and how should i call it?

    prepare('SELECT

    `student`.`name`,

    `student`.`age`

    FROM `student`');

    $stmt->execute();

    while ($row = $stmt->fetch()):

    ?>

    Name:

    Age:

  • http://www.goodcoresoft.com/asp-dot-net-development-company/ ASP.NET Development Company

    I think the information above is pretty awesome I like it very much. I am also a Dedicated PHP Developers so this info will be useful for
    me.

  • Bob McTavish

    Just want to say thanks. Migrating all my php code to PDO and this article has been a massive help.

  • njou

    thank you for the article, but I need help,I have an error but I do not understand
    “Fatal error: Uncaught exception ‘PDOException’ with message
    ‘SQLSTATE[3D000]: Invalid catalog name: 1046 No database selected’”
    thank you in advance

    • System0

      Had the same problem, in mysql i had the table named ‘table’ which is a keyword. Once i changed it from ‘table’ to ‘new_table’ it was ok. So make sure that you table names are not keywords used in mysql.

  • Jamie R

    Thank you a ton for this tutorial Jeffrey. A big help when getting back into php.

    I’m trying to create a small CMS for music tutorials, and I want to create everything from scratch.
    Always been uncertain how to approach injections, but by using parameters to pass the content I now see how it can become a lot more secure.

  • sunny

    Very well written tutorial. I really liked how you were clearing all the possible confusion that a reader would be having (like I had) while going through the article. thanks really helped me!

  • Gerard Buijze

    What’s the best way to handle queries like ‘select ‘.$fields.’ from ‘.$table.”.$where.”.$orderby.”; with pdo prepared statements?

  • batz

    thank you. helped me :)

  • mr. Donet

    bad spelling, incomplete examples, overall that article was a useless disappointment