Skip to content

Instantly share code, notes, and snippets.

@silverslice
Last active August 29, 2015 14:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save silverslice/b2aa3d9ec7a6a742d8d9 to your computer and use it in GitHub Desktop.
Save silverslice/b2aa3d9ec7a6a742d8d9 to your computer and use it in GitHub Desktop.
Mysql wrapper for legacy projects
<?php
/**
* Подключение к БД
*
* @param $host
* @param $user
* @param $pass
* @param $db
* @return bool|resource
*/
function db_connect($host, $user, $pass, $db)
{
$dbh = mysql_connect($host, $user, $pass, false, 128);
if (!$dbh) {
return false;
}
mysql_select_db($db, $dbh);
return $dbh;
}
/**
* Выполнение запроса
*
* param string $query Запрос
* param mixed ...$params Данные для подстановки вместо плейсхолдеров
*
* @return resource
* @throws DbException
*/
function db_query()
{
$sql = db_parse(func_get_args());
$res = mysql_query($sql);
if (!$res) {
if (!empty($GLOBALS['__transaction'])) {
throw new DbException(mysql_error(), mysql_errno());
}
if (defined('DEBUG') && DEBUG) {
// 1062 - (Duplicate entry '%s' for key)
if (mysql_errno() != 1062) {
db_notify($sql);
}
}
}
return $res;
}
/**
* Прямой запрос без плейсхолдеров
*
* @param $q
* @return resource
*/
function db_real_query($q)
{
return mysql_query($q);
}
/**
* Формирование строки запроса с учетом плейсхолдеров
*
* @param string|array $param
* @return string
*/
function db_parse($param)
{
if (is_array($param)) {
$GLOBALS['__args'] = $param;
} else {
$GLOBALS['__args'] = func_get_args();
}
return preg_replace_callback('#\?[isfaep]?#', 'db_prepare_callback', array_shift($GLOBALS['__args']));
}
/**
* Парсинг плейсхолдеров
*
* @param $m
* @return string
*/
function db_prepare_callback($m)
{
if (!sizeof($GLOBALS['__args'])) {
return "''"; // список параметров исчерпался - вставляем ''
}
$value = array_shift($GLOBALS['__args']); // текущий аргумент
switch ($m[0]) {
case '?': // по умолчанию функция сама выбирает тип данных
if (is_int($value)) {
return $value;
} elseif (is_null($value)) {
return 'null';
} elseif (is_array($value) && isset($value['db_expr'])) {
return $value['db_expr'];
} else {
return "'" . db_escape($value) . "'";
}
case '?i': return intval($value); // integer
case '?s': return "'" . db_escape($value) . "'"; // string
case '?f': return str_replace(',', '.', floatval($value)); // float
case '?e': return db_escape($value); // escape
case '?p': return $value; // sql part
case '?a': // array
if (!$value) {
return 'NULL';
}
foreach ($value as &$e) {
if (!is_int($e)) {
$e = "'" . db_escape($e) . "'";
}
}
return implode(',', $value);
}
return '';
}
/**
* Возвращает sql выражение, которое будет напрямую вставлено в запрос
*
* @param $value
* @return array
*/
function db_expr($value)
{
return array('db_expr' => $value);
}
/**
* Текст ошибки
*
* @return string
*/
function db_error()
{
return mysql_error();
}
/**
* Номер ошибки
*
* @return int
*/
function db_errno()
{
return mysql_errno();
}
/**
* Число записей в результате запроса
*
* @param $result
* @return int
*/
function db_num_rows($result)
{
return mysql_num_rows($result);
}
/**
* Есть ли записи в результате запроса
*
* @param $result
* @return bool
*/
function db_has_rows($result)
{
return is_resource($result) && (mysql_num_rows($result) > 0);
}
/**
* Возвращает число затронутых прошлой операцией рядов
*
* @return int
*/
function db_affected_rows()
{
return mysql_affected_rows();
}
/**
* Возвращает id вставленной записи
*
* @return int
*/
function db_insert_id()
{
return mysql_insert_id();
}
/**
* Одно значение из результата
*
* @param $result
* @return string
*/
function db_fetch_one($result)
{
return mysql_result($result, 0, 0);
}
/**
* Строка результата
*
* @param $result
* @return array
*/
function db_fetch_row($result)
{
return mysql_fetch_row($result);
}
/**
* Ассоциативный массив из результата
*
* @param $result
* @return array
*/
function db_fetch_assoc($result)
{
return mysql_fetch_assoc($result);
}
/**
* Экранирование строки
*
* @param $str
* @return string
*/
function db_escape($str)
{
if (defined('MAGIC_QUOTES') && MAGIC_QUOTES) { // magic quotes ON
return $str;
}
return mysql_real_escape_string($str);
}
/**
* Явное экранирование строки
*
* @param $data
* @return array|string
*/
function db_real_escape($data)
{
if (is_array($data)) {
return array_map('db_real_escape', $data);
} else {
return mysql_real_escape_string($data);
}
}
/**
* Список строк результата в массиве
*
* @param $result
* @return array
*/
function db_fetch_all($result)
{
$items = array();
while ($ar = db_fetch_assoc($result)) {
$items[] = $ar;
}
return $items;
}
/**
* Все строки в двумерном массиве, индексированном значениями первого поля в запросе
*
* @param $result
* @return array
*/
function db_fetch_all_keyed($result)
{
$items = array();
while ($ar = db_fetch_assoc($result)) {
$key = array_shift($ar);
$items[$key] = $ar;
}
return $items;
}
/**
* Ассоциативный массив скаляров, индексированный полем из первого параметра
*
* @param $result
* @return array
*/
function db_fetch_pairs($result)
{
$items = array();
while ($ar = db_fetch_row($result)) {
$items[$ar[0]] = $ar[1];
}
return $items;
}
/**
* Массив скаляров, индексированный числами по порядку
*
* @param $result
* @return array
*/
function db_fetch_col($result)
{
$items = array();
while ($ar = db_fetch_row($result)) {
$items[] = $ar[0];
}
return $items;
}
/**
* Первый элемент первой строки результата
*
* @return bool|string
*/
function db_get_one()
{
$args = func_get_args();
$res = call_user_func_array('db_query', $args);
if (!$res) {
return false;
}
if (!db_num_rows($res)) {
return false;
}
return db_fetch_one($res);
}
/**
* Первая строка результата в виде ассоциативного массива
*
* @return array|bool
*/
function db_get_assoc()
{
$args = func_get_args();
$res = call_user_func_array('db_query', $args);
if (!$res) {
return false;
}
if (!db_num_rows($res)) {
return false;
}
return db_fetch_assoc($res);
}
/**
* Все строки в двумерном массиве, индексированном числами по порядку
*
* @return array|bool
*/
function db_get_all()
{
$args = func_get_args();
$res = call_user_func_array('db_query', $args);
if (!$res) {
return false;
}
return db_fetch_all($res);
}
/**
* Все строки в двумерном массиве, индексированном значениями первого поля в запросе
*
* @return array|bool
*/
function db_get_all_keyed()
{
$args = func_get_args();
$res = call_user_func_array('db_query', $args);
if (!$res) {
return false;
}
return db_fetch_all_keyed($res);
}
/**
* Ассоциативный массив скаляров, индексированный полем из первого параметра
*
* @return array|bool
*/
function db_get_pairs()
{
$args = func_get_args();
$res = call_user_func_array('db_query', $args);
if (!$res) {
return false;
}
return db_fetch_pairs($res);
}
/**
* Массив скаляров, индексированный числами по порядку
*
* @return array|bool
*/
function db_get_col()
{
$args = func_get_args();
$res = call_user_func_array('db_query', $args);
if (!$res) {
return false;
}
if (!db_num_rows($res)) {
return false;
}
return db_fetch_col($res);
}
/**
* Вставка данных в БД
*
* @param $table
* @param array $params пареметры в виде поле => значение
* @return bool|int|mixed
*/
function db_insert($table, $params = array())
{
$sql = "INSERT INTO `$table` SET `". join('` = ?,`', array_keys($params)) ."` = ?";
$args[0] = $sql;
$args = array_merge($args, array_values($params));
$res = call_user_func_array('db_query', $args);
if ($res) {
$res = db_insert_id();
if ($res === 0) { // no autoincrement field
$res = true;
}
}
return $res;
}
/**
* Многострочный insert
*
* @param $table
* @param array $fields массив полей
* @param array $data массив данных
* @return bool|resource
*/
function db_multi_insert($table, $fields, $data = array())
{
if (!$data) {
return false;
}
$sql = "INSERT IGNORE INTO `$table` (`" . join('`,`', array_values($fields)) . "`) VALUES ";
$rows = count($data);
foreach ($data as $i => $row) {
foreach ($row as &$field) {
if (is_null($field)) {
$field = 'null';
} else {
$field = "'" . db_escape($field) . "'";
}
}
$sql .= '(' . join(', ', $row) . ')';
if ($i < $rows - 1) {
$sql .= ', ';
}
}
$res = mysql_query($sql);
if (!$res && defined('DEBUG')) {
db_notify($sql);
}
return $res;
}
/**
* Обновление информации в БД
*
* @param $table
* @param array $params массив данных в виде поле => значение
* @param array $where массив условий (если несколько - будут объединены как AND)
* @return mixed
*/
function db_update($table, $params = array(), $where = array())
{
$where_sql = array();
foreach ($where as $key => $value) {
$where_sql[] = "{$key} = ?";
}
$sql = "UPDATE `$table` SET `". join('` = ?,`', array_keys($params)) ."` = ? WHERE " . join(' AND ', $where_sql);
$args[0] = $sql;
$args = array_merge($args, array_values($params), array_values($where));
$res = call_user_func_array('db_query', $args);
return $res;
}
/**
* Импорт sql-файла
*
* @param $filename
* @return bool
*/
function db_import($filename)
{
if (!is_file($filename)) {
return false;
}
// Temporary variable, used to store current query
$templine = '';
$lines = file($filename);
foreach ($lines as $line) {
// Skip it if it's a comment
if (substr($line, 0, 2) == '--' || $line == '') {
continue;
}
$templine .= $line;
// If it has a semicolon at the end, it's the end of the query
if (substr(trim($line), -1, 1) == ';') {
db_real_query($templine);
$templine = '';
}
}
return true;
}
/**
* Старт транзакции
*/
function db_start_transaction()
{
$GLOBALS['__transaction'] = true;
db_query('START TRANSACTION');
}
/**
* Фиксация транзакции
*/
function db_commit()
{
$GLOBALS['__transaction'] = false;
db_query('COMMIT');
}
/**
* Откат транзакции
*/
function db_rollback()
{
$GLOBALS['__transaction'] = false;
db_query('ROLLBACK');
}
/**
* Wrapper для транзакций
*
* @param Closure $process Функция, выполняемая в транзакции
* @return bool
*/
function db_transaction($process)
{
if (!is_callable($process)) {
return false;
}
try {
db_start_transaction();
$process();
db_commit();
return true;
} catch (DbException $e) {
db_rollback();
return false;
}
}
/**
* Вывод отладочной информации
*
* @param string $query
*/
function db_notify($query = '')
{
echo "<p style='border:5px solid #b20000; background-color:#fff; padding:5px;'><strong>Database Error:</strong><br/>" . db_error() . "</p>";
echo "<p style='border:5px solid #b20000; background-color:#fff; padding:5px;'><strong>Last Query:</strong><br/>" . $query . "</p>";
echo "<pre>";
debug_print_backtrace();
echo "</pre>";
exit;
}
/**
* Старт профайлера выполненных запросов
*/
function db_start_profile()
{
db_query('RESET QUERY CACHE');
db_query('SET profiling_history_size = 100');
db_query('SET profiling = 1');
}
/**
* Отображает статистику выполненных запросов (при условии, что профайлинг был активирован)
*/
function db_show_profile()
{
$stat = db_get_all('SHOW PROFILES');
echo "<table border=1 cellpadding=3>";
$total = 0;
foreach ($stat as $i => $ar) {
$total += $ar['Duration'];
echo "<tr>
<td width=2%>".++$i."</td>
<td width=2%>$ar[Query_ID]</td>
<td width=80%>$ar[Query]</td>
<td>$ar[Duration]</td>
</tr>";
}
echo "<tr><td colspan='3'></td><td><b>$total</b></td></tr>";
echo "</table>";
}
class DbException extends Exception {}
<?php
include '/path/to/DbMysql.php';
class DbMysqlTest extends PHPUnit_Framework_TestCase
{
/**
* Создание таблицы для тестов
*/
public static function setUpBeforeClass()
{
$db = include '/path/to/db_config.php';
db_connect($db['host'], $db['user'], $db['pass'], $db['db']);
db_query("DROP TABLE IF EXISTS `test_db`;");
db_query("
CREATE TABLE `test_db` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`code` char(15) NOT NULL,
`name` varchar(200) NOT NULL,
`price` decimal(10,2) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
");
$lines = [
[1, '001', 'Кружка', 200.00],
[2, '002', 'Ложка', 300.00],
[3, '002', 'Кастрюля', 400.00],
];
db_multi_insert('test_db', ['id', 'code', 'name', 'price'], $lines);
}
/**
* Удаление тестовой таблицы
*/
public static function tearDownAfterClass()
{
db_query("DROP TABLE `test_db`;");
}
public function testGetOne()
{
$code = db_get_one('SELECT code FROM test_db WHERE id = 2');
$this->assertEquals('002', $code);
}
public function testGetAssoc()
{
$ar = db_get_assoc('SELECT code, name FROM test_db WHERE id = 2');
$this->assertEquals(['code' => '002', 'name' => 'Ложка'], $ar);
}
public function testGetAll()
{
$ar = db_get_all('SELECT id, price FROM test_db WHERE id = 1 OR id = 2');
$expected = [
['id' => 1, 'price' => 200.00],
['id' => 2, 'price' => 300.00],
];
$this->assertEquals($expected, $ar);
}
public function testGetAllKeyed()
{
$ar = db_get_all_keyed('SELECT code, price, id FROM test_db WHERE id = 1 OR id = 2');
$expected = [
'001' => ['id' => 1, 'price' => 200.00],
'002' => ['id' => 2, 'price' => 300.00],
];
$this->assertEquals($expected, $ar);
}
public function testGetPairs()
{
$ar = db_get_pairs('SELECT id, code FROM test_db WHERE id = 1 OR id = 2');
$expected = [
1 => '001',
2 => '002',
];
$this->assertEquals($expected, $ar);
}
public function testGetCol()
{
$ar = db_get_col('SELECT id, code FROM test_db WHERE id = 1 OR id = 2');
$expected = [1, 2];
$this->assertEquals($expected, $ar);
}
public function testInsert()
{
db_insert('test_db', ['id' => 4, 'price' => 400.00]);
$price = db_get_one('SELECT price FROM test_db WHERE id = 4');
$this->assertEquals(400.00, $price);
}
public function testMultiInsert()
{
$values = [
[10, 110],
[11, 111],
];
db_multi_insert('test_db', ['id', 'price'], $values);
$price = db_get_one('SELECT price FROM test_db WHERE id = 10');
$this->assertEquals(110.00, $price);
$price = db_get_one('SELECT price FROM test_db WHERE id = 11');
$this->assertEquals(111.00, $price);
}
public function testUpdate()
{
db_query('INSERT INTO test_db (id, price) VALUES (200, 80)');
db_update('test_db', ['price' => 20], ['id' => 200]);
$price = db_get_one('SELECT price FROM test_db WHERE id = 200');
$this->assertEquals(20.00, $price);
}
public function testUpdateAndWhere()
{
db_query("INSERT INTO test_db (id, code, price) VALUES (300, '300', 90)");
db_update('test_db', ['price' => 95], ['id' => 300, 'code' => '300']);
$price = db_get_one('SELECT price FROM test_db WHERE id = 300');
$this->assertEquals(95.00, $price);
}
public function testDbParseInt()
{
$str = db_parse('test ?i', '1');
$this->assertEquals("test 1", $str);
}
public function testDbParseString()
{
$str = db_parse('test ?s', '1');
$this->assertEquals("test '1'", $str);
}
public function testDbParseFloat()
{
$str = db_parse('test ?f', '1.2');
$this->assertEquals("test 1.2", $str);
}
public function testDbParseEscape()
{
$str = db_parse('test ?e', "1'2");
$this->assertEquals("test 1\\'2", $str);
}
public function testDbParsePart()
{
$str = db_parse('test ?e', "go");
$this->assertEquals("test go", $str);
}
public function testDbParseArray()
{
$str = db_parse('test (?a)', [1, 2, 3]);
$this->assertEquals("test (1,2,3)", $str);
}
public function testDbParseArrayString()
{
$str = db_parse('test (?a)', ['1', '2', '3']);
$this->assertEquals("test ('1','2','3')", $str);
}
public function testDbParseDefaultString()
{
$str = db_parse('test ?', "1'");
$this->assertEquals("test '1\\''", $str);
}
public function testDbParseDefaultInt()
{
$str = db_parse('test ?', 1);
$this->assertEquals("test 1", $str);
}
public function testDbParseDefaultNull()
{
$str = db_parse('test ?', null);
$this->assertEquals("test null", $str);
}
public function testDbParseDefaultExpr()
{
$str = db_parse('test ?', db_expr('now()'));
$this->assertEquals("test now()", $str);
}
public function testDbEscape()
{
$str = db_escape("test '");
$this->assertEquals("test \\'", $str);
$str = db_escape('test "');
$this->assertEquals('test \\"', $str);
$str = db_escape('test \\');
$this->assertEquals('test \\\\', $str);
}
public function testDbNumRows()
{
$n = db_num_rows(db_query('SELECT id FROM test_db WHERE id = 1 OR id = 2'));
$this->assertEquals(2, $n);
}
public function testDbInsertId()
{
db_query("INSERT INTO test_db (id, code, price) VALUES (1000, '300', 850)");
$id = db_insert_id();
$this->assertEquals(1000, $id);
}
public function testTransactionCommit()
{
db_start_transaction();
db_query("INSERT INTO test_db (id, code, price) VALUES (3000, '1', 1)");
db_commit();
$id = db_get_one("SELECT id FROM test_db WHERE id = 3000");
$this->assertEquals(3000, $id);
}
public function testTransactionRollback()
{
db_start_transaction();
db_query("INSERT INTO test_db (id, code, price) VALUES (3001, '1', 1)");
db_rollback();
$id = db_get_one("SELECT id FROM test_db WHERE id = 3001");
$this->assertEquals(false, $id);
}
/**
* @expectedException DbException
*/
public function testTransactionFail()
{
db_query("INSERT INTO test_db (id, code, price) VALUES (3002, '1', 1)");
db_start_transaction();
db_query("INSERT INTO test_db (id, code, price) VALUES (3002, '1', 1)");
}
public function testTransactionWrapper()
{
db_transaction(function(){
db_query("INSERT INTO test_db (id, code, price) VALUES (3003, '1', 1)");
db_query("INSERT INTO test_db (id, code, price) VALUES (3004, '1', 1)");
});
$col = db_get_col('SELECT id FROM test_db WHERE id IN (3003, 3004)');
$this->assertEquals([3003, 3004], $col);
}
public function testTransactionWrapperFail()
{
db_query("INSERT INTO test_db (id, code, price) VALUES (3004, '1', 1)");
$res = db_transaction(function(){
db_query("INSERT INTO test_db (id, code, price) VALUES (3004, '1', 1)");
});
$this->assertEquals(false, $res);
}
}
@silverslice
Copy link
Author

Плейсхолдеры

Любые данные подставляются в запрос через плейсхолдеры. Типы плейсхолдеров:

  • ? - используется по умолчанию, тип данных выбирается автоматически, как правило это строка
  • ?i - приводится к целому числу
  • ?s - строка, заключается в 'кавычки' и экранируется
  • ?f - дробное число
  • ?e - строка экранируется, в кавычки не заключается
  • ?p - готовая часть запроса, вставляется "как есть"
  • ?a - массив, значения экранируются, заключаются в строки и соединяются запятой ('one', 'two', 'three').

Основные функции выборки данных

  • db_parse() - Возвращает строку с подставленными на место плейсхолдеров данными
  • db_get_one() - Первый элемент первой строки результата
  • db_get_assoc() - Первая строка результата в виде ассоциативного массива
  • db_get_all() - Все строки в двумерном массиве, индексированном числами по порядку
  • db_get_all_keyed() - Все строки в двумерном массиве, индексированном значениями первого поля в запросе
  • db_get_pairs() - Ассоциативный массив значений, индексированный полем из первого параметра
  • db_get_col() - Массив значений, индексированный числами по порядку
  • db_insert($table, $params = array()) - Вставка данных в БД
  • db_multi_insert($table, $fields, $data = array()) - Многострочный insert
  • db_update($table, $params = array(), $where = array()) - Обновление информации в таблице
  • db_expr($value) - Возвращает sql выражение, которое будет напрямую вставлено в запрос. Используется как правило в функциях db_insert и db_update, например: db_expr('NOW()')

Транзакции

Транзакции реализованы функциями:

  • db_start_transaction
  • db_commit
  • db_rollback

В транзакции при любой ошибке в запросе кидается исключение, эти исключения нужно корректно обрабатывать.

Использование:

  1. db_transaction - функция-обертка для выполнения нескольких запросов в транзакции. Она самостоятельно откатывает транзакцию в случае ошибок или фиксирует её, если все запросы выполнены успешно. Возвращает true/false в зависимости от того, удалась ли транзакция.
$res = db_transaction(function(){
    db_query("INSERT INTO test_db (id, code, price) VALUES (3004, '1', 1)");
    db_query("INSERT INTO test_db (id, code, price) VALUES (3005, '1', 1)");
});
  1. Ручное выполнение транзакций
try {
    db_start_transaction();
    db_query("INSERT INTO test_db (id, code, price) VALUES (3004, '1', 1)");
    db_query("INSERT INTO test_db (id, code, price) VALUES (3005, '1', 1)");
    db_commit();
} catch (DbException $e) {
    db_rollback();
}

Примеры использования см. в файле DbMysqlTest.php

@KarelWintersky
Copy link

Спасибо за проделанную работу, но я уже всерьез задумался о PDO в новой версии движка.

С ним, правда, есть свои вопросы, ничуть не менее интересные :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment