Свои 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 | |
скачать