Вступление: Когда Query Builder молчит, а SQL нужно увидеть
Вы когда-нибудь писали сложный запрос через Query Builder, запускали его — и получали ошибку или не те данные? Руки чешутся увидеть, что же на самом деле генерирует ваша цепочка методов, но фреймворк упорно возвращает объект, а не строку. Знакомая ситуация?
Этот вопрос — один из самых популярных на Stack Overflow (десятки тысяч просмотров). И не зря. Умение "вытащить" сырой SQL из Query Builder — это суперсила, которая экономит часы отладки. Особенно когда вы работаете с ORM вроде Doctrine или Eloquent в Laravel, где запросы строятся через цепочку методов, а реальный SQL скрыт внутри.
В этой статье мы разберём, как получить raw SQL строку из Query Builder в разных PHP-фреймворках, покажем примеры кода и объясним, почему это важно для каждого разработчика. Поехали!
1. Почему вам может понадобиться raw SQL?
Прежде чем нырнуть в код, давайте поймём, зачем вообще смотреть на "сырой" SQL, если Query Builder и так всё делает за нас.
- Отладка ошибок. Когда запрос не работает, вы видите только объект. А SQL — это истина. Вы увидите, какие таблицы джойнятся, какие условия WHERE применяются, и где ошибка в синтаксисе.
- Оптимизация производительности. Query Builder может сгенерировать неэффективный запрос. Посмотрев на него, вы заметите лишние JOIN'ы или отсутствие индексов.
- Обучение. Если вы новичок, просмотр сгенерированного SQL помогает понять, как ORM транслирует ваши PHP-методы в реальные SQL-команды.
- Экспорт и логирование. Иногда нужно записать запрос в лог или отправить его в другой сервис. Без строки это невозможно.
Теперь перейдём к самому интересному — коду.
2. Laravel Eloquent: toSql() и getBindings()
Laravel — самый популярный PHP-фреймворк, и его Query Builder (часть Eloquent) предоставляет встроенные методы для получения SQL. Вот как это работает.
Простой пример: toSql()
use Illuminate\Support\Facades\DB;
$query = DB::table('users')
->where('age', '>', 18)
->where('status', 'active')
->orderBy('name', 'asc');
$sql = $query->toSql();
echo $sql;
// Вывод: select * from `users` where `age` > ? and `status` = ? order by `name` asc
Отлично! Но вы заметили знаки вопроса? Это плейсхолдеры для подготовленных запросов. Чтобы увидеть реальные значения, нужно использовать getBindings().
Полный SQL с подстановкой значений
$bindings = $query->getBindings();
$fullSql = vsprintf(str_replace('?', '%s', $sql), array_map(function ($value) {
return is_numeric($value) ? $value : "'" . $value . "'";
}, $bindings));
echo $fullSql;
// Вывод: select * from `users` where `age` > 18 and `status` = 'active' order by `name` asc
Этот трюк заменяет плейсхолдеры на реальные значения. Но будьте осторожны: не используйте этот метод для выполнения запроса — он не экранирует данные, это только для отладки.
Метод dd() для быстрой отладки
В Laravel есть удобный метод dd() (dump and die), который выводит SQL и биндинги:
DB::table('users')->where('age', '>', 18)->dd();
// Остановит выполнение и покажет:
// sql: select * from `users` where `age` > ?
// bindings: [18]
Совет: Используйте dd() только в development-среде. В production он убьёт запрос.
3. Doctrine ORM: getSQL() и параметры
Doctrine — мощный ORM, используемый в Symfony и других проектах. Его Query Builder немного отличается, но принцип тот же.
Базовый пример
use Doctrine\ORM\EntityManagerInterface;
// Предположим, $entityManager уже получен через DI
$queryBuilder = $entityManager->createQueryBuilder();
$queryBuilder
->select('u')
->from('App\Entity\User', 'u')
->where('u.age > :age')
->setParameter('age', 18)
->orderBy('u.name', 'ASC');
$query = $queryBuilder->getQuery();
$sql = $query->getSQL();
echo $sql;
// Вывод: SELECT u0_.id AS id_0, u0_.name AS name_1, u0_.age AS age_2 FROM users u0_ WHERE u0_.age > ? ORDER BY u0_.name ASC
Опять знаки вопроса. Чтобы получить параметры, используйте getParameters():
$parameters = $query->getParameters();
foreach ($parameters as $param) {
echo $param->getName() . ' => ' . $param->getValue() . "\n";
}
// Вывод: age => 18
Doctrine не даёт готового метода для подстановки, но вы можете написать свой хелпер. Главное — помните: это для отладки, не для выполнения.
4. Yii2 Query Builder: createCommand()->rawSql
Yii2 — ещё один популярный фреймворк. Его Query Builder использует класс yii\db\Query.
use yii\db\Query;
$query = new Query();
$query->select('*')
->from('users')
->where(['>', 'age', 18])
->andWhere(['status' => 'active']);
// Получаем команду и raw SQL
$command = $query->createCommand();
$sql = $command->rawSql;
echo $sql;
// Вывод: SELECT * FROM `users` WHERE `age` > 18 AND `status` = 'active'
Обратите внимание: rawSql уже подставляет значения! Это удобно для быстрой отладки.
5. CakePHP Query Builder: sql() и valueBinder()
CakePHP 4+ использует свой Query Builder. Вот как получить SQL:
use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
$query = $connection->newQuery()
->select('*')
->from('users')
->where(['age >' => 18]);
$sql = $query->sql();
$bindings = $query->valueBinder()->bindings();
echo $sql;
// Вывод: SELECT * FROM users WHERE age > :c0
Для подстановки значений:
$fullSql = $query->sql($query->valueBinder());
echo $fullSql;
// Вывод: SELECT * FROM users WHERE age > 18
CakePHP тоже делает подстановку автоматически, если передать valueBinder в sql().
6. Написание универсального логгера SQL
Если вы работаете с разными проектами или хотите логировать все запросы, напишите простой класс-логгер. Вот пример для Laravel:
class SqlLogger
{
public static function toRawSql($query)
{
$sql