WSDB


WSDB предоставляет статический доступ к основному подключению базы данных CMS.

Поддерживает

  • статический доступ к основному подключению
  • регистрацию дополнительных подключений
  • выборку, добавление, обновление и удаление данных
  • атомарное увеличение числовых значений
  • выбор одной приоритетной строки из каждой группы
  • подсчёт уникальных значений столбца
  • условия, сортировку и ограничение выборки
  • работу с JSON-полями
  • транзакции с автоматическим откатом
  • блокировку параллельного выполнения операций
  • проверку существования таблиц, столбцов и индексов
  • автоматическое использование префикса таблиц

Класс автоматически доступен после запуска CMS. Создавать его экземпляр не нужно.

Быстрый старт

Получение одной строки:

php
$page = WSDB::fetchOne(
    'page',
    ['id' => 15],
    ['id', 'title', 'url']
);

Получение списка:

php
$pages = WSDB::fetchAll(
    table: 'page',
    where: ['status' => 'publish'],
    columns: ['id', 'title', 'url'],
    order: ['id' => 'DESC'],
    limit: 10
);

Добавление данных:

php
$id = WSDB::insert(
    'page',
    [
        'title' => 'Новая страница',
        'status' => 'draft',
    ]
);

Подключения

Основное подключение регистрируется CMS автоматически. Для обычной работы дополнительно настраивать его не нужно.

on(string $name = 'default'): Database

Возвращает объект Database для выполнения запросов:

php
$database = WSDB::on();
$page = $database->fetchOne('page', ['id' => 15]);

Для основного подключения методы Database можно вызывать напрямую через WSDB:

php
$page = WSDB::fetchOne('page', ['id' => 15]);

connection(string $name = 'default'): DatabaseConnection

Возвращает параметры зарегистрированного подключения:

php
$connection = WSDB::connection();

$pdo = $connection->pdo();
$prefix = $connection->prefix();

Обычно получать DatabaseConnection вручную не требуется.

isRegistered(string $name = 'default'): bool

Проверяет, зарегистрировано ли подключение с указанным именем:

php
if (WSDB::isRegistered('analytics')) {
    $database = WSDB::on('analytics');
}

register(DatabaseConnection $connection, string $name = 'default'): void

Регистрирует дополнительное подключение:

php
$connection = DatabaseConnection::connect('localhost', 'analytics', 'user', 'password', 'ANALYTICS_');

WSDB::register($connection, 'analytics');

После регистрации запросы выполняются через выбранное подключение:

php
$events = WSDB::on('analytics')->fetchAllFrom('events', ['id', 'name']);

Имя подключения приводится к нижнему регистру. Оно должно начинаться с латинской буквы или цифры и может содержать цифры, _ и -.

Получение данных

Метод Возвращает Описание
fetchOne() array|null Возвращает первую найденную строку.
fetchAll() array Возвращает строки по условиям.
fetchGroupedRepresentatives() array Возвращает одну представительную строку из каждой группы.
fetchAllFrom() array Возвращает все строки таблицы.
count() int Возвращает количество строк.
countDistinct() int Возвращает количество уникальных значений столбца.

fetchOne(string $table, array $where, array $columns = ['*'], ?array $order = null, array $jsonFields = [], bool $forUpdate = false): ?array

php
$user = WSDB::fetchOne(
    'user',
    ['id' => 7],
    ['id', 'login', 'email']
);

Если строка не найдена, возвращается null.

Чтение с блокировкой

Параметр $forUpdate по умолчанию равен false. При передаче true выполняется блокирующее чтение, для которого обязательна активная транзакция.

Пример для собственной таблицы products с полями id и stock: уменьшение остатка на единицу, если товар существует и остаток положительный.

php
$changed = WSDB::transaction(function (Database $database): bool {
    $product = $database->fetchOne('products', ['id' => 15], ['id', 'stock'], forUpdate: true);

    if ($product === null || (int) $product['stock'] < 1) {
        return false;
    }

    return $database->update('products', ['stock' => (int) $product['stock'] - 1], ['id' => $product['id']]) === 1;
});

Чтение и зависящие от него изменения выполняйте внутри одного callback, используя переданное подключение $database.

Блокировки освобождаются при фиксации или откате транзакции. После завершения WSDB::transaction() полученная строка больше не остаётся заблокированной этой транзакцией.

fetchAll(string $table, array $where, array $columns = ['*'], ?array $order = null, ?int $limit = null, ?int $offset = null, array $jsonFields = []): array

php
$posts = WSDB::fetchAll(
    table: 'post_news',
    where: ['status' => 'publish'],
    columns: ['id', 'title'],
    order: ['publish_date' => 'DESC'],
    limit: 20
);

Параметр $where обязателен, но допускает пустой массив. При передаче [] отбор по условиям не выполняется:

php
$posts = WSDB::fetchAll(table: 'post_news', where: [], columns: ['id', 'title'], order: ['id' => 'DESC'], limit: 10);

Сортировка и ограничение продолжают действовать. В примере возвращается не более десяти строк с наибольшими ID.

Параметр $offset можно использовать только вместе с $limit.

fetchGroupedRepresentatives(string $table, string $groupColumn, string $preferredColumn, mixed $preferredValue, array $where = [], array $columns = ['*'], ?array $order = null, ?int $limit = null, ?int $offset = null, array $jsonFields = []): array

Возвращает одну строку из каждой группы, отдавая предпочтение заданному значению:

php
$posts = WSDB::fetchGroupedRepresentatives(
    table: 'post_news',
    groupColumn: 'translation_key',
    preferredColumn: 'language',
    preferredValue: 'ru',
    where: ['status' => 'publish'],
    columns: ['id', 'title', 'language', 'translation_key'],
    order: ['id' => 'DESC'],
    limit: 20
);

Сначала применяются условия where, затем строки объединяются по groupColumn. Если приоритетному значению соответствуют несколько строк, выбирается строка с наименьшим id среди них. Если совпадений нет, выбирается строка с наименьшим id во всей группе.

Таблица должна содержать столбец id. Сортировка, ограничение и смещение применяются к уже выбранным строкам. offset можно использовать только вместе с limit.

fetchAllFrom(string $table, array $columns = ['*'], ?array $order = null, array $jsonFields = []): array

Используйте этот метод для выборки без условий:

php
$languages = WSDB::fetchAllFrom('site_language', ['id', 'code', 'label'], ['sort_order' => 'ASC', 'id' => 'ASC']);

count(string $table, array $where = []): int

php
$total = WSDB::count(
    'page',
    ['status' => 'publish']
);

Без условий подсчитываются все строки таблицы:

php
$total = WSDB::count('page');

countDistinct(string $table, string $column, array $where = []): int

Возвращает количество уникальных значений столбца:

php
$total = WSDB::countDistinct(
    'post_news',
    'translation_key',
    ['status' => 'publish']
);

Без условий проверяются все строки таблицы. Значения null при подсчёте не учитываются.

Условия

Простое значение проверяет равенство:

php
$where = ['status' => 'publish'];

Поддерживаемые операторы:

Оператор Описание
= Равно.
in Значение входит в список.
like Сравнение через LIKE.
> Больше.
< Меньше.
>= Больше или равно.
<= Меньше или равно.
!= Не равно.
is-null Значение равно NULL.
is-not-null Значение не равно NULL.
json-search Поиск значения внутри JSON-массива.
any-row Хотя бы одна строка JSON-массива соответствует условиям. Только для чтения и подсчёта.
php
$pages = WSDB::fetchAll(
    'page',
    [
        'status' => [
            'in' => ['publish', 'draft'],
        ],
    ]
);

Для объединения групп доступны $or и $and:

php
$users = WSDB::fetchAll(
    'user',
    [
        '$or' => [
            ['role' => 'admin'],
            ['role' => 'editor'],
        ],
    ]
);

Числовое сравнение

Чтобы сравнивать значение поля как число, добавьте numeric: true в правило:

php
$where = [
    'meta.price' => ['>=' => 100, 'numeric' => true],
];

Пример отбирает строки, у которых значение price в JSON-поле meta не меньше 100.

Поддерживаются операторы =, !=, >, <, >=, <= и in. Правило содержит один оператор и необязательный флаг numeric. Для диапазона объединяйте отдельные условия через $and.

Для сравнения передавайте конечное число или числовую строку. Оператор in принимает непустой массив таких значений. Флаг numeric должен быть логическим значением.

Пустые, отсутствующие и нечисловые значения поля не соответствуют числовому условию.

Проверка строк JSON-массива

Оператор any-row проверяет, что хотя бы одна строка JSON-массива соответствует заданным условиям. Каждая строка представлена массивом ячеек, нумерация которых начинается с 0.

Например, JSON-поле meta содержит варианты товара:

json
{
    "offers": [
        ["red", "XL", 150],
        ["blue", "M", 90]
    ]
}

Для поиска красного варианта размера XL с ценой не меньше 100 используйте:

php
$where = [
    'meta.offers' => [
        'any-row' => [
            0 => 'red',
            1 => 'XL',
            2 => ['>=' => 100, 'numeric' => true],
        ],
    ],
];

Здесь ячейка 0 содержит цвет, 1 содержит размер, а 2 содержит цену. Все условия должны выполняться в одной строке. Совпадения из разных строк не объединяются.

Внутри any-row поддерживаются группы $and, $or и числовые условия для отдельных ячеек. Вложенный any-row и оператор json-search не поддерживаются.

Правило any-row должно содержать непустой массив условий. На одном уровне с ним нельзя указывать другие операторы или numeric: true.

Оператор доступен только в запросах чтения и подсчёта. В условиях изменения и удаления данных он не поддерживается.

Отсутствующий или пустой массив не даёт совпадений. Элементы, которые не являются массивами, пропускаются.

Сортировка

Сортировка передаётся массивом поле => направление:

php
$order = [
    'publish_date' => 'DESC',
    'id' => 'ASC',
];

Поддерживаются только ASC и DESC.

Числовая сортировка

Для сортировки числовых значений, в том числе внутри JSON, передайте настройки поля:

php
$order = [
    'meta.price' => ['direction' => 'ASC', 'numeric' => true],
    'id' => 'ASC',
];

В этом примере цена сортируется по возрастанию как число: 2 будет перед 10. При одинаковой цене строки упорядочиваются по id.

В настройках поля direction по умолчанию равен ASC. Пустые, отсутствующие и нечисловые значения располагаются в конце как при ASC, так и при DESC.

Числовая сортировка не включает числовое сравнение в условиях $where.

Выбор столбцов

По умолчанию возвращаются все столбцы. Для ограничения результата передайте список:

php
$page = WSDB::fetchOne(
    'page',
    ['id' => 15],
    ['id', 'title', 'url']
);

Для псевдонима используйте исходное поле => новое имя:

php
$page = WSDB::fetchOne(
    'page',
    ['id' => 15],
    [
        'title' => 'name',
    ]
);

Изменение данных

Метод Возвращает Описание
insert() int Добавляет строку и возвращает её идентификатор.
update() int Обновляет строки и возвращает их количество.
increment() int Увеличивает числовое значение и возвращает количество изменённых строк.
upsert() int Добавляет или обновляет строку.
delete() int Удаляет строки и возвращает их количество.

insert(string $table, array $data): int

php
$id = WSDB::insert(
    'user',
    [
        'login' => 'new-user',
        'email' => 'user@example.com',
        'role' => 'user',
    ]
);

update(string $table, array $data, array $where): int

php
$updated = WSDB::update(
    'page',
    ['status' => 'publish'],
    ['id' => 15]
);

increment(string $table, string $column, array $where, int $amount = 1): int

Увеличивает значение числового столбца и возвращает количество изменённых строк:

php
$updated = WSDB::increment(
    'post_news',
    'views',
    ['id' => 15]
);

По умолчанию значение увеличивается на 1. Другую величину можно передать через amount:

php
$updated = WSDB::increment(
    'post_news',
    'views',
    ['id' => 15],
    5
);

Изменение выполняется одним запросом без предварительного получения текущего значения. Если столбец содержит null, исходным значением считается 0.

Условия where обязательны. Значение amount должно быть больше 0; уменьшение значений метод не поддерживает.

upsert(string $table, array $data, array $update = []): int

php
$id = WSDB::upsert(
    'options',
    [
        'type' => 'global',
        'name' => 'sitename',
        'value' => 'Мой сайт',
    ],
    [
        'value' => 'Мой сайт',
    ]
);

Метод обновляет строку при конфликте уникального ключа.

delete(string $table, array $where): int

php
$deleted = WSDB::delete(
    'page',
    ['id' => 15]
);

Удаление без условий запрещено.

JSON-поля

Для преобразования JSON-полей в массивы передайте их через jsonFields:

php
$page = WSDB::fetchOne(
    table: 'page',
    where: ['id' => 15],
    columns: ['id', 'meta'],
    jsonFields: ['meta']
);

Пустое значение преобразуется в пустой массив.

updateJson(string $table, string $jsonColumn, array $set, array $where): int

Обновляет отдельные значения JSON-поля:

php
$updated = WSDB::updateJson(
    'user',
    'meta',
    [
        'profile.name' => 'Иван',
        'profile.phone' => '+375000000000',
    ],
    ['id' => 7]
);

upsertJson(string $table, array $keyData, string $jsonColumn, array $jsonSet): int

Создаёт строку или обновляет отдельные значения JSON-поля:

php
$id = WSDB::upsertJson(
    'options',
    [
        'type' => 'themes',
        'name' => 'example',
    ],
    'value',
    [
        'color' => '#1251b7',
    ]
);

Массивы, переданные в обычные методы записи, автоматически сохраняются как JSON.

Транзакции

transaction(callable $callback): mixed

Выполняет несколько операций как единое целое и возвращает результат callback.

В примере используются собственные таблицы orders и order_items с полями, указанными в коде. Таблица orders должна автоматически создавать идентификатор id.

php
$orderId = WSDB::transaction(function (Database $database): int {
    $orderId = $database->insert('orders', ['status' => 'new']);
    $database->insert('order_items', ['order_id' => $orderId, 'product_id' => 15, 'quantity' => 2]);

    return $orderId;
});

Если callback завершился успешно, изменения фиксируются. Если возникло исключение, изменения отменяются, а исключение передаётся вызывающему коду.

Все связанные операции выполняйте через подключение $database, переданное в callback.

Вложенные транзакции на одном подключении не поддерживаются.

Блокировка операций

withAdvisoryLock(string $name, int $timeout, callable $callback): mixed

Не позволяет нескольким процессам одновременно выполнять одну операцию:

php
$result = WSDB::withAdvisoryLock('product-import', 10, function (Database $database) {
    return $database->fetchAllFrom('products');
});

$timeout задаёт время ожидания блокировки в секундах. Блокировка освобождается после завершения callback, в том числе при возникновении исключения.

Имя блокировки должно содержать от 1 до 64 латинских букв, цифр или символов _, ., :, -.

Информация о базе данных

Метод Возвращает Описание
getPrefix() string Возвращает префикс таблиц подключения.
getServerVersion() string Возвращает версию сервера базы данных.
listTables() array Возвращает список таблиц.
describeTable(string $table) array Возвращает описание столбцов таблицы.
showIndexes(string $table) array Возвращает индексы таблицы.
tableExists(string $table) bool Проверяет существование таблицы.
columnExists(string $table, string $column) bool Проверяет существование столбца.
indexExists(string $table, string $index) bool Проверяет существование индекса.

Имена таблиц передаются без системного префикса:

php
if (WSDB::tableExists('page')) {
    $columns = WSDB::describeTable('page');
}

Префикс таблиц

Передавайте имя таблицы без системного префикса:

php
$page = WSDB::fetchOne('page', ['id' => 15]);

Префикс текущего подключения добавляется автоматически.

Исключения

Исключение Когда возникает
InvalidArgumentException Передано недопустимое имя, пустые данные или неверный аргумент запроса.
LogicException Подключение отсутствует или уже зарегистрировано; выполняется вложенная транзакция либо повторно запрошена блокировка с тем же именем на том же подключении.
LogicException forUpdate: true передан без активной транзакции на том же подключении.
RuntimeException Не удалось подключиться, завершить транзакцию или получить блокировку.
BadMethodCallException Через WSDB вызван неизвестный метод Database.
PDOException Сервер базы данных отклонил запрос.
JsonException Переданное значение невозможно преобразовать в JSON.

Важные замечания

  • не создавайте экземпляр WSDB
  • основное подключение регистрируется CMS автоматически
  • статические методы используют основное подключение
  • для дополнительного подключения используйте WSDB::on($name)
  • fetchOne(), update(), increment() и delete() требуют непустые условия
  • для выборки без условий передайте [] в fetchAll() или используйте fetchAllFrom()
  • count() без условий подсчитывает все строки таблицы
  • значения запросов передаются через подготовленные выражения
  • имена таблиц и столбцов должны задаваться разработчиком