Ядро бази даних
Nette Database Core є рівнем абстракції бази даних і забезпечує основну функціональність.
Встановлення
Завантажте та встановіть пакет за допомогою Composer:
composer require nette/database
Підключення та налаштування
Щоб підключитися до бази даних, просто створіть екземпляр класу Nette\Database\Connection:
$database = new Nette\Database\Connection($dsn, $user, $password);
Параметр $dsn
(ім'я джерела даних) – такий самий, як
використовується в PDO, наприклад host=127.0.0.1;dbname=test
. У разі
невдачі викидається виняток Nette\Database\ConnectionException
.
Однак, більш складний спосіб пропонує конфігурація програми. Ми додамо
розділ database
, і він створить необхідні об'єкти та панель
Database
в панелі налагодження Tracy.
database:
dsn: 'mysql:host=127.0.0.1;dbname=test'
user: root
password: password
Об'єкт з'єднання, який ми отримуємо як сервіс від DI-контейнера, наприклад:
class Model
{
// передаємо Nette\Database\Explorer для роботи з рівнем Database Explorer
public function __construct(
private Nette\Database\Connection $database,
) {
}
}
Для отримання додаткової інформації дивіться конфігурацію бази даних.
Запити
Для запиту до бази даних використовуйте метод query()
, який
повертає ResultSet.
$result = $database->query('SELECT * FROM users');
foreach ($result as $row) {
echo $row->id;
echo $row->name;
}
echo $result->getRowCount(); // повертає кількість рядків, якщо вона відома
Над ResultSet
можна виконати ітерацію тільки один раз, якщо
нам потрібно виконати ітерацію кілька разів, необхідно перетворити
результат у масив за допомогою методу fetchAll()
.
Ви можете легко додати параметри в запит, зверніть увагу на знак питання:
$database->query('SELECT * FROM users WHERE name = ?', $name);
$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active);
$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids - масив
УВАГА, ніколи не об'єднуйте рядки, щоб уникнути уразливості через SQL-ін'єкції!
$db->query('SELECT * FROM users WHERE name = ' . $name); // НЕПРАВИЛЬНО!!!
У разі невдачі query()
викидає або виняток
Nette\Database\DriverException
, або одне з його дочірніх винятків:
- ConstraintViolationException – порушення будь-якої з умов
- ForeignKeyConstraintViolationException – неприпустимий зовнішній ключ
- NotNullConstraintViolationException – порушення умови NOT NULL
- UniqueConstraintViolationException – конфлікт унікального індексу
Крім query()
, існують і інші корисні методи:
// повертає асоціативний масив id => name
$pairs = $database->fetchPairs('SELECT id, name FROM users');
// повертає всі рядки у вигляді масиву
$rows = $database->fetchAll('SELECT * FROM users');
// повертає один рядок
$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id);
// повертає одне поле
$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id);
У разі невдачі всі ці методи викидають виняток
Nette\Database\DriverException
.
Вставка, оновлення та видалення
Параметр, який ми вставляємо в SQL-запит, також може бути масивом (у
цьому випадку можна пропустити знак підстановки ?
), что может
быть полезно для оператора INSERT
:
$database->query('INSERT INTO users ?', [ // тут може бути опущений знак питання
'name' => $name,
'year' => $year,
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978)
$id = $database->getInsertId(); // повертає автоінкремент вставленого рядка
$id = $database->getInsertId($sequence); // або значення послідовності
Вставка декількох значень:
$database->query('INSERT INTO users', [
[
'name' => 'Jim',
'year' => 1978,
], [
'name' => 'Jack',
'year' => 1987,
],
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987)
Ми також можемо передавати файли, об'єкти DateTime або перерахування:
$database->query('INSERT INTO users', [
'name' => $name,
'created' => new DateTime, // або $database::literal('NOW()')
'avatar' => fopen('image.gif', 'r'), // вставляє вміст файлу
'status' => State::New, // enum State
]);
Оновлення рядків:
$result = $database->query('UPDATE users SET', [
'name' => $name,
'year' => $year,
], 'WHERE id = ?', $id);
// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123
echo $result->getRowCount(); // повертає кількість порушених рядків
Для UPDATE ми можемо використовувати оператори +=
і -=
:
$database->query('UPDATE users SET', [
'age+=' => 1, // note +=
], 'WHERE id = ?', $id);
// UPDATE users SET `age` = `age` + 1
Видалення:
$result = $database->query('DELETE FROM users WHERE id = ?', $id);
echo $result->getRowCount(); // повертає кількість порушених рядків
Просунуті запити
Вставка або оновлення, якщо запис уже існує:
$database->query('INSERT INTO users', [
'id' => $id,
'name' => $name,
'year' => $year,
], 'ON DUPLICATE KEY UPDATE', [
'name' => $name,
'year' => $year,
]);
// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978)
// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978
Зверніть увагу, що Nette Database розпізнає SQL-контекст, у який вставлено
параметр масиву, і будує SQL-код відповідним чином. Так, з першого масиву
він генерує (id, name, year) VALUES (123, 'Jim', 1978)
, а другий перетворює на
name = 'Jim', year = 1978
.
Ми також можемо описати сортування за допомогою масиву, в якому ключами є імена стовпців, а значеннями – значення типу boolean, що визначають, чи слід сортувати в порядку зростання:
$database->query('SELECT id FROM author ORDER BY', [
'id' => true, // за зростанням
'name' => false, // за спаданням
]);
// SELECT id FROM author ORDER BY `id`, `name` DESC
Якщо виявлення не спрацювало, ви можете вказати форму збірки за
допомогою знака підстановки ?
, за яким слідує підказка.
Підтримуються такі підказки:
?values | (key1, key2, …) VALUES (value1, value2, …) |
?set | key1 = value1, key2 = value2, … |
?and | key1 = value1 AND key2 = value2 … |
?or | key1 = value1 АБО key2 = value2 … |
?order | key1 ASC, key2 DESC |
У реченні WHERE використовується оператор ?and
, тому умови
пов'язані AND
:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
'year' => $year,
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978
Який можна легко змінити на OR
, використовуючи знак
підстановки ?or
:
$result = $database->query('SELECT * FROM users WHERE ?or', [
'name' => $name,
'year' => $year,
]);
// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978
Ми можемо використовувати оператори в умовах:
$result = $database->query('SELECT * FROM users WHERE', [
'name <>' => $name,
'year >' => $year,
]);
// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978
А також перерахування:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => ['Jim', 'Jack'],
'role NOT IN' => ['admin', 'owner'], // перерахування + оператор NOT IN
]);
// SELECT * FROM users WHERE
// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner')
Ми також можемо включити частину користувацького SQL-коду, використовуючи так званий SQL-літерал:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
'year >' => $database::literal('YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR())
Як альтернативу:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('year > YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR())
SQL-літерал також може мати свої параметри:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('year > ? AND year < ?', $min, $max),
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017)
Завдяки цьому ми можемо створювати цікаві комбінації:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('?or', [
'active' => true,
'role' => $role,
]),
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin')
Ім'я змінної
Існує знак підстановки `?name', який використовується, якщо ім'я таблиці або стовпця є змінною. (Обережно, не дозволяйте користувачеві маніпулювати вмістом такої змінної):
$table = 'blog.users';
$column = 'name';
$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name);
// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim'
Транзакції
Існує три методи роботи з транзакціями:
$database->beginTransaction();
$database->commit();
$database->rollback();
Елегантний спосіб пропонує метод transaction()
. Ви передаєте
зворотний виклик, який виконується в транзакції. Якщо під час
виконання виникає виняток, транзакція скидається, якщо все йде добре,
транзакція фіксується.
$id = $database->transaction(function ($database) {
$database->query('DELETE FROM ...');
$database->query('INSERT INTO ...');
// ...
return $database->getInsertId();
});
Як бачите, метод transaction()
повертає значення зворотного виклику,
що повертається.
Транзакція() також може бути вкладеною, що спрощує реалізацію незалежних сховищ.