-
Notifications
You must be signed in to change notification settings - Fork 0
Debugging
When a query goes wrong in production, the default PDOException message is often not enough on its own — you get the SQLSTATE code and a vendor message, but not the actual SQL or parameters that ran. The package gives you three complementary tools.
DB::createImmutable([
'dsn' => '...',
'username' => '...',
'password' => '...',
'debug' => true,
]);When debug is on, any SQLExecuteException thrown by the driver is wrapped with the compiled SQL and the bound parameters appended to the message. The original PDOException is preserved as getPrevious() so stack traces stay intact.
Example of a debug-enabled message:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version for the right syntax to use
near 'fROM users' at line 1
-- Query: sELECT * fROM users WHERE id = :id
-- Args: {":id":13}
Production caveat: debug-mode messages can leak sensitive data into error reporters / log files. Keep it off in production, or sanitise before forwarding to third-party services.
For "why is this slow" or "is this query even being issued" questions:
DB::enableQueryLog();
// run the suspect code path...
print_r(DB::getQueryLogs());
DB::disableQueryLog();See Query Logging for the full surface — including how to wire it into a request middleware.
DB::createImmutable([
'dsn' => '...',
'log' => function (string $msg): void {
$GLOBALS['app']->logger->error('[DB] ' . $msg);
},
]);Connection-level errors land in the channel even when debug is off — see Query Logging → the log channel for the four supported shapes.
When a query is misbehaving, work down this list:
DB::enableQueryLog();
$result = $myCode();
var_dump(DB::getQueryLogs());If the buffer is empty, the suspect code never reached the database. The bug is upstream.
The log buffer prints the exact prepared SQL. Eyeball it before reaching for any other tool. Common surprises:
- A
where()you thought was there is missing (chain broken by an early return). - An
orderBy()is duplicated (you called it twice in different code paths). - Identifier escaping wrapped a function name (
COUNT(*)) — useDB::raw()for SQL fragments.
The log buffer also prints the bound args:
['query' => '… WHERE id = :id', 'args' => [':id' => '13']]Note the string '13' — if your query is failing on a strict type comparison, this might be why. Coerce earlier.
Copy the query field straight into a database CLI / GUI and substitute the placeholders. If the SQL runs there, the bug is in your application logic; if it fails there too, you have an SQL bug, not a PHP bug.
For exceptions that escape your normal try / catch, switch debug on, reproduce once, switch it back off. The full SQL + args in the exception message is usually enough to find the cause.
When the package cannot reach the database at all, the error comes from Connection itself rather than from a query:
InitORM\DBAL\Connection\Exceptions\ConnectionException:
SQLSTATE[HY000] [2002] Connection refused
Things to check:
| Symptom | Likely cause |
|---|---|
Connection refused |
Wrong host / port; database server not running. |
Access denied for user |
Wrong username / password / database name. |
Unknown database |
database field points at a database that doesn't exist. |
could not find driver |
ext-pdo_<driver> not installed (pdo_mysql, pdo_pgsql, pdo_sqlite). |
SQLSTATE[HY000] [14] unable to open database file (SQLite) |
The file path is not writable, or the parent directory does not exist. |
For SQLite specifically: every PDO handle gets its own :memory: database, so any schema you applied to handle A is invisible to handle B. Either share the handle or use a file path.
$res = DB::select('*')->from('users')->read();
var_dump($res->numRows()); // 0 — but I have data?!This is the SQLite SELECT caveat — PDOStatement::rowCount() is unreliable for SELECT on drivers that do not buffer results. Use count($res->rows()) instead:
$rows = DB::select('*')->from('users')->read()->asAssoc()->rows();
var_dump(count($rows));For INSERT / UPDATE / DELETE on common drivers, numRows() / affectedRows() is reliable.
If you used group() with bound parameters inside the callback (e.g. via orLike) and the result is empty even though matching rows exist, you are hitting a known limitation in initorm/query-builder 2.x — parameters bound inside group() do not propagate to the outer builder. See the warning in Query Builder.
Workaround pattern (the DataTables helper uses this):
$clauses = [];
$params = [];
foreach ($columns as $i => $col) {
$key = ':search_' . $i;
$clauses[] = "{$col} LIKE {$key}";
$params[$key] = '%' . $needle . '%';
}
DB::where(DB::raw('(' . implode(' OR ', $clauses) . ')'));
foreach ($params as $key => $value) {
DB::setParameter($key, $value);
}-
Query Logging —
enableQueryLogand thelogchannel. -
Configuration — the
debug/log/queryLogsfields. - Troubleshooting — symptom-driven fix list.
initphp/database · MIT License · part of the InitPHP family
Source · Issues · Discussions · Packagist · Contributing · Security Policy
Getting Started
Core API
ORM
Advanced
DataTables Helper
Recipes
- Index
- — Pagination
- — Search & Filters
- — Upsert / REPLACE INTO
- — Audit Log
- — DataTables Bootstrap
- — Repository Pattern
Reference
Migration & Help