Подготовленные выражения в 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() со следующими отличиями.

  1. При отсутствии параметра $fetch возвращается True, а не mysqli_result.
  2. mysqli_result вообще вернуть нельзя, поэтому формат no недопустим.
  3. Все форматы итераторов работают, но возвращают не итераторы, а массивы.

Также в 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()?

Эти выражения, конечно, хорошая вещь, но их эффективность проявляется на запросах, многократно повторяющихся в рамках сценария.

Для единичных запросов (а в отдельном процессе веб-приложения) их большинство, они, наоборот, хуже. Поэтому, пусть будут отдельно.

© Григорьев Олег aka vasa_c, 2006—2010