How to query mysql using PDO in PHP?

October 26, 2018

Years ago you used to see mysql_... functions everywhere. This has been (fortuantely) replaced by first the mysqli functions and more recently the object oriented way of PDO. Using PDO you can easily swap between database drivers (Mysql, Postgre, SQLite, etc). Here is how to connect to a mysql database, and then how to query it using PDO.

This also applies to mariadb

Connecting to MySQL with PDO in PHP

You should wrap this in a try/catch to handle a database connection error.

  try {
    $host = "your host";
    $dbname = "your_database";
    $user = "your_user";
    $pass = "qwerrtyuiop12345678";
    $dbh = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
  catch(PDOException $e) {
      echo  $e->getMessage(); // handle this better, this is just for the demo!

How to query the database

With PDO you don't have to think about escaping data. Every query should be 'prepared', then you can bind variables to placeholders. If you follow this example it should make sense.

In this example, we will do an insert and create a blog post with the following data:

$title = "Blog post title";
$post_body = "Blog post body";
$status = "published_status";

Unnamed placeholders

  $prepared_statement = $dbh->("INSERT INTO blog_posts (title, post_body, status) values (?, ?, ?)");

These are indexed, starting at 1.

$prepared_statement->bindParam(1, $title);
$prepared_statement->bindParam(2, $post_body);
$prepared_statement->bindParam(3, $status);

And then you execute it:


Named placeholders

You can also used named placeholders. They don't have to match anything else in the sql statement.

Although you will see a : before their names (such as :title) here and in most code, they aren't required. But they are a common convention that you should follow!

  $prepared_statement = $dbh->("INSERT INTO blog_posts (title, post_body, status) values ( :title, :body, :post_status )");

Now that we have prepared the statement and used some named params, we need to bind them:

$prepared_statement->bindParam(":title", $title);
$prepared_statement->bindParam(":body", $post_body);
$prepared_statement->bindParam(":post_status", $status);

And then you execute it:


Alternative ways to do this:

You can also pass an array to ->execute with an array:

  $prepared_statement = $dbh->("INSERT INTO blog_posts (title, post_body, status) values ( :title, :body, :post_status )");
      "title" =>"my new blog post",
      "body" => "Welcome to my new blog",
      "post_status" => "published_status",
  // or
  $prepared_statement = $dbh->("INSERT INTO blog_posts (title, post_body, status) values ( ?,  ?, ? )");
      "my new blog post",
      "Welcome to my new blog",

An important thing to note about bindParam...

If you look at the method structure:

bool PDOStatement::bindParam ( mixed $parameter , mixed &$variable [, int $data_type = PDO::PARAM_STR [, int $length [, mixed $driver_options ]]] )

You might notice the second parameter. It is &$variable - by reference. So if you did this:

    $title = "title 1";
  $prepared_statement = $dbh->("INSERT INTO blog_posts (title) values ( :title)");
  $prepared_statement->bindParam(1, $title);
  $title = "updated title here";

Then a new row would be inserted with "updated title here".

Why? because sent $title to bindParam, which saves the reference. You then update $title to "updated title here", so when you run ->execute() it uses whatever value is in $title, which is obviously the updated value.

Use bindValue() to avoid this issue.

How to get rows from the database?

By default PDO will return an array indexed by both column name and number (e.g. [1=> "some blog title", 'blog_title' => "some blog title", 2 => "some blog post body" , "post_body" => "some blog post body"].

There are a few ways to change this. You can use $statement->setFetchMode(...) (or $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);) to change this behaviour.

If I want to just work with an array I will normally use PDO::FETCH_ASSOC. The fetchObject method is nice too - it fetches the next row and returns it as an object. You can define what class it will create, so you can set it up to return model objects.

But anyway, on to some examples.

SELECT queries...

I am going to use the direct ->query() here, avoiding the binding and execute.

$query = $dbh->query("SELECT * FROM blog_posts");
foreach ($query as $row)

But I could also do this:

$statement = $dbh->prepare("SELECT * FROM blog_posts WHERE id = ?");
$statement->execute([ $blog_post_id] );
//get one row:
$row = $statement->fetch();
$statement = $dbh->prepare("SELECT * FROM blog_posts WHERE category_id = ?");
$statement->execute([ $category_id] );
// get all:
$rows = $statement->fetchAll();

Update and delete

You can run these in a similar manner.

Delete SQL statement with PDO

$statement = $dbh->prepare("DELETE FROM blog_posts WHERE category_id = ?");
$statement->execute([ $category_id] );

Update SQL statement with PDO

$new_title = "foobar";
$statement = $dbh->prepare("UPDATE blog_posts set title = ? WHERE id = ?");
$statement->execute([ $title, $id] );