Зацените класс для работы с БД
Идея родилась давно, некоторые аспекты навеяны реализацией Д. Котерова - DBSimple
db_generic.php
class db_generic
{
function &connect($link)
{
$parsedLink = db_generic::parseLink($link);
if (!$parsedLink) return null;
$class = 'db_'.$parsedLink['engine'];
if (!class_exists($class))
{
$file = $class.".php";
$base = basename($file);
$dir = dirname(__FILE__);
if (@is_file($path = "$dir/$base"))
{
require_once($path);
}
else
{
trigger_error("Error loading database driver: no file \"$file;\"", E_USER_ERROR);
return null;
}
}
$object = new $class($parsedLink);
return $object;
}
function parseLink($link)
{
$parsedLink = null;
$parsedLink = explode("/",$link);
if (!$parsedLink) return null;
$result['engine'] = $parsedLink[0];
$result['host'] = $parsedLink[1];
$result['db'] = $parsedLink[2];
$result['user'] = $parsedLink[3];
$result['pass'] = $parsedLink[4];
return $result;
}
}
?>
db_mysql.php
require_once dirname(__FILE__).'/db_generic.php';
class db_mysql
{
var $link;
var $database;
function __construct($link)
{
$ok = $this->link = @mysql_connect(
$link['host'],
$link['user'],
$link['pass'],
true
);
if (!$ok) trigger_error("Could not connect to database", E_USER_ERROR);
$ok = @mysql_select_db($link['db'], $this->link);
if (!$ok) trigger_error("Could not select database", E_USER_ERROR);
$this->database = $link['db'];
}
function tableExists($name)
{
// Получаем список таблиц
$result = mysql_list_tables($this->database);
$rcount = mysql_num_rows($result);
// Проверяем каждый элемент списка на совпадения
for ($i=0;$i<$rcount;$i++)
{
if (mysql_tablename($result, $i)==$name) return true;
}
return false;
}
function arrayToString($array)
{
if (!is_array($array)) exit;
$quote = '';
if (is_string($array[0])) $quote = "'";
$str='';
foreach ($array as $a)
{
$str .= ", $quote".$a.$quote;
}
$str = substr($str,2);
return $str;
}
function parseQuery($query, $args)
{
$acount = count($args);
$parsedQuery = $query;
echo '<hr><b>debug info db_mysql.php [62]:</b> <i>'.$parsedQuery.'</i><hr>';
$parsedQuery = str_replace('?', '#?#', $parsedQuery);
echo '<hr><b>debug info db_mysql.php [65]:</b> <i>'.$parsedQuery.'</i><hr>';
for ($i=0;$i<$acount;$i++)
{
if (is_string($args[$i]))
{
$parsedQuery = preg_replace('/#{1}[\?]{1}#{1}/', "'".$args[$i]."'", $parsedQuery, 1);
}
if (is_int($args[$i]) or is_float($args[$i]))
{
$parsedQuery = preg_replace('/#{1}[\?]{1}#{1}/', $args[$i], $parsedQuery, 1);
}
if (is_array($args[$i]))
{
$parsedQuery = preg_replace('/#{1}[\?]{1}#{1}/', "(".$this->arrayToString($args[$i]).")", $parsedQuery, 1);
}
}
echo '<hr><b>debug info db_mysql.php [82]:</b> <i>'.$parsedQuery.'</i><hr>';
return $parsedQuery;
}
function isSelect($query)
{
$pos = stripos($query, 'select');
if ($pos !== false)
return true;
else
return false;
}
function query($query, $assoc = false, $args)
{
echo '<hr><b>debug info db_mysql.php [97]:</b> <i>'; print_r($args); echo'</i><hr>';
$parsedQuery = $this->parseQuery($query, $args);
$result = mysql_query($parsedQuery);
if (!$result) exit(mysql_errno());
$rows = null;
if ($this->isSelect($parsedQuery))
{
if (!$assoc)
{
while ($row = mysql_fetch_row($result))
$rows[] = $row;
return $rows;
}
else
{
while ($row = mysql_fetch_assoc($result))
$rows[] = $row;
return $rows;
}
}
else
{
echo '<hr><b>debug info db_mysql.php [97]:</b> <i> isSelect = '.$this->isSelect($parsedQuery).'</i><hr>';
$rows = mysql_affected_rows();
return $rows;
}
}
function selectCell($query, $assoc)
{
$args = func_get_args();
$args = array_slice($args,2);
$rows = $this->query($query, $assoc, $args);
if (!is_array($rows)) return $rows;
if (!count($rows)) return null;
reset($rows);
$row = current($rows);
if (!is_array($row)) return $row;
reset($row);
return current($row);
}
function selectRow($query, $assoc)
{
$args = func_get_args();
$args = array_slice($args,2);
$rows = $this->query($query, $assoc, $args);
if (!is_array($rows)) return $rows;
if (!count($rows)) return array();
reset($rows);
return current($rows);
}
function select($query, $assoc)
{
$args = func_get_args();
$args = array_slice($args,2);
$rows = $this->query($query, $assoc, $args);
return $rows;
}
function newId($table_name='', $id_name='')
{
if (!tableExists) return null;
$newId = $this->selectCell("SELECT max($id_name) FROM $table_name");
$newId++;
return $newId;
}
}
?>
Использование
$last_name = 'Petrov';
$name = 'Ivan'
$DB->select('SELECT * FROM some_table WHERE lastname = ? and first_name = ?', 1, $last_name, $name); // запрос к базе, результат возвращается в виде ассоциативного массива
Кому не лень - протестируйте плиз. Если будут вопросы - задавайте.
Лично мне не нравится в парсинге запросов подстановка параметров:
тока не знаю пока как сделать красивее и проще.
Принимается любая конструктивная критика и отзывы.
Ну, собственно, это баловство и нужно было для личных нужд, просто чтобы писать в последствии не кучу строк кода на каждый запрос, а одну. Кроме MySQL не планируется ничего использовать в обозримом будущем, а поддержку mysql_real_escape_string() обязательно добавлю попозже.
Сейчас это скорее наметка, заготовка.
ToDo на ближайшую неделю:
1. Защита от SQL Injections
2. Нормальная обработка ошибок.
У меня есть вопрос, по поводу парсинга параметров SQL, сейчас это реализовано с помощью регулярных выражений и preg_replace заменяет все знаки "?" по очереди на переданные аргументы.
Возникает казус когда передается строковый параметр содержащий "?".
Во избежание этого вставлен такой костыль: "?" заменяется сначала на "#?#", с расчетом что такая комбинация в параметре - большая редкость, потом все идет по старой схеме.
Есть другие варианты решения? Потому что это далеко не идеальное и казус все же может возникнуть, хоть и вероятность минимальна. Хочется исключить этот баг полностью.
PS: всем высказавшимся большое спасибо.
PPS: сейчас это действительно "ничего особенного", но новичкам может быть полезно, да и мне хочется довести это дело до ума, чтобы была простая реализация и интуитивное использование.
Есть другие варианты решения? Потому что это далеко не идеальное и казус все же может возникнуть, хоть и вероятность минимальна. Хочется исключить этот баг полностью.
Исопльзовать sprintf и не париться. Т.е. все поступившие параметры обрабатываешь mysql_escape_string, затем обычным sprintf все вставляешь. Париться намного меньше и людям намного понятей, чем эти вопросики юзать.
Во избежание этого вставлен такой костыль: "?" заменяется сначала на "#?#", с расчетом что такая комбинация в параметре - большая редкость, потом все идет по старой схеме.
Ну так все равно нельзя) А если будет такое?) Причем лишний раз будет пробегание по скрипту и замена. Вооот. Так что сделайте так, как я выше написал - самое оптимальное и разумное в данном случае
Ну так все равно нельзя) А если будет такое?) Причем лишний раз будет пробегание по скрипту и замена. Вооот. Так что сделайте так, как я выше написал - самое оптимальное и разумное в данном случае
Спасибо! Про sprintf - отлично. Про вопросики - у меня пережиток такой остался от java, очень привычно и %s, %d, ... даже на ум не приходили :)
mysql_escape_string - будет по умолчанию обязательно, сейчас еще хочу простенький errorHandler прикрутить, построенный на исключениях (чтобы была возможность эксепшены и на экран выводить и в логи писать по отдельности и вместе), или примерно так: обработчик выводит небольшое сообщение об ошибке, а в логи пишет полный stacktrace, дабы не показывать нехорошим людям что и где у нас сбоит, а для самих было бы все максимально прозрачно.
Например, мне не понятно, как можно передать параметры в sprintf из другой функции, например:
{
return sprintf($query /*,... как все необязательные параметры передать сюда */);
}
Ранее использовалась func_get_args() и все параметры в парсер передавались как массив, тут же такая конструкция не прокатит, как быть?
Короче такой глобальный вопрос "как передавать необязательные параметры в виде необязательных параметров?".
Вынесу этот вопрос в отдельную тему пожалуй...
Добавлено:
1. Настраиваемая обработка ошибок (отдельный, независимый класс), реализовано с помощью исключений, можно выводить на экран или писать в логи ошибку и ее стектрэйс.
2. Защита от SQL Injections.
Исправлено:
1. Парсинг запроса, теперь как sprintf.