Пакет · cdo

Построение условий

Qb строит параметризованные фрагменты WHERE, которые можно композировать, вкладывать и переиспользовать. Каждое значение становится именованной привязкой, поэтому значения защищены от инъекций по построению. Это руководство охватывает повседневные шаблоны; полный список операторов — в справочнике операторов Qb.

Имена колонок вставляются как есть

Привязываются только значения. Аргумент имени колонки подставляется в SQL дословно, без кавычек и экранирования. Никогда не передавайте пользовательский ввод как имя колонки — Qb::eq('status', $userInput) безопасно, а Qb::eq($userInput, 'active') — вектор SQL-инъекции.

Композиция через and / or

Начните с листовых условий и объединяйте их:

php
use Flytachi\Winter\Cdo\Qb;

$where = Qb::and(
  Qb::eq('status', 'active'),
  Qb::gte('age', 18),
  Qb::isNull('banned_at'),
);
// status = :iqb0 AND age >= :iqb1 AND banned_at IS NULL

and / or / xor принимают любое число условий и молча пропускают null или пустые — именно это делает необязательные фильтры чистыми.

Необязательные (динамические) фильтры

Поскольку условия null выбрасываются, вы можете встраивать их через тернарный оператор. Пустые списки для in() нужно защищать, так как in() бросает исключение на пустом массиве.

php
$where = Qb::and(
  Qb::eq('status', 'active'),
  $minAge  !== null ? Qb::gte('age', $minAge)     : null,
  $country !== null ? Qb::eq('country', $country)  : null,
  $tagIds          ? Qb::in('tag_id', $tagIds)     : null, // защита пустого массива
);

Защищайтесь от полностью пустого условия

Если все необязательные фильтры пропущены, Qb::and(...) даёт пустой фрагмент. Передача его в update() или delete() порождает запрос без WHERE. Проверяйте $where->getQuery() !== '' перед запуском мутации.

Управление приоритетом через clip

SQL связывает AND крепче, чем OR. Когда вы их смешиваете, оберните группу OR в clip(), чтобы навязать нужную группировку:

php
// Неверно: читается как (published AND role='editor') OR role='admin'
Qb::and(
  Qb::eq('published', true),
  Qb::or(Qb::eq('role', 'editor'), Qb::eq('role', 'admin')),
);

// Верно: clip делает OR единым сгруппированным членом
Qb::and(
  Qb::eq('published', true),
  Qb::clip(
      Qb::or(Qb::eq('role', 'editor'), Qb::eq('role', 'admin')),
  ),
);
// published IS TRUE AND (role = :iqb0 OR role = :iqb1)

Пошаговое построение (изменяемое)

Qb неизменяем по умолчанию, но методы addAnd / addOr / addXor мутируют на месте — удобно, когда вы накапливаете условия в цикле:

php
$where = Qb::eq('status', 'active');

foreach ($filters as $column => $value) {
  $where->addAnd(Qb::eq($column, $value));
}

Переиспользование значения через именованную привязку

По умолчанию каждое значение получает непрозрачный плейсхолдер (:iqb0). Чтобы сослаться на одно и то же значение в нескольких условиях, постройте CDOBind один раз и передайте его — привяжется единственный :name, общий для каждого использующего его условия:

php
use Flytachi\Winter\Cdo\CDOBind;

$uid = new CDOBind('uid', $currentUserId);

$where = Qb::or(
  Qb::eq('author_id',   $uid),
  Qb::eq('reviewer_id', $uid),
  Qb::eq('assignee_id', $uid),
);
// author_id = :uid OR reviewer_id = :uid OR assignee_id = :uid

Аварийный выход: raw

Когда ни один оператор не подходит (функции вендора, подзапросы), Qb::raw() вставляет SQL дословно — но по-прежнему позволяет привязывать значения через плейсхолдеры:

php
Qb::raw('JSON_CONTAINS(tags, :tag)', ['tag' => '"php"']);
// JSON_CONTAINS(tags, :tag)  — :tag привязан, текст функции — дословный

raw не санитизирует строку запроса

Строка SQL в raw() выдаётся ровно как написана. Никогда не подставляйте в неё пользовательский ввод — передавайте значения через аргумент $binds.

Использование фрагмента

update() и delete() потребляют Qb напрямую. Для чтения разберите его на части и привяжите к PDO-запросу:

php
$stmt = $cdo->prepare("SELECT * FROM users WHERE " . $where->getQuery());
foreach ($where->getBinds() as $bind) {
  $stmt->bindValue($bind->getName(), $bind->getValue());
}
$stmt->execute();

Связанное