Error handling
Every database failure surfaces as a single exception type, CDOException, which
always wraps the original PDOException. That means one catch for the whole DML
surface, with the full SQLSTATE detail one getPrevious() away.
The exception
Flytachi\Winter\Cdo\Connection\CDOException
└── extends \RuntimeExceptionCDOException is a thin RuntimeException subclass. It carries a human-readable
message describing the operation, and its $previous is the underlying
PDOException — preserving the SQLSTATE code, the driver’s message, and the stack
trace.
When each method throws
| Scenario | Method |
|---|---|
| PDO connection failed | CDO::__construct() |
INSERT failed |
insert(), insertGroup() |
UPDATE failed |
update() |
DELETE failed |
delete() |
| Upsert query failed | upsert(), upsertGroup() |
conflictColumns is empty |
upsert(), upsertGroup() |
in() / notIn() throw a different type
Passing an empty array to Qb::in() / Qb::notIn() throws
InvalidArgumentException (not CDOException) — it’s a builder-time programming
error, caught before any query runs. See Qb operators.
Catching failures
Catch CDOException, then reach into the previous PDOException for the code:
use Flytachi\Winter\Cdo\Connection\CDOException;
try {
$cdo->insert('users', ['email' => 'alice@example.com', 'name' => 'Alice']);
} catch (CDOException $e) {
echo $e->getMessage(); // "Error when creating a record in the database (...)"
$pdo = $e->getPrevious(); // the original PDOException
echo $pdo?->getCode(); // SQLSTATE, e.g. "23505"
echo $pdo?->getMessage(); // driver message
}Reacting to a specific error
Branch on the SQLSTATE code from the wrapped exception:
try {
$cdo->insert('users', $data);
} catch (CDOException $e) {
$pdo = $e->getPrevious();
// 23xxx = integrity constraint (unique / FK) on both PG and MySQL
if ($pdo && str_starts_with((string) $pdo->getCode(), '23')) {
throw new DuplicateException('Already exists');
}
throw $e; // re-throw anything unrecognised
}SQLSTATE reference
Common codes seen via $e->getPrevious()->getCode():
| Code | Meaning | Database |
|---|---|---|
23000 |
Integrity constraint violation | MySQL / MariaDB |
23505 |
Unique violation | PostgreSQL |
23503 |
Foreign key violation | PostgreSQL |
42P01 |
Undefined table | PostgreSQL |
42000 |
Syntax error | MySQL / MariaDB |
08006 |
Connection failure | PostgreSQL |
HY000 |
General error | Various |
Retry-on-failure pattern
For transient connection loss, reconnect once and retry:
try {
$id = ConnectionPool::db(AppDb::class)->insert('events', $event);
} catch (CDOException $e) {
ConnectionPool::getConfigDb(AppDb::class)->reconnect();
$id = ConnectionPool::db(AppDb::class)->insert('events', $event);
}Related
- Logging & diagnostics — health checks and reconnects
- CDO API — what each method throws
- Qb operators — builder-time validation errors