Last active
August 29, 2015 14:14
-
-
Save silverslice/b2aa3d9ec7a6a742d8d9 to your computer and use it in GitHub Desktop.
Mysql wrapper for legacy projects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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 {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
} | |
} |
Спасибо за проделанную работу, но я уже всерьез задумался о PDO в новой версии движка.
С ним, правда, есть свои вопросы, ничуть не менее интересные :)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Плейсхолдеры
Любые данные подставляются в запрос через плейсхолдеры. Типы плейсхолдеров:
Основные функции выборки данных
Транзакции
Транзакции реализованы функциями:
В транзакции при любой ошибке в запросе кидается исключение, эти исключения нужно корректно обрабатывать.
Использование:
Примеры использования см. в файле DbMysqlTest.php