How to Extract Raw SQL from Query Builder in PHP: 3 Working Methods

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

"Doctor, my Query Builder is generating something strange..."

A familiar situation: you write an elegant chained query using Query Builder, confident that everything works perfectly, but the output is either an error or incorrect data. The first thing that comes to any PHP developer's mind: "Show me what you're actually generating!"

The question "How do I get the query builder to output its raw SQL query as a string?" is one of the most popular on Stack Overflow (thousands of views and dozens of answers). And this is no coincidence. The ability to quickly get clean SQL is a superpower that saves hours of debugging.

In this article, we will cover 3 working methods for PHP frameworks (Laravel, Doctrine, CakePHP), show real code examples, and explain when to use each method. Let's go!



Why is this even needed? (Briefly and to the point)

  • Debugging — understand why a query returns incorrect data.
  • Optimization — see which JOINs and WHEREs are actually executed.
  • Learning — beginners often don't understand how Query Builder translates methods into SQL.
  • Logging — save the raw query for auditing or replay.

Ready? Then let's move to the code!



Method 1: Laravel Eloquent / Query Builder — Classic

In Laravel, there are several ways, but the most reliable is to use toSql() together with getBindings(). Why together? Because toSql() shows the query with ? placeholders, and without substituting values it's useless.



Simple example: SELECT with WHERE

use App\\Models\\User;

$query = User::where('email', 'like', '%@gmail.com') ->where('active', 1) ->orderBy('created_at', 'desc');

// Get SQL with placeholders$sql = $query->toSql();$bindings = $query->getBindings();

// Assemble everything into a readable form$fullSql = vsprintf(str_replace('?', '%s', $sql), $bindings);

echo $fullSql;// Result:// SELECT * FROM users WHERE email LIKE '%@gmail.com' AND active = 1 ORDER BY created_at DESC

Important: vsprintf is not a panacea. For complex data types (e.g., NULL or arrays), it's better to use Str::replaceArray() or the laravel-query-builder-debug package.



Advanced variant: with join and whereIn

$query = DB::table('orders')    ->join('users', 'orders.user_id', '=', 'users.id')    ->whereIn('orders.status', ['pending', 'processing'])    ->where('orders.total', '>', 100)    ->select('orders.*', 'users.name');

$bindings = $query->getBindings();$sql = $query->toSql();

// Format nicely$fullSql = preg_replace_array('/\\?/', $bindings, $sql);echo $fullSql;// SELECT orders.*, users.name FROM orders INNER JOIN users ON orders.user_id = users.id WHERE orders.status IN ('pending', 'processing') AND orders.total > 100

Tip: Use preg_replace_array() — it's safer than vsprintf, especially when the data contains the % character.



Method 2: Doctrine DBAL — For Those Who Like Strictness

Doctrine Query Builder (often used in Symfony) is a more complex beast. There is no ready-made toSql() here, but there is getSQL() and getParameters(). Plus, you need to account for parameter types.



Basic example

use Doctrine\\DBAL\\Connection;use Doctrine\\DBAL\\Query\\QueryBuilder;

/** @var Connection $connection */$queryBuilder = $connection->createQueryBuilder();$queryBuilder ->select('p.*', 'c.name as category_name') ->from('products', 'p') ->join('p', 'categories', 'c', 'p.category_id = c.id') ->where('p.price > :minPrice') ->andWhere('p.sto

Blogs

Book Recommendations