Класс для безопасной работы с MySQL

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Итак, какая же цель? Облегчение говнокодинга? Или его устранение?
цель - принять Иисуса реальность, а не жить в облачном хостинге, почесывая ЧСВ о новичков в стиле "сначала добейся, заработай на правильный хостинг, потом поговорим"
цель Фанат сформулировал в первом посте: сделать мир лучше для студентов, создать удобную простую библиотеку для новичков

студенты не пойдут платить 300 р за хостинг для своего сайтика потому что там правильно работает mb_string
нет, они выкинут все наши правильные библиотеки, позавидуют всем нам умным и богатым со своими серверами, и напишут по-своему, криво, но работающее у них

если мы хотим сделать мир лучше для людей - надо решать их проблемы, а не свои
 

Фанат

oncle terrible
Команда форума
Пока родил вот это.
Полноценно тестировать нет уже мочи, но, вроде, работает.
PHP:
	private function prepareQuery($args)
	{
		$query = '';
		$raw   = array_shift($args);
		$array = preg_split('~(\?[nsiuap?])~',$raw,null,PREG_SPLIT_DELIM_CAPTURE);
		$anum  = count($args);
		$pnum  = floor(count($array) / 2);

		if ( $pnum != $anum )
		{
			$this->error("Number of args ($anum) doesn't match number of placeholders ($pnum) in [$raw]");
		}

		foreach ($array as $i => $part)
		{
			if ( $i % 2 )
			{
			    $value = array_shift($args);
				switch ($part)
				{
					case '?n':
						$part = $this->escapeIdent($value);
						break;
					case '?s':
						$part = $this->escapeString($value);
						break;
					case '?i':
						$part = $this->escapeInt($value);
						break;
					case '?a':
						$part = $this->createIN($value);
						break;
					case '?u':
						$part = $this->createSET($value);
						break;
					case '?p':
						$part = $value;
						break;
					case '??':
						$part = '?';
						break;
					default:
						$this->error("Unknown error");
				}
			}
			$query .= $part;
		}
		return $query;
	}
 

Фанат

oncle terrible
Команда форума
Так.
Оно косячит на удвоенных вопросах.
Удвоенные вопросы мы делаем для экранирования.
Ну так может и нахрен это экранирование?
В запросе сочетание ?s само по себе встретиться не может.
В передаваемых данных оно нам теперь по барабану
Вуаля! Выкидываем вопрос из паттерна.

В итоге код получился даже проще.

PHP:
	private function prepareQuery($args)
	{
		$query = '';
		$raw   = array_shift($args);
		$array = preg_split('~(\?[nsiuap])~u',$raw,null,PREG_SPLIT_DELIM_CAPTURE);
		$anum  = count($args);
		$pnum  = floor(count($array) / 2);
		if ( $pnum != $anum )
		{
			$this->error("Number of args ($anum) doesn't match number of placeholders ($pnum) in [$raw]");
		}

		foreach ($array as $i => $part)
		{
			if ( ($i % 2) == 0 )
			{
				$query .= $part;
				continue;
			}

		    $value = array_shift($args);
			switch ($part)
			{
				case '?n':
					$part = $this->escapeIdent($value);
					break;
				case '?s':
					$part = $this->escapeString($value);
					break;
				case '?i':
					$part = $this->escapeInt($value);
					break;
				case '?a':
					$part = $this->createIN($value);
					break;
				case '?u':
					$part = $this->createSET($value);
					break;
				case '?p':
					$part = $value;
					break;
			}
			$query .= $part;
		}
		return $query;
	}
Единственное ограничение - нельзя динамически собирать сами шаблоны запроса.
Его надо будет оговорить специально.
 

Фанат

oncle terrible
Команда форума
?p допускает иньекции, а мой сниппет из лички - нет ;)
Во-первых, по поводу "допускает инъекции". Их допускает банальная переменная в запросе. Так что если дурак захочет - он всё равно поломает.
Во-вторых, снипет обладает очень ограниченной функциональностью.
Всё суть parse() в том, что ты можешь ваять не толкьо те конструкции, которые предусмотрел автор либы, а вообще любые. соблюдая при этом главное правило безопасности - "любые динамические элементы попадают в запрос только через плейсхолдер".
Хотя, может, я не до конца понимаю принцип работы снипета. Если покажешь мне примеры, как с твоим снипетом делаются
http://www.phpfaq.ru/examples#multiinsert
http://www.phpfaq.ru/examples#onduplicate (ладно, пусть без ?u - допустим, SET собирается руками)
http://www.phpfaq.ru/examples#where
http://www.phpfaq.ru/examples#complex
- тогда я всерьёз задумаюсь над тем, чтобы сделать что-то подобное.
 

Фанат

oncle terrible
Команда форума
Кстати, ты лукавишь.
Если говорить про ?p, то аналогом его является не сам снипет, а функция привязки результата к объекту - то есть, часть квери-билдера.
 

Василий М.

Новичок
PHP:
$w = array();
$where = '';
if ($one) $w[] = $db->parse("one = ?s",$one); 
if ($two) $w[] = $db->parse("two IN (?a)",$two);
if ($tre) $w[] = $db->parse("tre <= ?i",$tre);
if (count($w)) $where = "WHERE ".implode(' AND ',$w);
$data = $db->getArr("SELECT * FROM table ?p LIMIT ?i,?i",$where, $start,$per_page);
я категорически против такого подхода, не знаю почему, но это мне не нравится.
ИМХО запрос должен формироваться как есть, аргументы - складироваться в массив и все это вызываться через
PHP:
call_user_func_array(array($this->db, 'query'), $args)
 

С.

Продвинутый новичок
Василий М., поскольку плейсхолдеры непоименованные, замучаешься соответствие аргументов соблюдать.
 

С.

Продвинутый новичок
Запросы не всегда бывают фиксированного вида. Часто (если не очень часто) запрос формируется динамически, проходя сначала через кучу if'ов и foreach'ей. Замучаешься соответствие аргументов соблюдать.
 

weregod

unserializer
Во-первых, по поводу "допускает инъекции". Их допускает банальная переменная в запросе. Так что если дурак захочет - он всё равно поломает.
Во-вторых, снипет обладает очень ограниченной функциональностью.
Сниппет допускает в plain() только строки без кавычек или объекты класса сниппета, то есть мышь не проскакивает ;)

Хотя, может, я не до конца понимаю принцип работы снипета. Если покажешь мне примеры, как с твоим снипетом делаются
http://www.phpfaq.ru/examples#multiinsert
PHP:
$query = new DB_Snippet("INSERT INTO table VALUES " . mb_substr(str_repeat('%s,', sizeof($data), 0, -1));
foreach ($data as $row) {
    $query->plain(
        new DB_Snippet("(NULL,%s,%s, NOW())")
            ->q($row['name'])
            ->q($row['lastname'])
    )
}
$db->query($query);
Остальное по аналогии.
 

Фанат

oncle terrible
Команда форума
Ну, может, оно так и безопаснее. Поздравляю, у тебя получилось лучше.
 

Фанат

oncle terrible
Команда форума
я только не понял.
если снипет пропускает только без кавычек - это получается, что
PHP:
"INSERT INTO `table` VALUES "
в него передать нельзя?
 

WMix

герр M:)ller
Партнер клуба
я понял иначе, plain пропускает только строки без кавычек. а в сам Snippet возможно передать любой string...
PHP:
class DB_Snippet{
public function plain($q){
  if(is_string($q))
    $q=addslashes($q);
  elseif( $q instanceof DB_Snippet ) 
    $q=$q->toString()
  else throw ....
  ..
}
}
 

fixxxer

К.О.
Партнер клуба
Василий М.
Подобные классы нафиг не нужны никому, кроме новичков, и носят исключительно образовательную роль.
А тебя слушать никто не будет, потому что ты все время ведешь себя, как придурок, даже если пишешь все хорошо и правильно :D
 

weregod

unserializer
я только не понял.
если снипет пропускает только без кавычек - это получается, что
PHP:
"INSERT INTO `table` VALUES "
в него передать нельзя?
обратные кавычки можно ;)
одинарные и двойные можно только через DB_Query::q(), оно их безусловно квотирует
 
Сверху