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

Ядрото на базата данни на Nette е слой за абстракция на базата данни и осигурява основни функции.

Инсталация

Изтеглете и инсталирайте пакета с помощта на 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($последователност); // или стойност на последователността

Вмъкване на няколко стойности:

$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.

Можем също така да опишем сортирането с помощта на масив, в който ключовете са имена на колони, а стойностите са булеви стойности, които определят дали да се сортират във възходящ ред:

$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 ключ1 = стойност1 ИЛИ ключ2 = стойност2 …
?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() връща стойността на обратната връзка.

Функцията 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');

Индекси и първични ключове

Отражението предоставя информация за индексите и първичните ключове:

$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";
}
версия: 4.0