«Doutor, meu Query Builder está gerando algo estranho…»
\nSituação familiar: você escreve uma consulta encadeada elegante no Query Builder, tem certeza de que tudo funciona perfeitamente, mas no final — ou um erro, ou dados incorretos. A primeira coisa que vem à mente de qualquer desenvolvedor PHP: «Mostre-me o que você está realmente gerando!».
\nA pergunta «How do I get the query builder to output its raw SQL query as a string?» é uma das mais populares no Stack Overflow (milhares de visualizações e dezenas de respostas). E não é por acaso. A capacidade de obter rapidamente um SQL limpo é um superpoder que economiza horas de depuração.
\nNeste artigo, abordaremos 3 métodos funcionais para frameworks PHP (Laravel, Doctrine, CakePHP), mostraremos exemplos reais de código e explicaremos quando usar cada método. Vamos lá!
\n\nPor que isso é necessário? (Resumido e direto ao ponto)
\n- \n
- Depuração — entender por que a consulta retorna dados incorretos. \n
- Otimização — ver quais JOINs e WHEREs estão realmente sendo executados. \n
- Aprendizado — iniciantes muitas vezes não entendem como o Query Builder traduz métodos em SQL. \n
- Registro de logs — salvar a consulta bruta para auditoria ou repetição. \n
Pronto? Então vamos ao código!
\n\nMétodo 1: Laravel Eloquent / Query Builder — clássico
\nNo Laravel, existem várias maneiras, mas a mais confiável é usar toSql() junto com getBindings(). Por que juntos? Porque toSql() mostra a consulta com placeholders ?, e sem substituir os valores, é inútil.
Exemplo simples: SELECT com WHERE
\nuse App\\Models\\User;\n\n$query = User::where('email', 'like', '%@gmail.com')\n ->where('active', 1)\n ->orderBy('created_at', 'desc');\n\n// Obtém o SQL com placeholders\n$sql = $query->toSql();\n$bindings = $query->getBindings();\n\n// Monta tudo em um formato legível\n$fullSql = vsprintf(str_replace('?', '%s', $sql), $bindings);\n\necho $fullSql;\n// Resultado:\n// SELECT * FROM users WHERE email LIKE '%@gmail.com' AND active = 1 ORDER BY created_at DESC\nImportante: vsprintf não é uma solução universal. Para tipos de dados complexos (por exemplo, NULL ou arrays), é melhor usar Str::replaceArray() ou o pacote laravel-query-builder-debug.
Variante avançada: com join e whereIn
\n$query = DB::table('orders')\n ->join('users', 'orders.user_id', '=', 'users.id')\n ->whereIn('orders.status', ['pending', 'processing'])\n ->where('orders.total', '>', 100)\n ->select('orders.*', 'users.name');\n\n$bindings = $query->getBindings();\n$sql = $query->toSql();\n\n// Formata de forma elegante\n$fullSql = preg_replace_array('/\\?/', $bindings, $sql);\necho $fullSql;\n// 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\nDica: Use preg_replace_array() — é mais seguro que vsprintf, especialmente quando os dados contêm o caractere %.
Método 2: Doctrine DBAL — para quem gosta de rigor
\nO Doctrine Query Builder (frequentemente usado no Symfony) é um bicho mais complicado. Aqui não há um toSql() pronto, mas existem getSQL() e getParameters(). Além disso, é necessário considerar os tipos dos parâmetros.
Exemplo básico
\nuse Doctrine\\DBAL\\Connection;\nuse Doctrine\\DBAL\\Query\\QueryBuilder;\n\n/** @var Connection $connection */\n$queryBuilder = $connection->createQueryBuilder();\n$queryBuilder\n ->select('p.*', 'c.name as category_name')\n ->from('products', 'p')\n ->join('p', 'categories', 'c', 'p.category_id = c.id')\n ->where('p.price > :minPrice')\n ->andWhere('p.sto