PDO (PHP Data Objects)
PDO is PHP's database-agnostic data access layer. Rather than
providing one extension per database engine with its own quirks and
function names, PDO gives you a single PDO class with a consistent
set of methods, and lets a separate driver underneath handle the
differences between MySQL, PostgreSQL, SQLite, SQL Server, and
others. This page covers connecting, configuring error handling,
the difference between ->query() and ->prepare(), and fetching
results — the core vocabulary you'll use on almost every PDO page
that follows.
Connecting with a DSN
A PDO connection is described by a DSN (Data Source Name) — a single string that tells PDO which driver to use and how to reach the database. The driver name comes first (mysql:, pgsql:, sqlite:), followed by driver-specific options.
Connecting to MySQL
<?php $dsn = 'mysql:host=localhost;port=3306;dbname=shop;charset=utf8mb4'; $pdo = new PDO($dsn, 'app_user', 'secret');
Connecting to SQLite (just a file path — no server)
<?php
$pdo = new PDO('sqlite:/var/data/shop.sqlite');Notice that the SQLite example doesn't take a username, password, or host — there's no server to authenticate against, since SQLite reads and writes a single file directly. That's the DSN doing its job: the driver-specific details live inside the string, while the rest of your code that runs queries against $pdo stays identical no matter which driver is in use.
Making PDO throw exceptions on errors
By default, PDO's error mode is fairly quiet — some failures return false instead of raising anything, which makes them easy to miss. Setting PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION makes any database error throw a PDOException instead, so failures surface immediately and can be handled with normal try/catch blocks rather than manual if checks after every call.
Enabling exception mode at connection time
<?php
try {
$pdo = new PDO(
'mysql:host=localhost;dbname=shop;charset=utf8mb4',
'app_user',
'secret',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
} catch (PDOException $e) {
// Log the real message; never show it to the visitor
error_log('Database connection failed: ' . $e->getMessage());
die('Something went wrong. Please try again later.');
}->query() for fixed SQL, ->prepare() for anything with input
->query() runs a SQL string immediately and hands back a
PDOStatement you can iterate over. It's appropriate only when the
SQL contains no data from outside your own code — a static report, a
SHOW TABLES, or similar.
A query with no external input
<?php
$statement = $pdo->query('SELECT COUNT(*) AS total FROM products');
$row = $statement->fetch();
echo 'Total products: ' . $row['total'];Total products: 128
The same idea, done safely with prepare()
<?php
$minPrice = $_GET['min_price'] ?? 0;
$statement = $pdo->prepare('SELECT id, name, price FROM products WHERE price >= ?');
$statement->execute([$minPrice]);
$products = $statement->fetchAll(); // an array of associative arraysFetching results
PDO::FETCH_ASSOC is the most commonly used fetch mode: each row
comes back as an associative array keyed by column name.
->fetch() returns one row at a time (or false when there are no
more), while ->fetchAll() returns every remaining row as a single
array, which is convenient for smaller result sets.
Row-by-row vs all-at-once
<?php
$statement = $pdo->query('SELECT id, name FROM products');
// One row at a time
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
echo $row['name'] . PHP_EOL;
}
// Or, all rows in one call
$statement = $pdo->query('SELECT id, name FROM products');
$allProducts = $statement->fetchAll(PDO::FETCH_ASSOC);Switching database drivers with minimal code change
This is the core value proposition of PDO: because ->prepare(),
->execute(), and ->fetch() behave the same no matter which
driver is loaded, moving an application from MySQL to PostgreSQL —
or spinning up a lightweight SQLite copy for local development or
testing — is often just a matter of changing the DSN string.
Same query code, different DSN
<?php
// Production
$pdo = new PDO('mysql:host=db.prod;dbname=shop;charset=utf8mb4', $user, $pass);
// Local development
$pdo = new PDO('sqlite:' . __DIR__ . '/dev.sqlite');
// The rest of the application's code is unchanged:
$statement = $pdo->prepare('SELECT * FROM products WHERE id = ?');
$statement->execute([$productId]);The caveat is that this only holds for SQL that's portable across engines. Vendor-specific syntax — MySQL's LIMIT x, y shorthand, PostgreSQL's RETURNING clause, or engine-specific functions — will still need adjusting if you actually change databases. PDO makes the connection and fetching layer portable; it doesn't rewrite your SQL for you.
Build the DSN for your driver, then create the
PDOobject with a username and password.Set
PDO::ATTR_ERRMODEtoPDO::ERRMODE_EXCEPTIONso failures throw instead of failing silently.Use
->query()only for SQL with no outside input; use->prepare()plus->execute()for everything else.Fetch with
PDO::FETCH_ASSOC(or set it as the default fetch mode) to get column-name-keyed arrays.