LearnContact
Lesson 6124 min read

PDO (PHP Data Objects)

Learn how PDO provides a consistent, database-agnostic way to connect to and query databases from PHP, including prepared statements and error handling.

Introduction

MySQLi works well, but it is built specifically for MySQL. If your application ever needed to switch databases, or you simply wanted one consistent, modern API for database work, you would reach for PDO instead.

This lesson introduces PDO (PHP Data Objects) — how to connect, query, fetch results, and use prepared statements — and closes with a direct comparison to MySQLi so you know when to reach for each.

What You Will Learn
  • What PDO is and why it is described as "database-agnostic."
  • How to connect with new PDO(...) inside a try/catch block.
  • How to run a query and fetch results with PDO::FETCH_ASSOC.
  • How to use PDO prepared statements with prepare() and execute().
  • How PDO compares to MySQLi, and when to prefer each.

What is PDO?

PDO (PHP Data Objects) is a database abstraction layer built into PHP. Rather than being tied to one database system like MySQLi is to MySQL, PDO provides a single, consistent, object-oriented API that works across MySQL, PostgreSQL, SQLite, SQL Server, and several other database systems.

This matters because it means the same PDO code you write for MySQL today could, with only a change to the connection string, work against a different database tomorrow — the queries, fetching, and prepared statement syntax stay the same.

Database-Agnostic

One API works across MySQL, PostgreSQL, SQLite, and more.

Fully Object-Oriented

PDO only offers an OOP interface — there is no procedural style to choose between.

Exceptions by Default

PDO can be configured to throw exceptions automatically on database errors.

Prepared Statements

Built-in, first-class support for prepared statements, just like MySQLi.

Connecting with PDO

A PDO connection is created with `new PDO(...)`, passing a Data Source Name (DSN) string that describes the database type, host, and database name, along with a username and password. Because PDO throws an exception if the connection fails, the connection should be wrapped in a try/catch block.

pdo-connect.php
<?php
$dsn = "mysql:host=localhost;dbname=school;charset=utf8mb4";
$user = "root";
$password = "";

try {
    $pdo = new PDO($dsn, $user, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    echo "Connected successfully!";
} catch (PDOException $e) {
    die("Connection failed: " . $e->getMessage());
}
?>
Example Output (success)
Connected successfully!
The DSN String

The DSN ("mysql:host=localhost;dbname=school;charset=utf8mb4") tells PDO which driver to use (mysql) and how to reach the database. Switching to a different database system mainly means changing this one string.

Running a Query & Fetching Results

For queries without any external input, `query()` runs the SQL directly, and `fetchAll()` with the `PDO::FETCH_ASSOC` mode returns every row as an associative array.

pdo-select.php
<?php
$stmt = $pdo->query("SELECT id, name, course FROM students");
$students = $stmt->fetchAll(PDO::FETCH_ASSOC);

foreach ($students as $student) {
    echo $student["name"] . " - " . $student["course"] . "\n";
}
?>
Example Output
Amit Verma - PHP Development
Sara Khan - Web Design

You can also fetch one row at a time with `fetch(PDO::FETCH_ASSOC)`, which is useful when you only expect a single result.

pdo-fetch-one.php
<?php
$stmt = $pdo->query("SELECT * FROM students WHERE id = 1");
$student = $stmt->fetch(PDO::FETCH_ASSOC);

echo $student["name"];
?>
Example Output
Amit Verma

Prepared Statements in PDO

PDO prepared statements follow the same principle you learned with MySQLi — the query structure and its data are sent separately — but with a slightly different syntax. Placeholders can be positional (`?`) or named (`:name`), and values are passed as an array directly to `execute()` instead of a separate bind_param() call.

pdo-prepared-positional.php
<?php
$username = $_POST["username"];
$password = $_POST["password"];

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$username, $password]);

$user = $stmt->fetch(PDO::FETCH_ASSOC);
?>

Named placeholders can make longer queries easier to read, since each value is matched by name instead of position.

pdo-prepared-named.php
<?php
$stmt = $pdo->prepare("INSERT INTO students (name, age, course) VALUES (:name, :age, :course)");

$stmt->execute([
    "name" => "Rahul Singh",
    "age" => 20,
    "course" => "Database Systems",
]);

echo "Rows inserted: " . $stmt->rowCount();
?>
Example Output
Rows inserted: 1

Insert, Update & Delete with PDO

The same prepare-then-execute pattern applies to UPDATE and DELETE, with `rowCount()` telling you how many rows were affected.

pdo-update-delete.php
<?php
// UPDATE
$stmt = $pdo->prepare("UPDATE students SET course = :course WHERE id = :id");
$stmt->execute(["course" => "Advanced PHP", "id" => 1]);

// DELETE
$stmt = $pdo->prepare("DELETE FROM students WHERE id = :id");
$stmt->execute(["id" => 3]);
echo "Rows deleted: " . $stmt->rowCount();
?>
Example Output
Rows deleted: 1

PDO vs MySQLi

Both PDO and MySQLi connect PHP to MySQL safely when used with prepared statements. The real difference is scope and consistency, not security.

PDO

  • Works with MySQL, PostgreSQL, SQLite, and more
  • Single, consistent object-oriented API
  • Throws exceptions on errors (when configured)
  • Named or positional placeholders in prepared statements

MySQLi

  • Works with MySQL (and MySQL-compatible databases) only
  • Offers both procedural and object-oriented styles
  • Reports errors through properties/return values by default
  • Positional placeholders bound with bind_param()
Which Should You Choose?

For new projects, PDO is often preferred: it offers database portability if requirements ever change, a single consistent API to learn, and exceptions by default that make error handling cleaner. MySQLi remains a perfectly valid choice, especially in existing MySQL-only codebases.

Common Mistakes

Avoid These Mistakes
  • Forgetting to wrap new PDO(...) in a try/catch, letting a connection failure crash with an unhandled exception.
  • Not setting PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION and missing errors that fail silently.
  • Mixing up named and positional placeholders within the same prepared statement.
  • Concatenating user input into a PDO query string instead of using prepare() and execute().

Best Practices

  • Always connect inside a try/catch block and handle PDOException explicitly.
  • Set PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION right after connecting.
  • Use prepared statements with execute([...]) for every query involving external input.
  • Pick named placeholders for longer queries and positional placeholders for short, simple ones.

Frequently Asked Questions

Is PDO faster than MySQLi?

Performance differences between them are negligible for almost all applications. The choice usually comes down to portability and API consistency, not speed.

Can PDO connect to databases other than MySQL?

Yes — that is its main advantage. Changing the DSN string to a different driver (such as pgsql or sqlite) lets the same PDO code target a different database system.

Do I still need prepared statements with PDO?

Yes. PDO prevents SQL injection only when you use prepare() and execute() with placeholders, exactly the same principle as MySQLi prepared statements.

What does PDO::FETCH_ASSOC do?

It tells PDO to return each row as an associative array keyed by column name, rather than a numerically indexed array or an object.

Should I rewrite existing MySQLi code to use PDO?

Not necessarily. If existing MySQLi code already uses prepared statements and only ever targets MySQL, there is no urgent need to switch — PDO is mainly a stronger choice for new projects.

Key Takeaways

  • PDO is a database-agnostic abstraction layer that works with MySQL and many other database systems through one API.
  • Connect with new PDO($dsn, $user, $password) inside a try/catch, since PDO throws PDOException on error.
  • Fetch results with fetch() or fetchAll() using the PDO::FETCH_ASSOC mode.
  • PDO prepared statements use prepare() plus execute([...]) with positional or named placeholders.
  • PDO is often preferred for new projects thanks to portability, a consistent OOP API, and exceptions by default.

Summary

PDO gives you one consistent, exception-driven, object-oriented way to talk to MySQL and many other databases, with prepared statements built in from the start.

In this lesson, you connected with PDO inside a try/catch, queried and fetched data with PDO::FETCH_ASSOC, used prepared statements with named and positional placeholders, and compared PDO directly to MySQLi. Next, you will use these database skills to build a real authentication and login system.

Lesson 61 Completed
  • You understand what makes PDO database-agnostic.
  • You can connect with new PDO(...) inside a try/catch.
  • You can query, fetch, and use prepared statements with PDO.
  • You can explain when to choose PDO over MySQLi.
Next Lesson →

Authentication & Login System