Подготовленные выражения в goDB
Что такое подготовленные выражения и какова их реализация в mysqli, неплохо объяснено здесь.
Реализация в mysqli основана на методе prepare()
и
классе mysqli_stmt
.
Она несколько многословная и goDB предоставляет некоторые дополнительные средства для её упрощения.
prepare() и класс goDBPrepare
Метод prepare()
расширен новым аргументом:
object goDB::prepare(string $query [, bool $godb]);
По умолчанию он работает также, как и прежде: возвращает объект класса mysqli_stmt
.
При указании же в качестве второго аргумента True
, возвращает объект класса goDBPrepare
.
$mysqliSTMT = $db->prepare('INSERT INTO `table` SET `name`=?');
$godbPrepare = $db->prepare('INSERT INTO `table` SET `name`=?', true);
Создать данный объект можно и через new goDBPrepare
:
goDBPrepare::__construct(goDB $db, string $query [, bool $lazy]);
$lazy
: отложенное подключение.
Если указать True
подготовленное выражение создастся только при попытке его использовать.
Основной метод goDBPrepare: execute()
.
mixed goDBPrepare::execute(array $data, string $fetch);
$prepare = $db->prepare('SELECT `name` FROM `users` WHERE `user_id`=?');
$name5 = $prepare->execute(array(5), 'el');
$name7 = $prepare->execute(array(7), 'el');
Это аналогично запросу в goDB с его pattern-data-fetch
, со следующими отличиями.
pattern
Подготовленные выражения используют свой формат с плейсхолдерами и плейсхолдеры goDB здесь не действуют.
По сути в подготовленных выражениях изначально есть только один плейсхолдер "?"
.
Однако, в mysqli::bind_param()
можно было указывать типы параметров в целях оптимизации.
В goDB указывать строку типов при запросе не нужно, но можно указать эти типы непосредственно для плейсхолдеров:
$prepare = $db->prepare('INSERT INTO `users` SET `name`=?s, `age`=?i, `double`=?d');
$userId = $prepare->execute(array('Вася', 33, 1/3), 'id');
Возможные форматы: ?s, ?i, ?d, ?b
.
Простой ?
соответствует ?s
.
Возможно завершать плейсхолдер точкой с запятой.
data
Этот такой же порядковый массив входных данных. Но так как нет плейсхолдеров goDB, то нет и сложных структур: только строки или числа.
fetch
execute()
поддерживает практически все те же форматы разбора,
что и goDB::query()
со следующими отличиями.
- При отсутствии параметра
$fetch
возвращаетсяTrue
, а неmysqli_result
. mysqli_result
вообще вернуть нельзя, поэтому форматno
недопустим.- Все форматы итераторов работают, но возвращают не итераторы, а массивы.
Также в goDBPrepare
определён магический метод
__invoke()
,
так что в PHP 5.3 выполнять подготовленные выражения можно и так:
$prepare = $db->prepare('INSERT INTO `users` SET `name`=?s, `age`=?i, `double`=?d');
$userId = $prepare(array('Вася', 33, 1/3), 'id');
Другие методы goDBPrepare
Закрытие подготовленного выражение когда оно более не нужно:
void goDBPrepare::close(void);
$prepare = $db->prepare($pattern);
$prepare->execute($data1);
$prepare->execute($data2);
$prepare->execute($data3);
$prepare->close();
После закрытия, execute()
и getSTMT()
начинают возвращать False
.
Узнать, закрыто ли уже выражение или нет, можно через isClosed()
.
Получение mysqli_stmt, соответствующего выражению, для более тонких манипуляций:
mysqli_stmt goDBPrepare::getSTMT(void);
$prepare = $db->prepare($pattern);
$stmt = $prepare->getSTMT();
var_dump($stmt->param_count);
Получение строки типов и итогового шаблона:
string goDBPrepare::getTypes(void);
string goDBPrepare::getQuery(void);
$prepare = $db->prepare('INSERT INTO `table` VALUES (?,?i,?i;,?d)');
$prepare->getTypes(); // siid
$prepare->getQuery(); // INSERT INTO `table` VALUES (?,?,?,?)
prepareExecute()
Вообще, создавать объект и работать с ним, нет необходимости. Можно просто:
mixed $db->prepareExecute(string $pattern, array $data, string $fetch [, mixed $cache = true]);
$userId = $db->prepareExecute('INSERT INTO `users` SET `name`=?', array('Вася'), 'id');
Обратите внимание на то, что несмотря на похожий формат с query()
, есть отличия.
$pattern
является шаблоном подготовленного выражения и поддерживает только его плейсхолдеры,
но не плейсхолдеры goDB. Соответственно и $data
может содержать только скалярные значения.
$fetch
же практически идентичен, за исключением того, что нельзя получить mysqli_result
.
Аргумент $cache
управляет кэшированием запроса и может содержать True
, False
или строку.
$cache=False
: нет кэширования, одноразовое выполнение.
Подготовленное выражение создаётся, одноразово используется и уничтожается.
$cache=True
(по умолчанию): подготовленное выражение кэшируется.
В следующем примере будет использоваться одно выражение.
$userId1 = $db->prepareExecute('INSERT INTO `users` SET `name`=?', array('Вася'), 'id');
$userId2 = $db->prepareExecute('INSERT INTO `users` SET `name`=?', array('Петя'), 'id');
$userId3 = $db->prepareExecute('INSERT INTO `users` SET `name`=?', array('Миша'), 'id');
$userId4 = $db->prepareExecute('INSERT INTO `users` SET `name`=?', array('Гриша'), 'id');
$cache=имя выражения
: создание именованного выражения (см. ниже).
Именованные подготовленные выражения
В качестве $cache
можно указать имя подготовленного выражение, под которым оно будет сохранено.
После чего вместо запроса можно указывать это имя:
$userId1 = $db->prepareExecute('INSERT INTO `users` SET `name`=?', array('Вася'), 'id', 'user:create');
$userId2 = $db->prepareExecute('user:create', array('Петя'), 'id');
$userId3 = $db->prepareExecute('user:create', array('Миша'), 'id');
$userId4 = $db->prepareExecute('user:create', array('Гриша'), 'id');
Во избежание неоднозначности (запрос это или имя), имена можно предварять решёткой.
user:create
и #user:create
равнозначны.
$userId5 = $db->prepareExecute('#user:create', array('Маша'), 'id');
Можно заранее насоздавать именованных подготовленных выражений, а потом использовать
$db->prepareNamed('user:create', 'INSERT INTO `users` SET `name`=?');
$db->prepareNamed('user:select', 'SELECT * FROM `users` WHERE `user_id`=?i');
$db->prepareNamed('user:remove', 'DELETE FROM `users` WHERE `user_id`=?i');
В этом случае будет использоваться ленивая инициализация подготовленного выражения. То есть запрос к базе на его создание будет послан только перед первым использованием.
getPrepare()
Получить закэшированное выражение (как с указанием запроса, так и с указанием имени) можно с помощью следующего метода:
goDBPrepare goDB::getPrepare(string $query [, bool $create = false]);
Если такого выражение нет в кэше и $create=False
, возвращается False
.
Иначе: выражение создаётся, кэшируется и возвращается.
Вариант с $create=True
можно использовать, как альтернативу prepare()
,
так как prepare()
не смотрит и не записывает в кэш.
Ошибки
При создании ошибочного подготовленного выражения генерируется goDBExceptionPrepareCreate
.
В случае использования отложенного создания, это исключение может выпасть из
execute
или getSTMT()
.
При выполнении подготовленного выражения могут генерироваться
goDBExceptionQuery
(ошибка запроса), goDBExceptionData*
(неверное количество входных данных),
goDBExceptionFetch*
(неверный формат разбора).
При указании несуществующего имени для именованных выражений генерируется
goDBExceptionPrepareNamed
.
Почему не в ядре?
Почему не внедрить подготовленные выражения непосредственно в метод query()
?
Эти выражения, конечно, хорошая вещь, но их эффективность проявляется на запросах, многократно повторяющихся в рамках сценария.
Для единичных запросов (а в отдельном процессе веб-приложения) их большинство, они, наоборот, хуже. Поэтому, пусть будут отдельно.