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'ів, які викликаються після кожного виконаного запиту: