A fetch mode sets the shape of each row: FETCH_ASSOC (the db.php default) an associative array, FETCH_NUM a list, FETCH_OBJ a stdClass, FETCH_COLUMN one column, and FETCH_CLASS an object of your own class with typed properties and methods. With fetchAll(), FETCH_KEY_PAIR turns two columns into key => value, FETCH_GROUP groups rows by the first column, and FETCH_UNIQUE keys them by it.
<?php
// Product.php: one row of the products table
final readonly class Product {
public int $id;
public string $sku;
public string $title;
public string $price; // DECIMAL arrives as a string: keep it exact
public int $stock;
public function label(): string { return "$this->sku $this->title ($this->price)"; }
}<?php
$pdo = require 'db.php';
require 'Product.php';
$st = $pdo->query('SELECT id, sku, title, price, stock FROM products WHERE price > 44');
foreach ($st->fetchAll(PDO::FETCH_CLASS, Product::class) as $p) {
echo $p->label(), "\n";
}
$st = $pdo->query('SELECT sku, stock FROM products WHERE stock < 10');
echo json_encode($st->fetchAll(PDO::FETCH_KEY_PAIR)), "\n";
$st = $pdo->query('SELECT c.name, p.sku FROM products p JOIN categories c
ON c.id = p.category_id WHERE c.id IN (3, 5) ORDER BY p.sku');
echo json_encode($st->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_COLUMN)), "\n";BK-SQL-01 SQL Queries That Scale (44.50)
BK-LAR-01 Laravel From the Ground Up (49.00)
{"BK-SQL-02":0,"BK-UX-01":7,"BK-LNX-01":9}
{"Accessories":["AC-MUG-01","AC-STK-01"],"Databases":["BK-SQL-01","BK-SQL-02"]}FETCH_CLASS sets the properties before the constructor runs (FETCH_PROPS_LATE reverses that) and may initialize readonly properties. Select exactly the declared columns: an extra category_id fails with "Cannot create dynamic property Product::$category_id". Typed properties convert on the way in, so a float $price would turn 44.50 into 44.5 (Subsection 4.16.12).