Baza danych Rdzeń
Nette Database Core to warstwa bazowa dostępu do bazy danych, tzw. warstwa abstrakcji bazy danych.
Instalacja
Pobierz i zainstaluj bibliotekę za pomocą Composera:
composer require nette/database
Podłączenie i konfiguracja
Aby połączyć się z bazą danych, wystarczy stworzyć instancję klasy Nette\Database\Connection:
$database = new Nette\Database\Connection($dsn, $user, $password);
Parametr $dsn
(nazwa źródła danych) jest taki sam jak ten używany przez PDO, np.
host=127.0.0.1;dbname=test
. W przypadku niepowodzenia rzuca wyjątek
Nette\Database\ConnectionException
.
Bardziej poręczny sposób oferuje jednak konfiguracja aplikacji,
gdzie wystarczy dodać sekcję database
, a ona sama utworzy potrzebne obiekty, a także pasek bazy danych w pasku Tracy.
database:
dsn: 'mysql:host=127.0.0.1;dbname=test'
user: root
password: password
Następnie otrzymujemy obiekt połączenia jako usługę z kontenera DI, na przykład:
class Model
{
// pro práci s vrstvou Database Explorer si předáme Nette\Database\Explorer
public function __construct(
private Nette\Database\Connection $database,
) {
}
}
Więcej informacji na temat konfiguracji bazy danych.
Pytania
Zapytania do bazy danych są zadawane za pomocą metody query()
, która zwraca ResultSet.
$result = $database->query('SELECT * FROM users');
foreach ($result as $row) {
echo $row->id;
echo $row->name;
}
echo $result->getRowCount(); // zwraca liczbę wierszy wyniku, jeśli jest znana
Możesz iterować nad ResultSet
tylko raz, jeśli potrzebujesz iterować więcej niż raz, musisz
przekonwertować wynik na tablicę za pomocą metody fetchAll()
.
Bardzo łatwo jest również dodać parametry do zapytania, zauważ znak zapytania:
$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 je pole
OSTRZEŻENIE, nigdy nie komponuj zapytań jako ciągów znaków, to stworzyłoby lukę w SQL injection
$db->query('SELECT * FROM users WHERE name = ' . $name); // ŠPATNĚ!!!
Jeśli query()
nie powiedzie się, rzuci albo Nette\Database\DriverException
albo jeden z jego
potomków:
- ConstraintViolationException – naruszenie jakiegoś ograniczenia dla tabeli
- ForeignKeyConstraintViolationException – nieprawidłowy klucz obcy
- NotNullConstraintViolationException – naruszenie ograniczenia NOT NULL
- UniqueConstraintViolationException – sprzeczny unikalny indeks
Oprócz strony query()
znajdują się na niej inne przydatne funkcje:
// zwróć tablicę asocjacyjną id => nazwa
$pairs = $database->fetchPairs('SELECT id, name FROM users');
// zwraca wszystkie rekordy jako tablicę
$rows = $database->fetchAll('SELECT * FROM users');
// zwraca pojedynczy rekord
$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id);
// zwraca bezpośrednio wartość komórki
$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id);
W przypadku niepowodzenia, wszystkie te metody rzucą Nette\Database\DriverException
.
Wstawianie, aktualizacja i usuwanie
Parametr, który wstawiamy do zapytania SQL może być również tablicą (w tym przypadku możliwe jest również użycie
placeholdera ?
vynechat), což se hodí třeba pro sestavení příkazu INSERT
:
$database->query('INSERT INTO users ?', [ // możemy tutaj pominąć znak zapytania
'name' => $name,
'year' => $year,
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978)
$id = $database->getInsertId(); // zwróć auto-increment wstawionego rekordu
$id = $database->getInsertId($sequence); // lub wartość sekwencji
Multiple INSERT:
$database->query('INSERT INTO users', [
[
'name' => 'Jim',
'year' => 1978,
], [
'name' => 'Jack',
'year' => 1987,
],
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987)
Jako parametry mogą być przekazywane pliki, obiekty DateTime lub typy wyliczeniowe:
$database->query('INSERT INTO users', [
'name' => $name,
'created' => new DateTime, // nebo $database::literal('NOW()')
'avatar' => fopen('image.gif', 'r'), // vloží soubor
'status' => State::New, // enum State
]);
Edycja zapisów:
$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(); // zwraca liczbę dotkniętych wierszy
Dla UPDATE możemy użyć operatorów +=
i -=
:
$database->query('UPDATE users SET', [
'age+=' => 1, // uwaga +=
], 'WHERE id = ?', $id);
// UPDATE users SET `age` = `age` + 1
Usuwanie:
$result = $database->query('DELETE FROM users WHERE id = ?', $id);
echo $result->getRowCount(); // zwraca liczbę dotkniętych wierszy
Pytania zaawansowane
Wstawianie lub edycja rekordu, jeśli już istnieje:
$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
Zauważ, że Nette Database rozpoznaje kontekst instrukcji SQL, w której wstawiony jest parametr z polem i odpowiednio
buduje kod SQL. Zbudował więc (id, name, year) VALUES (123, 'Jim', 1978)
z pierwszego pola, jednocześnie
przekształcając drugie pole do postaci name = 'Jim', year = 1978
.
Możemy również wpłynąć na sortowanie za pomocą tablicy, w kluczach podamy kolumny, a wartością będzie boolean określający czy sortować w porządku rosnącym:
$database->query('SELECT id FROM author ORDER BY', [
'id' => true, // vzestupně
'name' => false, // sestupně
]);
// SELECT id FROM author ORDER BY `id`, `name` DESC
Jeśli wykrywanie nie działa dla nietypowej konstrukcji, możesz określić formę budowy przez symbol wieloznaczny
?
, po którym następuje podpowiedź. Obsługiwane są następujące wskazówki:
(klucz1, klucz2, …) VALUES (wartość1, wartość2, …) | |
?set | key1 = value1, key2 = value2, … |
?and | key1 = value1 AND key2 = value2 … |
lub | key1 = value1 OR key2 = value2 … |
?order | key1 ASC, key2 DESC |
Klauzula WHERE używa operatora ?and
, więc warunki są połączone operatorem AND
:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
'year' => $year,
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978
Które możemy łatwo zmienić na OR
podając placeholder ?or
:
$result = $database->query('SELECT * FROM users WHERE ?or', [
'name' => $name,
'year' => $year,
]);
// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978
W warunkach możemy używać operatorów:
$result = $database->query('SELECT * FROM users WHERE', [
'name <>' => $name,
'year >' => $year,
]);
// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978
A także wyliczenia:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => ['Jim', 'Jack'],
'role NOT IN' => ['admin', 'owner'], // výčet + operátor NOT IN
]);
// SELECT * FROM users WHERE
// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner')
Możemy również wstawić do warunku fragment własnego kodu SQL, używając tzw. literału SQL:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
'year >' => $database::literal('YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR())
Lub alternatywnie:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('year > YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR())
Literał SQL może mieć również parametry:
$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)
Co pozwala nam na tworzenie ciekawych kombinacji:
$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')
Nazwa zmiennej
Istnieje również placeholder ?name
, którego używasz, jeśli nazwa tabeli lub kolumny jest zmienną. (Uważaj,
aby nie pozwolić użytkownikowi manipulować zawartością takiej zmiennej):
$table = 'blog.users';
$column = 'name';
$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name);
// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim'
Transakcja
Istnieją trzy metody pracy z transakcjami:
$database->beginTransaction(); // rozpoczęcie transakcji
$database->commit(); // commit
$database->rollback(); // rollback
Metoda transaction()
zapewnia elegancki sposób przekazywania wywołania zwrotnego, które jest wykonywane w
transakcji. Jeśli podczas wykonywania zostanie rzucony wyjątek, transakcja jest odrzucana; jeśli wszystko pójdzie dobrze,
transakcja jest popełniana.
$id = $database->transaction(function ($database) {
$database->query('DELETE FROM ...');
$database->query('INSERT INTO ...');
// ...
return $database->getInsertId();
});
Jak widać, metoda transaction()
zwraca wartość zwrotną wywołania zwrotnego.
Callback transaction()
może być również zagnieżdżony, co upraszcza implementację niezależnych
repozytoriów.
Refleksja
Nette Database zapewnia narzędzia do introspekcji struktury bazy danych poprzez klasę Nette\Database\Reflection. Klasa ta umożliwia pobieranie informacji o tabelach, kolumnach, indeksach i kluczach obcych. Refleksji można używać do generowania schematów, tworzenia elastycznych aplikacji współpracujących z bazami danych lub tworzenia ogólnych narzędzi bazodanowych.
Obiekt refleksji można uzyskać z instancji połączenia z bazą danych:
$reflection = $database->getReflection();
Praca z tabelami
Używając refleksji, można iterować po wszystkich tabelach w bazie danych:
// Lista nazw wszystkich tabel
foreach ($reflection->tables as $tableName => $table) {
echo $tableName . "\n";
}
// Sprawdzenie, czy tabela istnieje
if ($reflection->hasTable('users')) {
echo "The 'users' table exists";
}
// Pobieranie określonej tabeli
$table = $reflection->getTable('users');
Informacje o kolumnie
Dla każdej tabeli można uzyskać szczegółowe informacje o jej kolumnach:
// Iteracja po wszystkich kolumnach
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";
}
// Pobieranie określonej kolumny
$idColumn = $table->getColumn('id');
Indeksy i klucze podstawowe
Refleksja dostarcza informacji na temat indeksów i kluczy głównych:
$listColumnNames = fn(array $columns) => implode(', ', array_map(fn($col) => $col->name, $columns));
// Lista wszystkich indeksów
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";
}
// Pobieranie klucza głównego
if ($table->primaryKey) {
echo "Primary key: " . $listColumnNames($table->primaryKey->columns) . "\n";
}
Klucze obce
Można również uzyskać informacje na temat kluczy obcych:
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";
}