Свои SQL placeholder-ы

Вообще, плейсхолдеры - очень хорошая штука. Они позволяют избежать кучи ошибок и не дают создавать потенциально уязвимые запросы (для SQL инъекций) ну и плюсом являют собой синтаксический сахар, который помогает проще и быстрее работать с языком SQL.

Работа с плейсхолдерами обычно представляет собой нечто такое:
func( "select * from ? where `id`=?", "table1", 5 )

Внутри функции это развернется в:
select * from `table1` where `id`="5"

Всю работу по эскейпингу аргументов и заключение их в спецсимволы берет на себя как раз парсер плейсхолдеров.

Существуют так называемые "нативные" плейсхолдеры - они реализуются самой БД или драйвером БД и работают очень быстро, однако функционал их очень беден и работа отличается от БД к БД.

Я же расскажу здесь о "внешних" плейсхолдерах - т.е. реализуемых на стороне скриптового языка. Они чуть медленнее, так как обработка происходит в самом скрипте, но имеют на порядок больше функционал и независимы от БД.
В моем случае падение скорости совсем незначительное - используется обычное PCRE регулярное выражение для замены плейсхолдеров в строке и небольшая логика - в исходниках класса можно посмотреть.

Создать свои плейсхолдеры меня побудила статья про DBSimple от dkLab.
Там предлагались несколько плейсхолдеров, сильно облегчающих жизнь с SQL.
Идею и подход позаимствовал оттуда, а вот сами плейсхолдеры значительно изменил.

Обычные

В случае обычных плейсхолдеров, соответствующие им параметры передаются всегда в порядке появления их в строке.
пример
строка ?,?#=?{?}{?}
параметры a, b, c, d, e
сопоставление ? = a, ?# = b, ?{}{} = c, {?} = d, {?} = e

строка ?,?#=?{ ?{?[]}{123} }{?}
параметры a, b, c, d, e, f
сопоставление ? = a, ?# = b, ?{}{} = c, ?{}{} = d, {?[]} = e, {?} = f
?значение - параметр будет обработан согласно правилам обработки значений БД и заключен в соответствующие символы (например апострофы в MySQL)
?[]значения, разделенные запятыми - параметр должен быть массивом, каждое значение в массиве будет обработано также как ? и разделено запятыми
?[k]аналогично ?[] за исключением того, что в качестве значений будут взяты ключи массива
?~значение LIKE - параметр будет обработан согласно правилам БД для аргумента оператора LIKE и заключен в символы %
?#имя - параметр будет обработан согласно правилам обработки имен БД и заключен в соответствующие символы (например обратные апострофы для MySQL)
?#[]имена, разделенные запятыми - параметр должен быть массивом, каждое значение в массиве будет обработано также как ?# и разделено запятыми
?#[k]аналогично ?#[] за исключением того, что в качестве значений будут взяты ключи массива
?=[]имя = значение, разделенные запятыми - параметр должен быть массивом, ключ массива будет считаться именем и будет обработан также как ?#, значение массива будет обработано также как ?, и далее преобразовано к виду: имя=значение и разделено запятыми
?!"сырой" SQL - значение параметра будет просто вставлено в данное место без какой-либо обработки
?{...}{...}выборка по условию - если первый параметр true будет вставлено содержимое первых фигурных скобок, иначе - вторых. Условия могут быть вложенными. Если внутри скобок есть плейсхолдеры, то соответствующие им параметры должны идти все в порядке появления их плейсхолдеров в строке, вне зависимости от скобок.
??преобразуется в символ ?
?<преобразуется в символ {
?>преобразуется в символ }

Именованные

В этом случае все параметры передаются в виде одного именованного массива.
Комбинировать именованные и обычные плейсхолдеры нельзя.

Выглядят они почти так-же как и обычные: ?:name, ?[]:name, ?=[]:name и т.д., где name - имя плейсхолдера.

Имя может быть большими или маленькими буквами латинского алфавита, цифрами, или символами _ и -, и означает имя ключа из переданного массива.

Для ?#[], ?#[k], ?[], ?[k] и ?=[] имя может быть * - это будет означать использование текущего массива, а не его элемент по ключу name.

В случае именованных плейсхолдеров можно использовать одно и тоже значение по ключу много раз "?:field, ?~:field", и т.д.

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

Вызвать преобразование строки с плейсхолдерами можно двумя способами:

PLACEHOLDERS::parse( $context, $query, $p1, $p2, $p3, ... ) либо PLACEHOLDERS::parseByArr( $context, $query, array( $p1, $p2, $p3 ... ) )


Где $context - экземпляр класса, где определены функции, вызываемы в ходе работы парсера (обычно это класс БД), $query - строка запроса с плейсхолдерами, $p1, $p2, $p3 ... - значения плейсхолдеров.

Если парсинг прошел без ошибок - вернет строку, иначе - null, а получить ошибку можно с помощью PLACEHOLDERS::getErr(), что вернет строку с ошибкой. Кроме того, проверить, была ли ошибка можно через PLACEHOLDERS::isErr() - true если ошибка была, иначе false.


В классе, передаваемом в $context должны быть определены следующие функции:

prepareName - принимает в качестве аргумента строку и возвращает ее же, обработанную согласно правилам БД для имен и заключенную в соответствующие символы.

prepareValue - принимает в качестве аргументов строку и логическое значение модификатора работы.
Если модификатор равен false - возвращает строку, обработанную согласно правилам БД для значений и заключенную в соответствующие символы.
Если модификатор равен true - возвращает строку, обработанную согласно правилам БД для аргумента оператора LIKE, заключенную в соответствующие символы + символы %.

Понять проще на примере для MySQL:
class MySQLp {
	function prepareName( $s ) {
		return "`" . str_replace( "`", "", $s ) . "`";
	}
 
	function prepareValue( $s, $like ) {
		if ( $like ) return "'%" .
								str_replace(
									array( '\\', "\0", "\n", "\r", "'", '"', "\x1a", "%", "_" ),
									array( '\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z', '\\%', '\\_' ),
									$s
								) .
								"%'";
 
		return "'" .
				str_replace(
					array( '\\', "\0", "\n", "\r", "'", '"', "\x1a" ),
					array( '\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z' ),
					$s
				) .
				"'";
	}
}
 
var $my = new MySQLp();
 
var_dump(
	PLACEHOLDERS::parse(
		$my,
		"update `table` set ?=[]",
		array( "name" => 1, "name2" => 2 )
	)
);
 
if ( PLACEHOLDERS::isErr() ) var_dump( "Error: " . PLACEHOLDERS::getErr() );

Скачать placeholders.php, версия от 27.07.2014

История версий:

27.07.2014
  • исправлена некорректная проверка ошибок при пропуске одной из скобок в конструкции вида ?{}{}

    Теперь при пропуске какого либо фрагмента не проверяется корректность переданных туда выражений, проверяется только их корректное количество
09.09.2013
  • первый релиз
скачать

08.08.2013, Protocoder
Написать комментарий