Database Core

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, // ascending
	'name' => false, // descending
]);
// 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 OR 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() возвращает возвращаемое значение обратного вызова.

Транзакция() также может быть вложенной, что упрощает реализацию независимых хранилищ.