Главное меню

Класс для работы с Базой Данных (БД)

В качестве класса для работы с БД используется немного доработанная библиотека DBSimple Дмитрия Котерова

Библиотека имеет поддержку placeholder-ов: для вставки данных в строку SQL-запроса используются специальные маркеры, например, "?", а сами данные передаются позже: query('SELECT * FROM tbl WHERE id=?', $id). Это позволяет почти полностью возложить на библиотеку заботу о корректности передаваемых в запрос параметров и обезопасить приложение от SQL-инъекций.

Ниже приведена сокращенная версия руководства, которая позволит разрабатывать модули для LAB CMS.

Основные placeholder-ы

Чтобы избежать самой популярной среди скриптописателей проблемы с безопасностью — SQL Injection, существует хороший способ: использовать в запросах placeholder-ы и переложить обработку «небезопасных» данных на плечи библиотеки работы с СУБД. Иными словами, там, где «плохой» программист пишет «дырявый» код:

PHP
Db::i()->select("SELECT * FROM tbl WHERE a='$a' AND b='$b'");

«хороший» применит placeholder-ы:

PHP
Db::i()->select('SELECT * FROM tbl WHERE a=? AND b=?', $a, $b);

и, тем самым, гарантированно избавит себя от дыр вида SQL Injection. Основными для DbSimple являются placeholder-ы ? (вставка строки) и ?a (вставка списка или массива). Они используются в подавляющем большинстве случаев.

Строковой (бинарный): ?

Это самый простой вид placeholder-а, используемый в подавляющем большинстве случаев. Вставляемое на его место значение обрамляется апострофами ('), при этом все символы, которые СУБД считает служебными, экранируются в соответствии с правилами этой СУБД (в MySQL перед апострофами вставляется \).

PHP
Db::i()->select('SELECT * FROM tbl WHERE a=?', "test'string");
// MySQL: SELECT * FROM tbl WHERE a='test\'string'

Правильнее было бы назвать данный placeholder не строковым, а бинарным, т.к. с его помощью в БД можно вставлять произвольные бинарные данные (в том числе — картинки, исполняемые файлы и т. д.).

Если значение подставляемого параметра равно null, вместо обрамления его апострофами вставляется ключевое слово SQL NULL. Это же верно и для всех остальных типов placeholder-ов.

PHP
Db::i()->query('UPDATE tbl SET a=?', null);
// MySQL: UPDATE tbl SET a=NULL

Списковый/ассоциативный: ?a

Данный вид placeholder-ов удобно использовать для составления IN-выражений в SQL — при условии, что в программе имеется список с перечисленными значениями:

PHP
$ids = array(1, 101, 303);
Db::i()->select('SELECT name FROM tbl WHERE id IN(?a)', $ids);
// SELECT name FROM tbl WHERE id IN(1, 101, 303)

Помните, что в случае передачи пустого списка вы получите ошибочный SQL-запрос:

PHP
$ids = array();
Db::i()->select('SELECT name FROM tbl WHERE id IN(?a)', $ids);
// SELECT name FROM tbl WHERE id IN() - ОШИБКА!

Если в качестве параметра передан ассоциативный массив (ключи массива не целочисленные, а строковые), DbSimple заменяет ?a набором пар ключ=значение. Это удобно использовать в UPDATE-запросах:

PHP
$row = array(
  'id'   => 10,
  'date' => "2006-03-02"
);
Db::i()->query('UPDATE tbl SET ?a', $row);
// MySQL: UPDATE tbl SET `id`='10', `date`='2006-03-02'

Дополнительные placeholder-ы

Для удобства работы DbSimple поддерживает еще целый набор видов placeholder-ов, которые будут описаны далее. Они используются значительно реже и, как правило, позволяют просто сократить письмо. Если вы думаете, что все это слишком сложно для понимания, — не используйте дополнительные placeholder-ы.

Префиксный: ?_

Часто все имена таблиц, используемых в программе, имеют один и тот же префикс. Например, в форуме phpBB этот префикс, как правило, равен phpbb_, и таблицы называются phpbb_users, phpbb_sessions и т. д. Это делается для того, чтобы в одной базе данных можно было хранить сразу несколько наборов таблиц для разных форумов, избегая конфликтов имен.

Префиксный placeholder заменяется на некоторое фиксированное значение, ранее установленное для объекта базы данных:

PHP
define(TABLE_PREFIX, 'phpbb_'); // с подчерком!
Db::i()->setIdentPrefix(TABLE_PREFIX);
...
Db::i()->select('SELECT * FROM ?_users');
// SELECT * FROM phpbb_users
 
// Сравните:
$db->select('SELECT * FROM '.TABLE_PREFIX.'_users');
Если быть точным, ?_ не является обычным placeholder-ом, т.к. для него очередное значение не извлекается из списка параметров, а берется из другого источника. Это скорее удобная макроподстановка.

Идентификаторный: ?#

Ключевые слова SQL, такие как date, int и т. д., не могут использоваться в качестве имен полей и таблиц. Например, у вас не получится создать в таблице столбец с именем date. Тем не менее, многие СУБД предлагают способы, позволяющие все же ссылаться на подобные объекты. Имена идентификаторов следует окружить теми или иными ограничителями:

  • MySQL использует обратные апострофы (backticks): table.`date`.

Идентификаторный placeholder заставляет СУБД воспринимать значение как идентификатор:

PHP
Db::i()->select('SELECT ?# FROM tbl', 'date');
// MySQL: SELECT `date` FROM tbl
 
Db::i()->select('SELECT ID AS ?# FROM tbl', 'this is ID');
// MySQL: SELECT ID AS `this is ID` FROM tbl

Конечно, использование идентификаторного placeholder-а полностью защищает от уязвимостей вида SQL Injection. Передаваемый параметр обрамляется «идентификаторными кавычками», а если они встречаются в нем самом, то экранируются специфичным для СУБД образом.

Правила экранирования определятюся тем же самым виртуальным методом escape(). Для экранирования в стиле идентификаторов второй параметр полагается равным true.

Идентификаторно-списковый: ?#

Уже знакомый нам идентификаторный placeholder ?# может принимать в качестве значения не только строку, но также и массив (список значений). Это очень удобно для формирования INSERT-запросов в соответствии со стандартом SQL'92:

PHP
$row = array('id' => 101, 'name' => 'Rabbit', 'age' => 30);
Db::i()->query('INSERT INTO table(?#) VALUES(?a)', array_keys($row), array_values($row));

В зависимости от того, передаете вы placeholder-у ?# массив или строку, он "развернется" в список идентификаторов или в единственный идентификатор соответственно.

Вы можете спросить, почему же для вставки списка значений используется отдельный placeholder ?a, а для списка идентификаторов — тот же самый ?#. Ответ достаточно прост: применение ? для обработки и скаляра, и списка небезопасно, т.к. данные могут быть получены из формы и представлены злоумышленником в виде массива. Например, можно передать id[]=1&id[]=2 вместо id=1 в QUERY_STRING и "сломать" запрос Db::i()->select('SELECT * FROM table WHERE id=?', $_GET['id']). В то же время, "фальсифицировать" передачу списка идентификаторов вместо единственного значения практически невозможно.

Целочисленный: ?d

Переданный параметр преобразуется в целое цисло и вставляется без обрамления апострофами. В случае ошибки конвертирования вставляется 0.

Может возникнуть вопрос, зачем нужны целочисленные placeholder-ы, если СУБД и так умеют преобразовывать строки в числа? Например, MySQL конвертирует '10' в 10 при вставке в числовое поле. Оказывается, это верно не для всех существующих в мире СУБД. Кроме того, в выражении LIMIT ?, ? MySQL требует обязательной подстановки чисел, а не строк.

Вещественный (дробный): ?f

Вещественный placeholder можно использовать для передачи дробных (вещественных) чисел в СУБД. В зависимости от локальных настроек для разделения компонент PHP использует либо точку, либо запятую, в то время как стандарт SQL требует обязательного использования точки безотносительно к локальным настройкам. Чтобы не связываться с локальными настройками, просто применяйте дробный placeholder.

Ссылочный: ?n

В большинстве таблиц, с которыми приходится работать, присутствует целочисленный столбец с именем IDпервичный ключ данной таблицы. На этот столбец устанавливают уникальный индекс и "навешивают" auto_increment, чтобы при вставке очередной записи в таблицу она автоматически получала уникальный номер (как правило, отличный от нуля).

На первичный ключ очень удобно ссылаться из другой (или из той же самой) таблицы, такие ссылки называют внешними ключами. Например, у нас может быть таблица forest с полями (ID, PARENT_ID, NAME), определяющая множество деревьев. Чтобы не нарушать ссылочной целостности, корневой элемент каждого дерева должен иметь PARENT_ID=NULL.

Предположим, мы пишем скрипт, который вставляет в forest новую запись. ID родительского узла передается так: http://example.com/tree.php?parent=123. Что делать, когда нам нужно передать NULL в качестве идентификатора родителя?

PHP
Db::i()->query(
  'INSERT INTO forest(PARENT_ID, NAME) VALUES(?, ?)',
  ($_GET['parent']? $_GET['parent'] : null), $name
);

Теперь можно передать в GET-параметр parent значение "" или 0 для вставки NULL.

Ссылочный placeholder позволяет немного упростить письмо:

PHP
Db::i()->query(
  'INSERT INTO forest(PARENT_ID, NAME) VALUES(?n, ?)',
  $_GET['parent'], $name
);

Подставляемое значение преобразуется в целое число. Если это число равно нулю, то вместо него подставляется NULL, иначе — оно само.

Ссылочный placeholder крайне удобно применять для "выправления" баз данных MySQL типа MyISAM, если для "узлов-сирот" применялся не NULL в качестве родителя, а обычный 0. К сожалению, такое часто встречается на практике, т.к. MyISAM, в отличие от BDB и InnoDB, не поддерживает ссылочную целостность, а написать PARENT_ID=? вместо PARENT_ID<=>? бывает весьма заманчиво. (См. документацию MySQL на операторы IS_NULL и <=> в отношении NULL-значений.)

Выполнение запросов к БД

Библиотека DbSimple имеет в своем имени слово simple ("простой") потому, что она максимально упрощает процедуру выполнения запросов к базе данных.

Выборка всего результата: select()

Данный метод является самым простым и универсальным. С его помощью вы можете выполнить запрос к базе данных и (если это SELECT-запрос) получить в двумерный массив все строки результата операции.

PHP
$rows = Db::i()->select(
  'SELECT * FROM ?_users WHERE username LIKE ?', 'к%'
);
foreach ($rows as $numRow=>$row) {
  foreach ($row as $colName=>$cellValue) {
    echo "$numRow: $colName = $cellValue<br>";
  }
}

Если вам не нужны все строки результата, ограничьте выборку стаднартными средствами SQL — например, предложением LIMIT в MySQL:

PHP
$rows = Db::i()->select('SELECT * FROM ?_users LIMIT 10');
foreach ($rows as $numRow=>$row) {
  ...
}
Обратите внимание, что промежуточный слой абстракции "объект-результат" не используется, а выборка в любом случае производится полностью, от первой записи до последней. Это сделано совсем не случайно: я имею твердую уверенность, что данный слой абстракции в скриптах совершенно излишен и только запутывает программу. Хотите ограничить выборку или устроить выборочную навигацию по результату (seeking) — используйте для этого средства SQL, а не PHP.

Выборка ассоциативного массива

Результат выборки, который попал в $rows (см. предыдущий пример), является списком массивов. Иными словами, ключи $rows — целые числа, большие либо равные нулю и идущие по порядку.

В ряде случаев оказывается удобным индексировать результат не целыми числами, а ассоциативными значениями, взятыми в одном из столбцов выборки. Например, если мы выбираем пользователей, в качестве ключа может быть использован их primary key в базе (идентификатор).

Чтобы DbSimple сформировал ассоциативный массив, а не список, используйте для столбца фиксированное имя ARRAY_KEY:

PHP
$rows = Db::i()->select(
  'SELECT user_id AS ARRAY_KEY, * FROM ?_users'
);
foreach ($rows as $userId=>$userData) {
  ...
}
echo $rows[$_REQUEST['uid']]['username'];
echo $rows[$_REQUEST['uid']]['ARRAY_KEY']; // ошибка: нет поля

По наличию столбца с именем ARRAY_KEY библиотека определит, какой формат данных вы ожидаете получить, и произведет соответствующие преобразования. Т.к. этот столбец является служебным, он сам в результат выборки не попадет (см. последнюю строчку примера).

Выборка многомерного массива

Если результат выборки необходимо оформить в виде многомерного ассоциативного массива, используйте следующий синтаксис:

PHP
$messagesByTopics = Db::i()->select('
    SELECT
        message_topic_id AS ARRAY_KEY_1,
        message_id AS ARRAY_KEY_2,
        message_subject, message_text
    FROM
        ?_message
'
);

На выходе получится двумерный ассоциативный массив: $messagesByForumsAndTopics[topicId][messageId] = messageData. Поле message_topic_id, объявленное как ARRAY_KEY_1, стало первым индексом массива, а поле message_id (ARRAY_KEY_2) — вторым.

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

PHP
$usersByCity = Db::i()->select('
    SELECT
        city_id AS ARRAY_KEY_1,
        NULL AS ARRAY_KEY_2,
        user_name, user_email
    FROM
        ?_user
'
);

В данном примере будет получен массив списков пользователей $usersByCity[cityId][] = userData. Т.е. каждый элемент массива, соответствующий некоторому городу, содержит обычный список записей с данными пользователей. Мы достигли получения обычного списка, передав NULL в качестве ARRAY_KEY_2.

Вообще, специальными являются все поля вида ARRAY_KEY* (здесь "*" означает "любой текст"). Перед формированием индексов ассоциативного массива эти поля сортируются в алфавитном порядке, так что ARRAY_KEY_1 всегда будет более "ранним" индексом, чем ARRAY_KEY_2.

Выборка связанного дерева

Иногда в таблице хранится древовидная структура: каждая запись содержит поле parent_id, ссылающееся на ID родительского элемента. DbSimple облегчает выборку и такой структуры, формируя вложенные древовидным образом массивы:

PHP
$forest = Db::i()->select('
  SELECT
    user_id   AS ARRAY_KEY,
    parent_id AS PARENT_KEY,
    *
  FROM ?_users
'
);

Строго говоря, на выходе в $forest мы получаем не дерево, а лес — набор однокоренных деревьев. Дело в том, что в результатах выборки могут присутствовать сразу несколько элементов, не имеющих родителей. Все такие элементы объявляются вершинами дерева, а их "дети" строятся по правилу: PARENT_KEY "ребенка" равен ARRAY_KEY "родителя".

У каждого элемента результирующего массива, помимо его собственных данных (в нашем случае это * — все поля записи), имеется запись с ключом childNodes. В ней-то и содержится массив всех "детей" текущего элемента (или пустой массив, если это листовая вершина).

Выборка строки: selectRow()

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

Часто бывают случаи, когда выборка гарантировано состоит из одной записи. Предположим, у нас есть ID некоторого объекта, и мы хотим получить данные его полей. Можно для этого воспользоваться методом select(), а потом взять первую строку результата, однако удобнее будет применить метод selectRow():

PHP
$row = Db::i()->selectRow(
  'SELECT * FROM ?_users WHERE user_id=?', $uid
);
// Теперь в $row - массив вида имяПоля => значениеПоля.

Выборка ячейки: selectCell()

Иногда нам нужны данные в еще более простом формате, чем выдает selectRow(). Например, мы хотим получить имя пользователя, зная его ID, и при этом совершенно не интересуемся всеми остальными его полями. Метод selectCell() подходит здесь как нельзя лучше:

PHP
$userName = Db::i()->selectCell(
  'SELECT username FROM ?_users WHERE user_id=?', $uid
);
// В $userName - строка, имя пользователя.

Выборка столбца: selectCol()

Последний вид форматирования результатов выборки — получение одного столбца. Метод selectCol() трактует результат как массив-столбец и возвращает данные в виде списка:

PHP
$cities = Db::i()->selectCol('SELECT city_name FROM ?_cities');
// В $cities - список имен всех городов.

Можно также индексировать массив ассоциативными значениями, а не целыми числами. Это делается при помощи служебного поля с уже знакомым именем ARRAY_KEY:

PHP
$citiesById = Db::i()->selectCol(
  'SELECT city_id AS ARRAY_KEY, city_name FROM ?_cities'
);
foreach ($citiesById as $id=>$name) { ... }

Выборка страницы: selectPage()

Организация страничного навигатора по некоторому результату выборки может оказаться настоящей головной болью, если не знать, как оптимальнее всего подойти к этому вопросу. Помимо выполнения запроса на получение строк очередной страницы нужно дополнительно подсчитывать общее число записей. Иными словами, нам нужен один запрос с предложением LIMIT, и один — с выборкой COUNT(*).

Метод selectPage() сводит эти две операции к одному вызову. С его помощью вы передаете СУБД запрос с необходимыми вам LIMIT-ограничениями, а библиотека дополнительно производит еще и COUNT-запрос:

PHP
$rows = Db::i()->selectPage(
  $totalRows,
  'SELECT * FROM ?_users LIMIT ?d, ?d',
  $from, $pageSize
);
// Теперь:
// - в $rows: данные очередной страницы
// - в $totalRows: общее число записей в полной выборке

Обратите особое внимание, что первый параметр метода является ссылочным: в переменную, указанную на его месте, записывается общее число попадающих под запрос записей, без учета LIMIT-предложения.

Метод selectPage() можно использовать только для «простых» SQL-запросов, не содержащих предложение UNION. В противном случае результат не определен.

Выполнение обновлений: query()

Как видите, до сих пор повествование "крутилось" вокруг различных вариаций метода select(), предназначенного для организации выборок из базы данных (SELECT). Но ведь существуют еще и команды вставки (INSERT) и обновления (UPDATE) данных. Как быть с ними?

Библиотека DbSimple поддерживает метод query(), который удобно использовать именно для подобных запросов. А теперь — сюрприз: query() является ни чем иным, как полным синонимом для пресловутого select(). А называется он по-другому, чтобы не пришлось думать: как это — INSERT в select().

Метод query() (а значит, и select() тоже!) возвращает различные значения для INSERT и UPDATE-запросов:

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

Вот несколько примеров:

PHP
// Обновляем запись.
Db::i()->query(
  'UPDATE ?_users SET ?a WHERE user_id=?',
  $userData, $userData['user_id']
);
// Вставляем запись (MySQL).
$userId = Db::i()->query('INSERT INTO ?_users SET ?a', $userData);

Обработка ошибок в запросах

При возникновении ошибки запроса вызывается зарегистрированный ранее обработчик ошибок. Он, как правило, завершает работу скрипта и выдает исчерпывающую информацию о контексте вызова запроса. В большинстве случаев это поведение — самое разумное, однако для некоторых запросов может понадобиться временно отключить стандартный механизм и обработать ошибку вручную, непосредственно в коде программы.

Библиотека DbSimple позволяет позапросно отключать обработку ошибок, используя для этого стандартную нотацию PHP — оператор @. Иными словами, поставив @ перед вызовом любого из методов DbSimple, вы заставите этот метод вернуть null в случае возникновения проблем. Далее вы можете извлечь контекст выполнения запроса из свойства error (краткое описание ошибки — из errmsg) и поступить с этой информацией так, как вам нужно.

Конечно, не следует злоупотреблять данной возможностью. Используйте ее только в случае, если вы точно уверены, что обрабатываете ошибку вручную непосредственно после выполнения запроса.

Типичный пример: мы хотим вставить некоторую запись в таблицу, но, если она там уже имеется (нарушение уникальности), не завершать работу программы, а выводить пользователю аккуратное предупреждение в форме. Это можно сделать так:

PHP
// Предположим, по полю username имеется уникальный индекс.
if (!@Db::i()->query('INSERT INTO ?_users SET username=?', $name)) {
  echo 'Такой пользователь уже существует, попробуйте другое имя';
}

Макроподстановки в SQL-запросах

Каждый, кто писал скрипты со сложными запросами к СУБД, знает, какие проблемы начинаются, если запрос требуется составлять динамически. Например, если нам требуется добавить в выражение WHERE некоторое ограничение, если пользователь поставил галочку в форме, и не выполнять его в противном случае. Традиционно в таких случаях применяют динамическое составление SQL-запросов, формируя их в виде строки:

PHP
$sql = '
    SELECT *
    FROM goods
    WHERE category_id = ?
'
;
if (!empty($_POST['activated_at'])) {
    $sql .= ' AND activated_at IS NOT NULL';
}
$sql .= " ORDER BY price";
$rows = $db->select($sql, $categoryId);

Теперь представьте, что на activated_at наложено более сложное условие, учитывающее также и его величину:

PHP
$query = array($categoryId);
$sql = '
    SELECT *
    FROM goods
    WHERE category_id = ?
'
;
if (!empty($_POST['activated_at'])) {
    $sql .= ' AND activated_at > ?';
    $placeholders[] = $_POST['activated_at'];
}
$sql .= " ORDER BY price";
array_unshift($query, $sql);
$rows = call_user_func_array(array(&$db, 'select'), $query);

В примере выше мы используем всего одно динамическое поле, но на практике их может быть значительно больше. В результате читабельность кода резко снижается, не говоря уж о читабельности генерируемых SQL-запросов...

К счастью, данная проблема относится к классу беспроигрышно-разрешимых. А именно, имеется такой синтаксис, который позволяет создавать динамические SQL-запросы без какого-либо снижения читабельности кода! Он используется в DbSimple:

PHP
$rows = Db::i()->select('
        SELECT *
        FROM goods
        WHERE
            category_id = ?
          { AND activated_at > ? }
    '
,
    $categoryId,
    (empty($_POST['activated_at'])? DBSIMPLE_SKIP : $_POST['activated_at'])
);

Обратите внимание на блок, обрамленный фигурными скобками ({}-блок). Нетрудно догадаться, как он работает: если хотя бы один placeholder, используемый в этом блоке, имеет специальное значение DBSIMPLE_SKIP, то весь блок удаляется из запроса, в противном случае удаляются только обрамляющие фигурные скобки (точнее, они заменяются на пробелы, чтобы сформированный SQL-запрос хорошо читался).

В настоящий момент значение DBSIMPLE_SKIP определяется в библиотеке как log(0). Т.к. логарифма нуля в природе не существует, константа принимает значение "недопустимое значение: логарифм нуля" (оказывается, есть в PHP такое значение для числа с плавающей точкой), и вероятность того, что кому-то потребуется вставить его в БД, падает до нуля (да оно и не вставится для большинства СУБД).

Начав однажды пользоваться {}-макросами, через некоторое время перестаешь понимать, как же обходился без них раньше. Вот еще примеры запросов с макроподстановками:

PHP
$rows = Db::i()->select('
        SELECT *
        FROM
            goods g
          { JOIN category c ON c.id = g.category_id AND c.name = ? }
    '
,
    (empty($_POST['cat_name'])? DBSIMPLE_SKIP : $_POST['cat_name'])
);
PHP
$rows = Db::i()->select('
        SELECT *
        FROM
            goods g
          { JOIN category c ON c.id = g.category_id AND 1 = ? }
        WHERE
            1 = 1
          { AND c.name = ? }
    '
,
    (empty($_POST['cat_name'])? DBSIMPLE_SKIP : 1),
    (empty($_POST['cat_name'])? DBSIMPLE_SKIP : $_POST['cat_name'])
);

В последнем примере применены два интересных приема.

  • Первый {}-блок содержит placeholder, единственным назначением которого является указание, следует пропустить тело блока или нет. Если он равен 1, то выполняется «бесполезное» условие 1 = 1, и блок остается. Если же он равен DBSIMPLE_SKIP, то блок удаляется.
  • Второй {}-блок тоже используется довольно интересно. Он начинается с ключевого слова AND, поэтому мы вынуждены написать перед ним «бесполезное» и всегда истинное выражение 1 = 1, чтобы не получить синтаксическую ошибку. (Кстати, для OR-выражения надо было бы писать 1 = 0.)

Отсюда мораль: не всегда бесполезные на первый взгляд условия действительно не имеют смысла. Иногда их очень удобно использовать совместно с условными блоками.

Транзакции

Транзакции поддерживаются методами Db::i()->transaction(), commit() и rollback(). У каждого соединения в любой момент времени может существовать только одна текущая транзакция.

Запросы с атрибутами

Каждый запрос может быть снабжен одним или несколькими атрибутами, являющимися некоторыми указаниями для DbSimple. Они оформляются в виде SQL-комментариев, идущих перед телом запроса, и имеют формат:

Text
— AttributeName: AttributeValue

Атрибут BLOB_OBJ: объектные BLOB-поля

Если BLOB-ы очень большие, можно работать с ними как с объектами, выполняя read и write "кусками".

Для того, чтобы получить blob-поля в виде объектов, а не в виде строки, используйте синтаксис:

PHP
$row = Db::i()->selectRow('
    — BLOB_OBJ: true
    SELECT * FROM table WHERE id=123
'
);

В результате в $row['blob_field'] окажется не строка, равная содержимому blob-а, а объект DbSimple_*_Blob, у которого есть метод read().

Атрибут CACHE: кэширование запросов

Внимание: данная функциональность находится в разработке и в настоящее время может работать нестабильно. Используйте ее с осторожностью.

Кэширование осуществляется в предположении, что один и тот же запрос, выполняемый через небольшой промежуток времени, вернет одинаковый результат. Для управления этим промежутком используется синтаксис:

PHP
$row = Db::i()->select('
    — CACHE: 10h 20m 30s
    SELECT * FROM table WHERE id=123
'
);

Здесь "10h 20m 30s" - промежуток времени, в течение которого запрос будет браться из кэша. (Если указано только число, то оно трактуется как промежуток времени в секундах.)

Зависимость от источников данных

Вы можете также использовать дополнительные условия для управления инвалидацией кэша. Например, если одна из таблиц, участвующих в запросе, изменилась, следует считать кэш недействительным. Вы можете сообщить об этом DbSimple при помощи следующей конструкции:

PHP
$row = Db::i()->select('
    — CACHE: 10h 20m 30s, forum.modified, topic.modified
    SELECT *
    FROM forum JOIN topic ON topic.forum_id=forum.id
    WHERE id=123
'
);

Здесь предполагается, что в таблице forum имеется столбец с именем modified, хранящий дату последнего изменения записи (аналогично и с таблицей topic). Как только в указанных таблицах появляется новая запись, библиотека это обнаруживает, делая запрос SELECT MAX(forum.modified) FROM forum, и очищает кэш.

Конечно, чтобы инвалидация работала правильно, вы должны перечислить все таблицы, от которых зависит запрос. Кроме того, убедитесь, что с полем modified в БД связан индекс, иначе запрос на получение самой новой записи может работать очень долго.

 

Всего комментариев: 0

Имя*:

Email: