Конструктор запросов
Запросы к БД собираются цепочкой методов на репозитории: select, from,
where, join, orderBy, limit. Условия описываются предикатами Qb:: — они
параметризованы, поэтому запрос защищён от инъекций, а части можно свободно
комбинировать.
Что такое конструктор запросов и зачем
Конструктор запросов (query builder) — программный способ собирать SQL из методов вместо написания строки целиком.
Проблема. Сырые SQL-строки с интерполяцией значений — прямой путь к SQL-инъекции и дублированию: одну и ту же выборку с чуть разными фильтрами приходится переписывать заново, а собрать условие динамически (по наличию параметров) в строке неудобно.
Решение. Собирайте запрос из методов, а значения передавайте предикатами Qb::
— они уходят в параметры (:iqb0, :iqb1…), а не в текст SQL. Части запроса
переиспользуются и комбинируются. Об этом и раздел.
Начало запроса
instance() создаёт репозиторий для запроса; необязательный алиас сразу вызывает
as():
use Flytachi\Winter\K2\Ppa\Repository\Qb;
$users = UserRepository::instance('u') // FROM users u
->where(Qb::eq('u.status', 'active'))
->orderBy('u.id DESC')
->limit(20)
->findAll();select / from / as
| Метод | Назначение |
|---|---|
select(string) |
Заменить список колонок сырым выражением (гидрация → stdClass) |
from(string|repo) |
Переопределить источник (таблица или подзапрос); один раз |
as(string) |
Алиас таблицы (префикс колонок и FROM) |
$repo->select('status, COUNT(*) AS cnt, AVG(score) AS avg'); // агрегаты
$repo->from('public.archive_users'); // своя таблица
$repo->as('u'); // алиас uУсловия — where + Qb
where() задаёт условие, andWhere / orWhere / xorWhere дописывают. Значение
уходит в параметр, не в текст:
UserRepository::instance()
->where(Qb::eq('role', 'editor'))
->andWhere(Qb::eq('status', 'active'))
->andWhere(Qb::gt('score', 50))
->findAll();
// WHERE role = :iqb0 AND status = :iqb1 AND score > :iqb2Предикаты Qb
| Группа | Предикаты |
|---|---|
| Сравнение | eq · neq · gt · gte · lt · lte |
| NULL | isNull · isNotNull |
| Множество | in(col, []) · notIn(col, []) |
| Шаблон | like(col, val, insensitive) · notLike(...) |
| Диапазон | between · notBetween (+ betweenBy) |
| Логика | and(...) · or(...) · xor(...) · clip(...) |
| Сырое | raw('...', [binds]) · custom(...) |
| Прочее | empty() · case(...) |
Группировка условий
andWhere/orWhere дописывают на верхнем уровне без скобок. Для вложенной логики
группируйте через Qb::and / Qb::or внутри одного where():
// (status = 'active') OR (role = 'admin' AND score > 90)
UserRepository::instance()
->where(Qb::or(
Qb::eq('status', 'active'),
Qb::and(
Qb::eq('role', 'admin'),
Qb::gt('score', 90),
),
))
->findAll();Динамические фильтры
Qb::empty() как нейтральная точка + addAnd() — удобно собирать условие по
наличию параметров:
$qb = Qb::empty();
if ($status !== null) $qb->addAnd(Qb::eq('status', $status));
if ($role !== null) $qb->addAnd(Qb::eq('role', $role));
UserRepository::instance()->where($qb)->findAll();null — безопасный no-op
where(null) не добавляет условие. Это позволяет передавать необязательный фильтр
напрямую, без обёрток.
JOIN
Пять типов; источник — строка (таблица) или репозиторий (подзапрос); условие
$on — строка или Qb:
| Метод | SQL | ON |
|---|---|---|
join(src, on) |
JOIN |
обязательно |
joinInner(src, on) |
INNER JOIN |
обязательно |
joinLeft(src, on) |
LEFT JOIN |
обязательно |
joinRight(src, on) |
RIGHT JOIN |
обязательно |
joinCross(src) |
CROSS JOIN |
нет |
OrderRepository::instance('o')
->joinLeft('users u', 'o.user_id = u.id')
->joinLeft('products p', 'o.product_id = p.id')
->where(Qb::eq('o.status', 'shipped'))
->select('o.id, u.name, p.title')
->findAll();Источником джойна может быть другой репозиторий — если у него есть доп. части (where/limit), он оборачивается в производную таблицу, а параметры сливаются:
$recent = OrderRepository::instance('o')
->where(Qb::gt('o.created_at', '2024-01-01'))
->select('user_id, SUM(total) AS total_spent');
UserRepository::instance('u')
->joinLeft($recent, 'u.id = o.user_id')
->findAll();
// LEFT JOIN (SELECT ... FROM orders o WHERE o.created_at > :iqb0) o ON(...)Группировка, сортировка, лимит
| Метод | Назначение |
|---|---|
groupBy('col') |
GROUP BY |
having(Qb) |
HAVING (условие как where) |
orderBy('col DIR') |
ORDER BY |
limit(n) |
LIMIT |
ContractRepository::instance('d')
->joinInner('schedule sc', 'sc.document_id = d.id')
->where(Qb::eq('d.status', 'active'))
->groupBy('d.id')
->orderBy('MIN(sc.date) ASC')
->limit(50)
->findAll();CTE, UNION и биндинги
PPA поддерживает with() / withRecursive() (CTE), union() / unionAll() и
ручные биндинги binding(). Полный reference низкоуровневого построения — в
доках CDO.
Выполнение и отладка
Собранный запрос выполняется fetch-методом (findAll, find, count, exists —
см. CRUD и выборка). Посмотреть готовый SQL до выполнения:
$repo = UserRepository::instance('u')->where(Qb::eq('u.status', 'active'));
echo $repo->buildSql(); // собрать и вернуть SQL со всеми частями
// SELECT u.id, ... FROM users u WHERE u.status = :iqb0Дальше
- CRUD и выборка —
find/findAll/countи запись - Пагинация — постраничная выдача результатов
- CDO — низкоуровневый билдер, CTE, union, все операторы
Qb