Introduction
PDO offers multiple strategies for retrieving and structuring query results. Choosing the right fetch mode eliminates boilerplate code and lets you map database rows directly into the data structures your application needs.
Key Concepts
- Fetch Mode: A PDO constant that determines the format of returned rows (array, object, class instance).
- Hydration: The process of populating an object's properties from a database row.
- FETCH_CLASS: A fetch mode that instantiates a specified class and maps columns to its properties.
Real World Context
In a real application, you rarely want raw associative arrays flowing through your business logic. Fetch modes let you go from database rows to typed domain objects in a single step, improving IDE support and reducing mapping code.
Deep Dive
The most commonly used fetch modes each serve a distinct purpose:
php<?php $stmt = $pdo->query('SELECT id, name, email FROM users'); // FETCH_ASSOC - Associative array (recommended default) $user = $stmt->fetch(PDO::FETCH_ASSOC); // ['id' => 1, 'name' => 'John', 'email' => 'john@example.com'] // FETCH_NUM - Numeric array $user = $stmt->fetch(PDO::FETCH_NUM); // [1, 'John', 'john@example.com'] // FETCH_OBJ - stdClass object $user = $stmt->fetch(PDO::FETCH_OBJ); // $user->id, $user->name, $user->email
For domain-driven code, FETCH_CLASS hydrates results directly into your classes:
php<?php class User { public int $id; public string $name; public string $email; public function getDisplayName(): string { return $this->name; } } $stmt = $pdo->query('SELECT id, name, email FROM users'); $stmt->setFetchMode(PDO::FETCH_CLASS, User::class); foreach ($stmt as $user) { echo $user->getDisplayName(); }
Use FETCH_PROPS_LATE when your class has a constructor that sets defaults:
php<?php $stmt->setFetchMode( PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, User::class, ['constructorArg1'] );
Bulk fetch operations provide powerful data restructuring:
php<?php // Single column $emails = $stmt->fetchAll(PDO::FETCH_COLUMN, 2); // Key-value pairs $stmt = $pdo->query('SELECT id, name FROM users'); $names = $stmt->fetchAll(PDO::FETCH_KEY_PAIR); // [1 => 'John', 2 => 'Jane'] // Grouped by first column $stmt = $pdo->query('SELECT role, id, name FROM users'); $byRole = $stmt->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_ASSOC); // Unique by first column $indexed = $stmt->fetchAll(PDO::FETCH_UNIQUE | PDO::FETCH_ASSOC);
Custom hydration via callback:
php<?php $users = $stmt->fetchAll(PDO::FETCH_FUNC, function($id, $name, $email) { return new User((int) $id, $name, $email); });
Common Pitfalls
- Using FETCH_BOTH as the default — It doubles memory usage by storing each value under both numeric and string keys.
- Forgetting FETCH_PROPS_LATE with constructors — Without this flag, PDO sets properties before calling the constructor, which can overwrite them.
Best Practices
- Set FETCH_ASSOC as the connection default — Configure it once in your PDO options so every query benefits.
- Use FETCH_KEY_PAIR for lookup tables — Builds an id-to-name map in one call without manual looping.
Summary
- PDO provides multiple fetch modes for different data structure needs.
FETCH_ASSOCis the best default;FETCH_CLASSbridges the gap to domain objects.- Bulk fetch variations like
FETCH_KEY_PAIRandFETCH_GROUPeliminate manual array restructuring. - Use
FETCH_PROPS_LATEwhen hydrating classes that have constructors.
Code Examples
<?php
declare(strict_types=1);
class UserRepository {
public function __construct(private PDO $pdo) {}
public function findById(int $id): ?User {
$stmt = $this->pdo->prepare(
'SELECT id, name, email, created_at FROM users WHERE id = :id'
);
$stmt->execute(['id' => $id]);
$stmt->setFetchMode(PDO::FETCH_CLASS, User::class);
return $stmt->fetch() ?: null;
}
/** @return User[] */
public function findAll(): array {
$stmt = $this->pdo->query(
'SELECT id, name, email, created_at FROM users ORDER BY name'
);
$stmt->setFetchMode(PDO::FETCH_CLASS, User::class);
return $stmt->fetchAll();
}
/** @return array<int, string> */
public function getEmailsById(): array {
$stmt = $this->pdo->query('SELECT id, email FROM users');
return $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
}
}
?>