Ядро бази даних
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()
повертає значення зворотного виклику,
що повертається.
Транзакція() також може бути вкладеною, що спрощує реалізацію незалежних сховищ.
Рефлексія
Nette Database надає інструменти для самоаналізу структури бази даних за допомогою класу Nette\Database\Reflection. Цей клас дозволяє отримувати інформацію про таблиці, стовпці, індекси та зовнішні ключі. Ви можете використовувати рефлексію для генерації схем, створення гнучких додатків, які працюють з базами даних, або створення загальних інструментів для роботи з базами даних.
Ви можете отримати об'єкт рефлексії з екземпляра підключення до бази даних:
$reflection = $database->getReflection();
Робота з таблицями
Використовуючи рефлексію, ви можете ітераційно переглядати всі таблиці в базі даних:
// Перерахувати назви всіх таблиць
foreach ($reflection->tables as $tableName => $table) {
echo $tableName . "\n";
}
// Перевірити, чи існує таблиця
if ($reflection->hasTable('users')) {
echo "The 'users' table exists";
}
// Отримати конкретну таблицю
$table = $reflection->getTable('users');
Інформація про колонку
Для кожної таблиці ви можете отримати детальну інформацію про її стовпці:
// Ітерація по всіх стовпцях
foreach ($table->columns as $column) {
echo "Column: " . $column->name . "\n";
echo "Type: " . $column->nativeType . "\n";
echo "Nullable: " . ($column->nullable ? 'Yes': 'No') . "\n";
echo "Default value: " . ($column->default ?? 'None') . "\n";
echo "Primary key: " . ($column->primary ? 'Yes': 'No') . "\n";
echo "Auto-increment: " . ($column->autoIncrement ? 'Yes': 'No') . "\n";
}
// Отримати певний стовпець
$idColumn = $table->getColumn('id');
Індекси та первинні ключі
Reflection надає інформацію про індекси та первинні ключі:
$listColumnNames = fn(array $columns) => implode(', ', array_map(fn($col) => $col->name, $columns));
// Перерахувати всі індекси
foreach ($table->indexes as $index) {
echo "Index: " . ($index->name ?? 'Unnamed') . "\n";
echo "Columns: " . $listColumnNames($index->columns) . "\n";
echo "Unique: " . ($index->unique ? 'Yes': 'No') . "\n";
echo "Primary key: " . ($index->primary ? 'Yes': 'No') . "\n";
}
// Отримати первинний ключ
if ($table->primaryKey) {
echo "Primary key: " . $listColumnNames($table->primaryKey->columns) . "\n";
}
Зовнішні ключі
Ви також можете отримати інформацію про зовнішні ключі:
foreach ($table->foreignKeys as $fk) {
echo "Foreign key: " . ($fk->name ?? 'Unnamed') . "\n";
echo "Local columns: " . $listColumnNames($fk->localColumns) . "\n";
echo "References table: {$fk->foreignTable->name}\n";
echo "References columns: " . $listColumnNames($fk->foreignColumns) . "\n";
}