Простые обёртки для работы с MySQL и PostgreSQL в PHP, с почти одинаковым интерфейсом
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Vitaliy Filippov b840aa2eac Allow to log queries via error_log; log query times; add version getter 9 months ago
DatabaseMysql.php Some small fixes 1 year ago
DatabaseMysqlWP.php Fix MysqlWP 2 years ago
DatabasePdoPgsql.php Allow to log queries via error_log; log query times; add version getter 9 months ago
MysqlQueryBuilder.php Fix MysqlWP 2 years ago
README.md README 2 years ago

README.md

Зачем?

Зачем очередная обёртка, спросите вы? А вот зачем:

  • Удобный select builder по мотивам MediaWiki-овского - не то, чтобы он был везде нужен, да и параметры экранировать, в принципе, можно и через голое PDO... ...Но! Запросы часто нужно собирать по частям - и вот тут select builder на голых PHP-шных массивах очень удобен. Ну и в целом - синтаксис более краток, чем PDO. Но если не хочется - можно его и не использовать.
  • Удобные функции для изменения данных - insert, upsert, update, delete, просто по имени таблицы и переданному массиву записей
  • Автоматическое переподключение
  • Вложенные транзакции на SAVEPOINT'ах
  • Лог запросов

Я это использую во всех проектах примерно с момента знакомства с MediaWiki.

Лицензия и автор

Лицензия - GPL 3.0 или более новой версии

Автор - Виталий Филиппов, 2012-2017

Создание соединения

$db = new DatabasePdoPgsql([
    'host' => 'localhost',
    'port' => 5432,
    'socket' => '/var/run/postgresql',
    'dbname' => '',
    'username' => '',
    'password' => '',
    'reconnect' => true,
    'tableNames' => [],
    'queryLogFile' => '',
    'autoBegin' => false,
    'ondestroy' => 'commit',
    'init' => [],
]);

Опции в основном говорят сами за себя. Неочевидные:

  • tableNames - маппинг имён таблиц (поддерживается замена имён таблиц на уровне обёртки).
  • queryLogFile - путь к файлу с логом запросов
  • autoBegin - начинать ли транзакцию автоматически при первом запросе
  • ondestroy - что делать при уничтожении объекта с активной транзакцией: применить (“commit”) или откатить (“rollback”).

“SELECT BUILDER”

$db->select($tables, $fields, $where, $options, $format)

Пример:

$db->select(
    [
        'u' => 'users',
        'i' => [ 'INNER', 'instances', [ 'i.id=u.instance_id' ] ]
    ],
    'u.*, i.name instance_name',
    [
        'u.reg_timestamp > ?' => time(),
        'u.reg_timestamp < date_part(\'epoch\', now())',
        'u.key' => [ 'A', 'B', 'C' ], // превратится в условие: u.key IN ('A', 'B', 'C')
    ],
    [
        'FOR UPDATE', // или 'FOR SHARE' или 'LOCK IN SHARE MODE'
        'GROUP BY' => 'u.id',
        'ORDER BY' => [ 'u.reg_timestamp' => 'ASC' ],
        'LIMIT' => 10,
        'OFFSET' => 10,
    ],
    MS_HASH
);

По сути - это весь базовый синтаксис select builder'а. JOIN'ы могут быть LEFT, могут быть вложенными.

MS_* - формат возврата. По умолчанию возвращает массив записей БД в ассоциативном формате. MS_HASH - значение по умолчанию, его как 4-й параметр можно не указывать, либо вместо MS_HASH можно указать:

  • MS_VALUE - тогда вернётся только 1-е значение 1-й строки (скаляр)
  • MS_ROW - вернётся только 1-я строка (в ассоциативном формате)
  • MS_COL - вернётся только 1-я колонка (массив скаляров)
  • MS_RESULT - вернётся объект результата (mysqli/PDO) для ручной обработки, результат при этом не буферизуется

Можно вызвать $db->select("SELECT * FROM users", MS_HASH) - это просто выполнит текстовый запрос. В этом случае вторым параметром обязатело передать одну из констант MS_*.

Также вместо $db->select($tables, $fields, $where, $options) можно вызвать с теми же параметрами $db->select_builder($tables, $fields, $where, $options) - он вернёт текст запроса.

UPSERT

$db->upsert('users', [ [ 'email' => 'vasya@pupkin.ru', 'name' => 'vasya', 'surname' => 'pupkin' ] ], [ 'email' ]) - UPSERT.

Для тех, кто знает PostgreSQL - это INSERT ... ON CONFLICT (email) DO UPDATE ...

Третий параметр - список полей уникального ключа, по которым проверяет конфликт.

Для тех, кто знает MySQL - аналог REPLACE или точнее INSERT ... ON DUPLICATE KEY UPDATE ...

Другие функции

$db->quote($value) - экранирование значения

$db->query("TRUNCATE users") - выполнение произвольных запросов без чтения результата (только true/false)

$db->insert_row('users', [ 'name' => 'vasya', 'surname' => 'pupkin' ]) - вставка одной строки, вернёт ID

$db->insert('users', [ [ 'name' => 'vasya', 'surname' => 'pupkin' ], ... ]) - вставка множества строк

$db->update('users', [ 'name' => 'vasya' ], [ 'id' => 1 ]) - эквивалентно UPDATE users SET name='vasya’ WHERE id=1

Плюшка: в качестве первого параметра можно передавать JOIN'енные таблицы, в PostgreSQL это будет корректно превращено в конструкцию UPDATE table FROM ...

Например:

$db->update(
    [ 'u' => 'users', 'i' => [ 'INNER', 'instances', [ 'i.id=u.instance_id'] ] ],
    'name' => 'vasya',
    [ 'i.domain' => 'pupkin.ru' ]
);

$db->delete('users', [ 'id' => [ 1, 2, 3 ] ]) - удаление по условию

В качестве первого параметра, как и в update(), можно передавать JOIN'енные таблицы, в PostgreSQL это будет корректно превращено в конструкцию DELETE ... USING.

Транзакции

$db->begin(), $db->commit(), $db->rollback(), $db->commitAll(), $db->rollbackAll()

Транзакции. Могут быть вложенные, т.е. можно несколько раз подряд сделать begin и commit/rollback. Вложенные транзакции превращаются в SAVEPOINT'ы.

$db->commitAll() коммитит транзакцию со всеми активными SAVEPOINT'ами,

$db->rollbackAll() откатывает транзакцию со всеми активными SAVEPOINT'ами.

VALUES

Дополнительная плюшка: обёртка для синтаксиса “inline таблиц” PostgreSQL.

$users = $db->select(
    [
        'd' => $db->values_table([
            [ 'social_network' => 'vk.com', 'date' => '2017-01-01' ],
            [ 'social_network' => 'facebook.com', 'date' => '2017-01-02' ],
        ]),
        'u' => [ 'INNER', 'users', [ 'u.social_network=d.social_network', 'u.reg_date=d.date' ] ],
    ],
    'u.*',
    []
);

Эквивалентно

SELECT u.*
FROM (VALUES ('vk.com','2017-01-01'),('facebook.com','2017-01-02')) AS d ("social_network","date")
INNER JOIN users u ON (u.social_network=d.social_network) AND (u.reg_date=d.date)

MySQL

Всё вышеописанное, кроме VALUES, актуально и для MySQL - синтаксис точно такой же.

Дополнительная плюшка в MySQL - это CALC_FOUND_ROWS и функция $db->found_rows(), возвращающая количество найденных строк. Фактически эту опцию можно использоать и в PdoPgsql-драйвере - он пробует эмулировать её через COUNT(*) OVER() - но это не очень красивое решение.

И ещё одна плюшка - возможность использования той же самой обёртки для общения с SphinxSearch, работающему по протоколу SphinxQL - в ней есть поддержка sphinx-специфичных опций (но за описаниями лучше залезьте в код).