Ядро бази даних

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, або одне з його дочірніх винятків:

Крім 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() повертає значення зворотного виклику, що повертається.

Транзакція() також може бути вкладеною, що спрощує реалізацію незалежних сховищ.

версію: 4.0