Baza de date de bază
Nette Database Core este un strat de abstractizare a bazei de date și oferă funcționalitatea de bază.
Instalare
Descărcați și instalați pachetul folosind Composer:
composer require nette/database
Conectare și configurare
Pentru a vă conecta la baza de date, creați pur și simplu o instanță a clasei Nette\Database\Connection:
$database = new Nette\Database\Connection($dsn, $user, $password);
Parametrul $dsn
(numele sursei de date) este același cu cel utilizat de PDO, de
exemplu host=127.0.0.1;dbname=test
. În caz de eșec, se aruncă Nette\Database\ConnectionException
.
Cu toate acestea, o modalitate mai sofisticată oferă configurarea
aplicației. Vom adăuga o secțiune database
și aceasta creează obiectele necesare și un panou cu baza de
date în bara Tracy.
database:
dsn: 'mysql:host=127.0.0.1;dbname=test'
user: root
password: password
Obiectul de conexiune pe care îl primim ca serviciu de la un container DI, de exemplu:
class Model
{
// treceți Nette\Database\Explorer pentru a lucra cu stratul Database Explorer
public function __construct(
private Nette\Database\Connection $database,
) {
}
}
Pentru mai multe informații, consultați configurarea bazei de date.
Interogări
Pentru a interoga baza de date, utilizați metoda query()
care returnează ResultSet.
$result = $database->query('SELECT * FROM users');
foreach ($result as $row) {
echo $row->id;
echo $row->name;
}
echo $result->getRowCount(); // returnează numărul de rânduri dacă este cunoscut
Pe ResultSet
este posibilă iterarea doar o singură dată, dacă trebuie să iterăm de mai multe
ori, este necesar să convertim rezultatul în matrice prin metoda fetchAll()
.
Puteți adăuga cu ușurință parametri la interogare, rețineți semnul de întrebare:
$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 este o matrice
ATENȚIE, nu concatenați niciodată șiruri de caractere pentru a evita vulnerabilitatea de injecție SQL!
$db->query('SELECT * FROM users WHERE name = ' . $name); // WRONG!!!
În caz de eșec, query()
aruncă fie Nette\Database\DriverException
, fie unul dintre
descendenții săi:
- ConstraintViolationException – încălcarea oricărei constrângeri.
- ForeignKeyConstraintViolationException – cheie străină invalidă
- NotNullConstraintViolationException – încălcare a condiției NOT NULL
- UniqueConstraintViolationException – conflict cu un indice unic
În plus față de query()
, există și alte metode utile:
// returnează matricea asociativă id => nume
$pairs = $database->fetchPairs('SELECT id, name FROM users');
// returnează toate rândurile sub formă de matrice
$rows = $database->fetchAll('SELECT * FROM users');
// returnează un singur rând
$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id);
// returnează un singur câmp
$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id);
În caz de eșec, toate aceste metode aruncă Nette\Database\DriverException.
Inserare, actualizare și ștergere
Parametrul pe care îl introducem în interogarea SQL poate fi, de asemenea, matricea (caz în care este posibil să se omită
declarația wildcard ?
), which may be useful for the INSERT
:
$database->query('INSERT INTO users ?', [ // aici poate fi omis semnul întrebării
'name' => $name,
'year' => $year,
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978)
$id = $database->getInsertId(); // returnează creșterea automată a rândului inserat
$id = $database->getInsertId($sequence); // sau valoarea secvenței
Inserare multiplă:
$database->query('INSERT INTO users', [
[
'name' => 'Jim',
'year' => 1978,
], [
'name' => 'Jack',
'year' => 1987,
],
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987)
De asemenea, putem trece fișiere, obiecte DateTime sau enumerări:
$database->query('INSERT INTO users', [
'name' => $name,
'created' => new DateTime, // sau $database::literal('NOW()')
'avatar' => fopen('image.gif', 'r'), // inserează conținutul fișierului
'status' => State::New, // enum State
]);
Actualizarea rândurilor:
$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(); // returnează numărul de rânduri afectate
Pentru UPDATE, putem folosi operatorii +=
și -=
:
$database->query('UPDATE users SET', [
'age+=' => 1, // notă +=
], 'WHERE id = ?', $id);
// UPDATE users SET `age` = `age` + 1
Ștergere:
$result = $database->query('DELETE FROM users WHERE id = ?', $id);
echo $result->getRowCount(); // returnează numărul de rânduri afectate
Interogări avansate
Inserare sau actualizare, dacă există deja:
$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
Rețineți că Nette Database recunoaște contextul SQL în care este introdus parametrul de matrice și construiește codul
SQL în consecință. Astfel, din primul array generează (id, name, year) VALUES (123, 'Jim', 1978)
, în timp ce al
doilea se convertește în name = 'Jim', year = 1978
.
De asemenea, putem descrie sortarea folosind array, în care cheile sunt nume de coloane, iar valorile sunt booleane care determină dacă se sortează în ordine crescătoare:
$database->query('SELECT id FROM author ORDER BY', [
'id' => true, // ascendent
'name' => false, // descrescător
]);
// SELECT id FROM author ORDER BY `id`, `name` DESC
În cazul în care detectarea nu a funcționat, puteți specifica forma ansamblului cu ajutorul unui wildcard ?
urmat de un indiciu. Aceste indicii sunt acceptate:
?values | (key1, key2, …) VALUES (value1, value2, …) |
?set | key1 = valoare1, key2 = valoare2, … |
?and | key1 = valoare1 AND key2 = valoare2 … |
?or | key1 = valoare1 OR key2 = valoare2 … |
?order | key1 ASC, key2 DESC |
Clauza WHERE utilizează operatorul ?and
, astfel încât condițiile sunt legate prin AND
:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
'year' => $year,
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978
Care poate fi ușor de schimbat în OR
prin utilizarea caracterului wildcard ?or
:
$result = $database->query('SELECT * FROM users WHERE ?or', [
'name' => $name,
'year' => $year,
]);
// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978
Putem folosi operatori în condiții:
$result = $database->query('SELECT * FROM users WHERE', [
'name <>' => $name,
'year >' => $year,
]);
// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978
Și, de asemenea, enumerări:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => ['Jim', 'Jack'],
'role NOT IN' => ['admin', 'owner'], // enumerare + operator NOT IN
]);
// SELECT * FROM users WHERE
// `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner')
De asemenea, putem include o bucată de cod SQL personalizat folosind așa-numitul literal SQL:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
'year >' => $database::literal('YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR())
Alternativ:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('year > YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR())
Literalul SQL poate avea, de asemenea, parametrii săi:
$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)
Datorită cărora putem crea combinații interesante:
$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')
Denumire variabilă
Există un wildcard ?name
pe care îl utilizați dacă numele tabelului sau al coloanei este o variabilă.
(Atenție, nu permiteți utilizatorului să manipuleze conținutul unei astfel de variabile):
$table = 'blog.users';
$column = 'name';
$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name);
// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim'
Tranzacții
Există trei metode de tratare a tranzacțiilor:
$database->beginTransaction();
$database->commit();
$database->rollback();
O modalitate elegantă este oferită de metoda transaction()
. Se trece callback-ul care este executat în cadrul
tranzacției. Dacă în timpul execuției se aruncă o excepție, tranzacția este abandonată, iar dacă totul merge bine,
tranzacția este validată.
$id = $database->transaction(function ($database) {
$database->query('DELETE FROM ...');
$database->query('INSERT INTO ...');
// ...
return $database->getInsertId();
});
După cum puteți vedea, metoda transaction()
returnează valoarea de returnare a callback-ului.
Tranzacția() poate fi, de asemenea, imbricata, ceea ce simplifică implementarea de depozite independente.
Reflecție
Nette Database oferă instrumente pentru introspecția structurii bazei de date prin clasa Nette\Database\Reflection. Această clasă vă permite să obțineți informații despre tabele, coloane, indexuri și chei străine. Puteți utiliza reflecția pentru a genera scheme, pentru a crea aplicații flexibile care lucrează cu baze de date sau pentru a construi instrumente generale pentru baze de date.
Puteți obține un obiect reflection dintr-o instanță de conexiune la o bază de date:
$reflection = $database->getReflection();
Lucrul cu tabelele
Folosind reflexia, puteți itera peste toate tabelele din baza de date:
// Lista numele tuturor tabelelor
foreach ($reflection->tables as $tableName => $table) {
echo $tableName . "\n";
}
// Verificați dacă un tabel există
if ($reflection->hasTable('users')) {
echo "The 'users' table exists";
}
// Preluarea unui anumit tabel
$table = $reflection->getTable('users');
Informații despre coloană
Pentru fiecare tabel, puteți obține informații detaliate despre coloanele sale:
// Iterați peste toate coloanele
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";
}
// Preluarea unei anumite coloane
$idColumn = $table->getColumn('id');
Indexuri și chei primare
Reflecția oferă informații despre indici și chei primare:
$listColumnNames = fn(array $columns) => implode(', ', array_map(fn($col) => $col->name, $columns));
// Lista tuturor indicilor
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";
}
// Recuperarea cheii primare
if ($table->primaryKey) {
echo "Primary key: " . $listColumnNames($table->primaryKey->columns) . "\n";
}
Chei străine
De asemenea, puteți obține informații despre cheile străine:
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";
}