pyaesoneaung/to-raw-sql
Inspect the SQL behind your Laravel query builder and Eloquent queries. to-raw-sql converts builders into readable raw SQL strings, making debugging, logging, and optimization simple. A lightweight helper with a clean API for development.
Install it the usual way — the service provider is auto-discovered, so there is no config file to publish and nothing to register in config/app.php:
composer require pyaesoneaung/to-raw-sql
The package registers a toRawSql() macro on both Illuminate\Database\Query\Builder and Illuminate\Database\Eloquent\Builder, so the first thing to do is open php artisan tinker and call it on a query you already know the answer to:
User::where('active', true)->where('created_at', '>', now()->subWeek())->toRawSql();
// select * from `users` where `active` = 1 and `created_at` > '2024-01-01 00:00:00'
That is the entire mental model: toSql() gives you the statement with ? placeholders, toRawSql() gives you the same statement with the bindings substituted in, exactly as the driver would see it. The first real use case is debugging a query that "should work" — a whereIn with an empty array, a mis-ordered orWhere group, a date boundary — where seeing the interpolated values removes all ambiguity.
Look first at the package's service provider (it is a handful of lines) to confirm which builder classes get the macro, then verify against your Laravel version before you build habits around it — see the Gotchas section.
Debugging in place. The highest-value pattern is dropping toRawSql() next to an existing dump()/dd() chain while you are chasing a bug, then removing it. Because it is non-executing, it is safe to call on a builder you are about to run:
$query = Order::where('status', 'paid')->whereBetween('total', [$min, $max]);
logger()->debug($query->toRawSql());
$orders = $query->paginate(25); // builder is untouched
Logging the SQL of slow or suspicious code paths. Wrap it in a tiny helper rather than sprinkling toRawSql() everywhere, so you can strip it out later:
function sql_of($query): string
{
return $query->toRawSql();
}
This is genuinely useful inside queue jobs and console commands, where you cannot dd() and where the actual query only runs inside a transaction you would rather not break.
Reproducing a bug for a DBA or another developer. Because the output is a copy-pasteable statement, it is the fastest way to hand a query to someone with a MySQL client open. Pair it with ->toSql()/->getBindings() when the other party needs the parameterised form (some DBAs prefer that for EXPLAIN):
[$sql, $bindings] = [$query->toSql(), $query->getBindings()];
In tests, as a readable failure message. When an assertion about a dataset fails, dumping the raw SQL of the query under test is often faster than reading the stack trace:
$this->assertSame(3, $query->count(), $query->toRawSql());
Note that the third argument is only evaluated on failure in most assertion helpers, but toRawSql() is cheap enough that the eager form is fine here.
Admin/debug panels behind a gate. If you have an internal "query inspector" screen (Horizon-adjacent tooling, an artisan command, a dev-only route), wrap the call so it never leaks into production:
if (app()->isLocal()) {
dump($query->toRawSql());
}
Works across both builders. DB::table('users')->where(...)->toRawSql() and the Eloquent equivalent behave the same, so it is useful when you are rewriting a query-builder call into Eloquent (or vice versa) and want to prove the two produce the same SQL.
toRawSql() natively. If you are on a recent Laravel, this package is redundant — the framework's Illuminate\Database\Query\Builder::toRawSql() does the same job and handles binding substitution more robustly. Check Builder::hasMacro('toRawSql') or just try the call without the package installed before adding a dependency. This is the single biggest reason the package is a hard sell on new projects; it is realistically a tool for Laravel 8/9 codebases or legacy apps pinned below 10.15.whereJsonContains variations, new where* methods added since). Test the specific query shape you depend on. Dependents count is effectively zero, which tells you the community has moved to the native method.? inside a raw fragment will corrupt the output: whereRaw("name LIKE '%?%'") or a whereRaw with a ? inside a quoted string will have the wrong binding spliced in. The same applies to ?? in some grammars. If your query uses whereRaw with inline literals, verify the output by eye before trusting it.DB::select().get(), first(), paginate(), count() all execute. toRawSql() must be called on the builder before those, and it does not execute anything itself — it is a pure read of toSql() plus bindings.toRawSql() into a global DB::listen or exception handler without a redaction step. For production observability, prefer DB::listen() with QueryExecuted and a binding scrubber.? for a prepared statement viewer), define your own macro under a different name — do not shadow toRawSql, because a future Laravel upgrade will collide with it.$1 placeholders, MySQL backticks), you will be writing your own macro; this package only covers the default grammar path.How can I help you explore Laravel packages today?