SQL-подход
Nette Database предлагает два пути: вы можете писать SQL-запросы сами (SQL-подход) или позволить генерировать их автоматически (см. Explorer). SQL-подход дает вам полный контроль над запросами и при этом обеспечивает их безопасное построение.
Подробности о подключении и конфигурации базы данных можно найти в главе Подключение и конфигурация.
Базовые запросы
Для выполнения запросов к базе данных используется метод query()
.
Он возвращает объект ResultSet,
который представляет результат запроса. В случае сбоя метод выбрасывает исключение. Результат запроса можно
перебирать с помощью цикла foreach
или использовать одну из вспомогательных функций.
Для безопасной вставки значений в SQL-запросы мы используем параметризованные запросы. Nette Database делает их максимально простыми — достаточно добавить запятую и значение после SQL-запроса:
При наличии нескольких параметров у вас есть два варианта записи. Вы можете либо «перемежать» SQL-запрос параметрами:
Либо сначала написать весь SQL-запрос, а затем добавить все параметры:
Защита от SQL Injection
Почему важно использовать параметризованные запросы? Потому что они защищают вас от атаки под названием SQL Injection, при которой злоумышленник мог бы подставить собственные SQL-команды и тем самым получить или повредить данные в базе данных.
Никогда не вставляйте переменные непосредственно в SQL-запрос! Всегда используйте параметризованные запросы, которые защитят вас от SQL Injection.
Ознакомьтесь с возможными рисками безопасности.
Техники запросов
Условия WHERE
Условия WHERE можно записать как ассоциативный массив, где ключи — это имена столбцов, а значения — данные для сравнения. Nette Database автоматически выберет наиболее подходящий SQL-оператор в зависимости от типа значения.
В ключе также можно явно указать оператор для сравнения:
Nette автоматически обрабатывает особые случаи, такие как значения
null
или массивы.
Для отрицательных условий используйте оператор NOT
:
Для объединения условий используется оператор AND
. Это можно
изменить с помощью плейсхолдера ?or.
Правила ORDER BY
Сортировку ORDER BY
можно записать с помощью массива. В ключах
указываем столбцы, а значением будет boolean, определяющий, сортировать ли
по возрастанию:
Вставка данных (INSERT)
Для вставки записей используется SQL-команда INSERT
.
Метод getInsertId()
возвращает ID последней вставленной строки. В
некоторых базах данных (например, PostgreSQL) необходимо в качестве
параметра указать имя последовательности, из которой должен
генерироваться ID, с помощью $database->getInsertId($sequenceId)
.
В качестве параметров можно передавать и специальные значения такие как файлы, объекты DateTime или перечисляемые типы.
Вставка нескольких записей одновременно:
Множественная вставка INSERT намного быстрее, так как выполняется один запрос к базе данных вместо множества отдельных.
Предупреждение о безопасности: Никогда не используйте в качестве
$values
невалидированные данные. Ознакомьтесь с возможными рисками.
Обновление данных (UPDATE)
Для обновления записей используется SQL-команда UPDATE
.
Количество затронутых строк возвращает $result->getRowCount()
.
Для UPDATE можно использовать операторы +=
и -=
:
Пример вставки или обновления записи, если она уже существует.
Используем технику ON DUPLICATE KEY UPDATE
:
Обратите внимание, что Nette Database распознает, в каком контексте
SQL-команды вставляется параметр с массивом, и в соответствии с этим
составляет из него SQL-код. Так, из первого массива он составил
(id, name, year) VALUES (123, 'Jim', 1978)
, в то время как второй преобразовал в вид
name = 'Jim', year = 1978
. Подробнее об этом мы поговорим в разделе Подсказки для построения SQL.
Удаление данных (DELETE)
Для удаления записей используется SQL-команда DELETE
. Пример с
получением количества удаленных строк:
Подсказки для построения SQL
Подсказка — это специальный плейсхолдер в SQL-запросе, который указывает, как значение параметра должно быть преобразовано в SQL-выражение:
Подсказка | Описание | Используется автоматически |
---|---|---|
?name |
используется для вставки имени таблицы или столбца | – |
?values |
генерирует (key, ...) VALUES (value, ...) |
INSERT ... ? , REPLACE ... ? |
?set |
генерирует присваивание key = value, ... |
SET ? , KEY UPDATE ? |
?and |
объединяет условия в массиве оператором AND |
WHERE ? , HAVING ? |
?or |
объединяет условия в массиве оператором OR |
– |
?order |
генерирует условие ORDER BY |
ORDER BY ? , GROUP BY ? |
Для динамической вставки имен таблиц и столбцов в запрос
используется плейсхолдер ?name
. Nette Database позаботится о правильной
обработке идентификаторов в соответствии с конвенциями данной базы
данных (например, заключение в обратные кавычки в MySQL).
Предупреждение: символ ?name
используйте только для имен
таблиц и столбцов из валидированных входных данных, иначе вы
подвергаетесь риску безопасности.
Остальные подсказки обычно указывать не нужно, так как Nette использует
умное автоопределение при составлении SQL-запроса (см. третий столбец
таблицы). Но вы можете использовать его, например, в ситуации, когда
хотите объединить условия с помощью OR
вместо AND
:
Специальные значения
Кроме обычных скалярных типов (string, int, bool), в качестве параметров можно передавать и специальные значения:
- файлы:
fopen('image.gif', 'r')
вставляет бинарное содержимое файла - дата и время: объекты
DateTime
преобразуются в формат базы данных - перечисляемые типы: экземпляры
enum
преобразуются в их значение - SQL-литералы: созданные с помощью
Connection::literal('NOW()')
вставляются непосредственно в запрос
В базах данных, которые не имеют нативной поддержки типа данных
datetime
(например, SQLite и Oracle), DateTime
преобразуется в значение,
указанное в конфигурации базы данных в элементе
formatDateTime
(значение по умолчанию — U
– unix timestamp).
SQL-литералы
В некоторых случаях необходимо указать в качестве значения
непосредственно SQL-код, который, однако, не должен восприниматься как
строка и экранироваться. Для этого служат объекты класса
Nette\Database\SqlLiteral
. Их создает метод Connection::literal()
.
Или альтернативно:
SQL-литералы могут содержать параметры:
Благодаря чему мы можем создавать интересные комбинации:
Получение данных
Сокращения для запросов SELECT
Для упрощения извлечения данных Connection
предлагает несколько
сокращений, которые объединяют вызов query()
с последующим
fetch*()
. Эти методы принимают те же параметры, что и query()
, то
есть SQL-запрос и необязательные параметры. Полное описание методов
fetch*()
вы найдете ниже.
fetch($sql, ...$params): ?Row |
Выполняет запрос и возвращает первую строку как объект Row |
fetchAll($sql, ...$params): array |
Выполняет запрос и возвращает все строки как массив
объектов Row |
fetchPairs($sql, ...$params): array |
Выполняет запрос и возвращает ассоциативный массив, где первый столбец представляет ключ, а второй — значение |
fetchField($sql, ...$params): mixed |
Выполняет запрос и возвращает значение первого поля из первой строки |
fetchList($sql, ...$params): ?array |
Выполняет запрос и возвращает первую строку как индексированный массив |
Пример:
foreach
– итерация по строкам
После выполнения запроса возвращается объект ResultSet, который позволяет
перебирать результаты несколькими способами. Самый простой способ
выполнить запрос и получить строки — это итерация в цикле foreach
.
Этот способ наиболее экономичен по памяти, так как возвращает данные
постепенно и не сохраняет их все сразу в памяти.
ResultSet
можно итерировать только один раз. Если вам нужно
итерировать повторно, сначала необходимо загрузить данные в массив,
например, с помощью метода fetchAll()
.
fetch(): ?Row
Возвращает строку как объект Row
. Если больше нет строк,
возвращает null
. Перемещает внутренний указатель на следующую
строку.
fetchAll(): array
Возвращает все оставшиеся строки из ResultSet
как массив объектов
Row
.
fetchPairs(string|int|null $key = null, string|int|null $value = null): array
Возвращает результаты как ассоциативный массив. Первый аргумент указывает имя столбца, который будет использоваться в качестве ключа в массиве, второй аргумент указывает имя столбца, который будет использоваться в качестве значения:
Если указать только первый параметр, значением будет вся строка, то
есть объект Row
:
В случае дублирующихся ключей используется значение из последней
строки. При использовании null
в качестве ключа массив будет
индексирован численно с нуля (тогда коллизий не происходит):
fetchPairs(Closure $callback): array
Альтернативно, в качестве параметра можно указать callback, который будет для каждой строки возвращать либо само значение, либо пару ключ-значение.
fetchField(): mixed
Возвращает значение первого поля из текущей строки. Если больше нет
строк, возвращает null
. Перемещает внутренний указатель на
следующую строку.
fetchList(): ?array
Возвращает строку как индексированный массив. Если больше нет строк,
возвращает null
. Перемещает внутренний указатель на следующую
строку.
getRowCount(): ?int
Возвращает количество затронутых строк последним запросом
UPDATE
или DELETE
. Для SELECT
это количество возвращенных
строк, но оно может быть неизвестно — в таком случае метод вернет
null
.
getColumnCount(): ?int
Возвращает количество столбцов в ResultSet
.
Информация о запросах
Для отладочных целей можно получить информацию о последнем выполненном запросе:
Для отображения результата в виде HTML-таблицы можно использовать:
ResultSet предлагает информацию о типах столбцов:
Логирование запросов
Мы можем реализовать собственное логирование запросов. Событие
onQuery
— это массив callback-функций, которые вызываются после
каждого выполненного запроса: