PHP 8: Modern PHP Development · Databases with PDO
Repositories and Data Mapping
Separate SQL from business logic with repositories that return domain objects.
A repository encapsulates data access for an aggregate behind an interface (OrderRepository::find(), save()), mapping rows to domain objects and back. Business code depends on the interface, so tests can use an in-memory fake and you can change storage without touching domain logic. Avoid N+1 queries by fetching related rows in bulk.
<?php
interface OrderRepository
{
public function find(int $id): ?Order;
public function save(Order $order): void;
}
final class PdoOrderRepository implements OrderRepository
{
public function __construct(private PDO $pdo) {}
public function find(int $id): ?Order
{
$stmt = $this->pdo->prepare('SELECT * FROM orders WHERE id = ?');
$stmt->execute([$id]);
$row = $stmt->fetch();
return $row ? $this->hydrate($row) : null;
}
public function save(Order $order): void
{
$this->pdo->prepare('UPDATE orders SET status = :status, total_cents = :total WHERE id = :id')
->execute(['status' => $order->status->value, 'total' => $order->total->amount, 'id' => $order->id]);
}
private function hydrate(array $row): Order
{
return new Order((int) $row['id'], OrderStatus::from($row['status']), new Money((int) $row['total_cents'], $row['currency']));
}
}
Common pitfalls
- Leaking SQL or PDO types into controllers and domain code.
- Generic repositories with dozens of query methods — add only what use cases need.
- Lazy loading relations in loops (N+1).
Key points
- Repositories hide persistence details
- Return domain objects, not raw rows
- Fakes make domain tests fast
Further reading: Martin Fowler: Repository pattern
Practice questions
What's the benefit of a repository interface in tests?
Answer
Domain/application services can be tested with an in-memory implementation, avoiding a database and making tests fast and deterministic.
How do you avoid N+1 queries when loading orders with their items?
Answer
Load all items for the fetched order IDs in one query (WHERE order_id IN (...)) and group them in PHP, or use a JOIN, instead of querying per order.
Open in the app to mark lessons complete, take notes and track your progress.