diff --git a/database/cs/@home.texy b/database/cs/@home.texy index 20cc87170b..57efa0ed50 100644 --- a/database/cs/@home.texy +++ b/database/cs/@home.texy @@ -1,20 +1,176 @@ +.[perex] +Nette Database je výkonná a elegantní databázová vrstva pro PHP, která vyniká svou jednoduchostí použití a chytrými funkcemi. Nevyžaduje žádnou složitou konfiguraci nebo generování entit, s Nette Database můžete začít pracovat okamžitě. +S Nette Database můžete pracovat dvěma způsoby - buď psaním SQL dotazů (Direct přístup), nebo nechat SQL generovat automaticky (Explorer přístup). -Podporované databáze -==================== +
+
+ + +Direct SQL +========== +- Bezpečné parametrizované dotazy +- Přesná kontrola nad podobou SQL dotazů +- Když píšete komplexní dotazy s pokročilými funkcemi +- Optimalizujete výkon pomocí specifických SQL funkcí + +
SEZNAMTE SE
+
+ +
+ + +Explorer +======== +- Vyvíjíte rychle bez psaní SQL +- Intuitivní práce s relacemi mezi tabulkami +- Oceníte automatickou optimalizaci dotazů +- Vhodné pro rychlou a pohodlnout práci s databází + +
SEZNAMTE SE
+
+ +
+ +--- + +
+
+ + +Automatická optimalizace výkonu +------------------------------- +- Inteligentní načítání souvisejících dat +- Adaptivní načítání pouze potřebných sloupců +- Minimalizace počtu databázových dotazů + +
+ +
+ + +Bezpečnost na prvním místě +-------------------------- +- Vestavěná ochrana proti SQL injection +- Parametrizované dotazy +- Bezpečné zpracování vstupních dat + +
+ +
+ + +Intuitivní práce s relacemi +--------------------------- +- Přirozený přístup k propojeným datům +- Podpora všech typů vazeb (1:1, 1:N, M:N) +- Bez nutnosti psát JOIN dotazy + +
+ +
+ + +Pohodlné debuggování +-------------------- +- Panel do [Tracy|tracy:] +- Všechny provedené dotazy s časy +- Vysvětlení dotazů (EXPLAIN) + +
+ +
-Nette podporuje následující databáze: -|* Databázový server |* DSN jméno |* Podpora v Core |* Podpora v Explorer -| MySQL (>= 5.1) | mysql | ANO | ANO -| PostgreSQL (>= 9.0) | pgsql | ANO | ANO -| Sqlite 3 (>= 3.8) | sqlite | ANO | ANO -| Oracle | oci | ANO | - -| MS SQL (PDO_SQLSRV) | sqlsrv | ANO | ANO -| MS SQL (PDO_DBLIB) | mssql | ANO | - -| ODBC | odbc | ANO | - +Nejjednodušší parametrické dotazy +--------------------------------- + +Stačí jen čárka a hodnota: + +
+```php .[dark] +$database->query(' + SELECT * + FROM users + WHERE name =', $name +); +``` +
+ +Žádné `?`, `:param`, `@param` nebo jiné speciální syntaxe - prostě jen otazník. + +
+ +
+ + +Chytrá detekce vazeb +-------------------- +Nepotřebujete konfigurovat entity ani mapování: + +
+```php .[dark] +$book = $explorer->table('book')->get(1); +// automaticky nalezne vazbu přes book.author_id +echo $book->author->name; +``` +
+ +
+ +
+ + +Adaptivní načítání dat +---------------------- + +Automaticky načítá jen sloupce, které skutečně používáte v kódu + +
+```php .[dark] +foreach ($books as $book) { + // načte z databáze jen sloupec 'title' + echo $book->title; +} +``` +
+ +
+ +
+ + +Přes 18 let vývoje +================== +Nette vyvíjíme přes 18 let - a číslo stále roste! Knihovny, které poskytujeme, jsou proto **velmi zralé, stabilní a široce používané**. Věří jim řada globálních korporací a pohání mnoho významných webových stránek. Kdo používá a důvěřuje Nette? + +
+
+ + +Instalace +========= + +Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|best-practices:composer]: + +```shell .[dark] +composer require nette/database +``` + + +Podporované databáze +-------------------- +Nette Database podporuje následující databáze: +|* Databázový server |* DSN jméno |* Podpora v Explorer +| MySQL (>= 5.1) | mysql | ANO +| PostgreSQL (>= 9.0) | pgsql | ANO +| Sqlite 3 (>= 3.8) | sqlite | ANO +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | ANO +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - {{title: Nette Database}} diff --git a/database/cs/@left-menu.texy b/database/cs/@left-menu.texy index 8353639528..ddeadbab96 100644 --- a/database/cs/@left-menu.texy +++ b/database/cs/@left-menu.texy @@ -1,8 +1,12 @@ Databáze ******** -- [Core] +- [Úvod |guide] +- [Direct SQL] - [Explorer] +- [Transakce |transactions] +- [Výjimky |exceptions] - [Reflexe |reflection] +- [Mapování |mapper] - [Konfigurace |configuration] - [Bezpečnostní rizika |security] - [Upgrade |upgrading] diff --git a/database/cs/core.texy b/database/cs/core.texy index 02c3104e43..096e5311b0 100644 --- a/database/cs/core.texy +++ b/database/cs/core.texy @@ -1,350 +1 @@ -Database Core -************* - -.[perex] -Nette Database Core je základní vrstva pro přístup k databázi, tzv. database abstraction layer. - - -Instalace -========= - -Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|best-practices:composer]: - -```shell -composer require nette/database -``` - - -Připojení a konfigurace -======================= - -Pro připojení k databázi stačí vytvořit instanci třídy [api:Nette\Database\Connection]: - -```php -$database = new Nette\Database\Connection($dsn, $user, $password); -``` - -Parametr `$dsn` (data source name) je stejný, [jaký používá PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], např. `host=127.0.0.1;dbname=test`. V případě selhání vyhodí výjimku `Nette\Database\ConnectionException`. - -Nicméně šikovnější způsob nabízí [aplikační konfigurace |configuration], kam stačí přidat sekci `database` a vytvoří se potřebné objekty a také databázový panel v [Tracy |tracy:] baru. - -```neon -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: root - password: password -``` - -Poté objekt spojení [získáme jako službu z DI kontejneru |dependency-injection:passing-dependencies], např.: - -```php -class Model -{ - // pro práci s vrstvou Database Explorer si předáme Nette\Database\Explorer - public function __construct( - private Nette\Database\Connection $database, - ) { - } -} -``` - -Více informací o [konfiguraci databáze|configuration]. - - -Dotazy -====== - -Databázové dotazy pokládáme metodou `query()`, která vrací [ResultSet |api:Nette\Database\ResultSet]. - -```php -$result = $database->query('SELECT * FROM users'); - -foreach ($result as $row) { - echo $row->id; - echo $row->name; -} - -echo $result->getRowCount(); // vrací počet řádků výsledku, pokud je znám -``` - -.[note] -Nad `ResultSet` je možné iterovat pouze jednou, pokud potřebujeme iterovat vícekrát, je nutno výsledek převést na pole metodou `fetchAll()`. - -Do dotazu lze velmi snadno přidávat i parametry, všimněte si otazníku: - -```php -$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 -``` - -
-POZOR, nikdy dotazy neskládejte jako řetězce, vznikla by zranitelnost [SQL injection |https://cs.wikipedia.org/wiki/SQL_injection] -/-- -$db->query('SELECT * FROM users WHERE name = ' . $name); // ŠPATNĚ!!! -\-- -
- -V případě selhání `query()` vyhodí buď `Nette\Database\DriverException` nebo některého z potomků: - -- [ConstraintViolationException |api:Nette\Database\ConstraintViolationException] - porušení nějakého omezení pro tabulku -- [ForeignKeyConstraintViolationException |api:Nette\Database\ForeignKeyConstraintViolationException] - neplatný cizí klíč -- [NotNullConstraintViolationException |api:Nette\Database\NotNullConstraintViolationException] - porušení podmínky NOT NULL -- [UniqueConstraintViolationException |api:Nette\Database\UniqueConstraintViolationException] - koliduje unikátní index - -Kromě `query()` jsou tu další užitečné funkce: - -```php -// vrátí asociativní pole id => name -$pairs = $database->fetchPairs('SELECT id, name FROM users'); - -// vrátí všechny záznamy jako pole -$rows = $database->fetchAll('SELECT * FROM users'); - -// vrátí jeden záznam -$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id); - -// vrátí přímo hodnotu buňky -$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id); -``` - -V případě selhání všechny tyto metody vyhodí `Nette\Database\DriverException`. - - -Insert, Update & Delete -======================= - -Parameterem, který vkládáme do SQL dotazu, může být i pole (v takovém případě je navíc možné zástupný symbol `?` vynechat), což se hodí třeba pro sestavení příkazu `INSERT`: - -```php -$database->query('INSERT INTO users ?', [ // tady můžeme otazník vynechat - 'name' => $name, - 'year' => $year, -]); -// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978) - -$id = $database->getInsertId(); // vrátí auto-increment vloženého záznamu - -$id = $database->getInsertId($sequence); // nebo hodnotu sekvence -``` - -Vícenásobný INSERT: - -```php -$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 můžeme předávat i soubory, objekty DateTime nebo [výčtové typy |https://www.php.net/enumerations]: - -```php -$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 -]); -``` - -Úprava záznamů: - -```php -$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(); // vrací počet ovlivněných řádků -``` - -Pro UPDATE můžeme využít operátorů `+=` a `-=`: - -```php -$database->query('UPDATE users SET', [ - 'age+=' => 1, // všimněte si += -], 'WHERE id = ?', $id); -// UPDATE users SET `age` = `age` + 1 -``` - -Mazání: - -```php -$result = $database->query('DELETE FROM users WHERE id = ?', $id); -echo $result->getRowCount(); // vrací počet ovlivněných řádků -``` - - -Pokročilé dotazy -================ - -Vložení, nebo úprava záznamu, pokud již existuje: - -```php -$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 -``` - -Všimněte si, že Nette Database pozná, v jakém kontextu SQL příkazu parametr s polem vkládáme a podle toho z něj sestaví SQL kód. Takže z prvního pole sestavil `(id, name, year) VALUES (123, 'Jim', 1978)`, zatímco druhé převedl do podoby `name = 'Jim', year = 1978`. - -Také řazení můžeme ovlivnit polem, v klíčích uvedeme sloupce a hodnotou bude boolean určující, zda řadit vzestupně: - -```php -$database->query('SELECT id FROM author ORDER BY', [ - 'id' => true, // vzestupně - 'name' => false, // sestupně -]); -// SELECT id FROM author ORDER BY `id`, `name` DESC -``` - -Pokud by u neobvyklé konstrukce detekce nezafungovala, můžete formu sestavení určit zástupným symbolem `?` doplněným o hint. Podporovány jsou tyto hinty: - -| ?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 - -V klauzuli WHERE se používá operátor `?and`, takže podmínky se spojují operátorem `AND`: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978 -``` - -Což můžeme snadno změnit na `OR` tím, že uvedeme zástupný symbol `?or`: - -```php -$result = $database->query('SELECT * FROM users WHERE ?or', [ - 'name' => $name, - 'year' => $year, -]); -// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978 -``` - -V podmínkách můžeme používat operátory: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name <>' => $name, - 'year >' => $year, -]); -// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978 -``` - -A také výčty: - -```php -$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') -``` - -Do podmínky také můžeme vložit kus vlastního SQL kódu pomocí tzv. SQL literálu: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - 'year >' => $database::literal('YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) -``` - -Nebo alternativě: - -```php -$result = $database->query('SELECT * FROM users WHERE', [ - 'name' => $name, - $database::literal('year > YEAR()'), -]); -// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) -``` - -SQL literál také může mít své parametry: - -```php -$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) -``` - -Díky čemuž můžeme vytvářet zajímavé kombinace: - -```php -$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') -``` - - -Proměnný název -============== - -Ještě existuje zástupný symbol `?name`, který využijete v případě, že název tabulky nebo sloupce je proměnnou. (Pozor, nedovolte uživateli manipulovat s obsahem takové proměnné): - -```php -$table = 'blog.users'; -$column = 'name'; -$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name); -// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim' -``` - - -Transakce -========= - -Pro práci s transakcemi slouží trojice metod: - -```php -$database->beginTransaction(); // zahájení transakce - -$database->commit(); // potvrzení - -$database->rollback(); // vrácení zpět -``` - -Elegantní způsob nabízí metoda `transaction()`, které předáme callback, který se vykoná v transakci. Pokud během vykonávání dojde k vyhození výjimky, transakce se zahodí, pokud vše proběhne v pořádku, transakce se potvrdí. - -```php -$id = $database->transaction(function ($database) { - $database->query('DELETE FROM ...'); - $database->query('INSERT INTO ...'); - // ... - return $database->getInsertId(); -}); -``` - -Jak vidíte, metoda `transaction()` vrací návratovou hodnotu callbacku. - -Volání `transaction()` může být i zanořeno, což zjednodušuje implementaci nezávislých repozitářů. +{{redirect: guide}} diff --git a/database/cs/direct-sql.texy b/database/cs/direct-sql.texy new file mode 100644 index 0000000000..9bc886be95 --- /dev/null +++ b/database/cs/direct-sql.texy @@ -0,0 +1,499 @@ +Direct SQL +********** + +.[perex] +S Nette Database můžete pracovat dvěma způsoby - buď přímo psát SQL dotazy (Direct přístup), nebo nechat SQL generovat automaticky ([Explorer přístup|explorer]). Direct přístup vám pomůže s bezpečným sestavováním dotazů, ale zachovává vám plnou kontrolu nad jejich podobou. + +.[note] +Informace o vytvoření připojení a konfiguraci najdete na [samostatné stránce |guide#Připojení a konfigurace]. + + +Pokládání SQL dotazů +==================== + +Pro dotazování do databáze slouží metoda `query()`. Ta vrací objekt [ResultSet |api:Nette\Database\ResultSet], který reprezentuje výsledek dotazu. V případě selhání metoda [vyhodí výjimku|exceptions]. +Výsledek dotazu můžeme procházet pomocí cyklu `foreach`, nebo použít některou z [pomocných funkcí|#Získání dat]. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; +} +``` + +Pro bezpečné vkládání hodnot do SQL dotazů používáme parametrizované dotazy. Nette Database je dělá maximálně jednoduché - stačí za SQL dotaz přidat čárku a hodnotu: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Při více parametrech máte dvě možnosti zápisu. Buď můžete SQL dotaz "prokládat" parametry: + +```php +$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age); +``` + +Nebo napsat nejdříve celý SQL dotaz a pak připojit všechny parametry: + +```php +$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age); +``` + + +Ochrana před SQL injection +========================== + +Proč je důležité používat parametrizované dotazy? Protože vás chrání před útokem zvaným SQL injection, při kterém by útočník mohl podstrčit vlastní SQL příkazy a tím získat nebo poškodit data v databázi. + +.[warning] +**Nikdy nevkládejte proměnné přímo do SQL dotazu!** Vždy používejte parametrizované dotazy, které vás ochrání před SQL injection. + +```php +// ❌ NEBEZPEČNÝ KÓD - zranitelný vůči SQL injection +$database->query("SELECT * FROM users WHERE name = '$name'"); + +// ✅ Bezpečný parametrizovaný dotaz +$database->query('SELECT * FROM users WHERE name = ?', $name); +``` + +Seznamte se s [možnými bezpečnostními riziky |security]. + + +Techniky dotazování +=================== + + +Podmínky WHERE +-------------- + +Podmínky WHERE můžete zapsat jako asociativní pole, kde klíče jsou názvy sloupců a hodnoty jsou data pro porovnání. Nette Database automaticky vybere nejvhodnější SQL operátor podle typu hodnoty. + +```php +$database->query('SELECT * FROM users WHERE', [ + 'name' => 'John', + 'active' => true, +]); +// WHERE `name` = 'John' AND `active` = 1 +``` + +V klíči můžete také explicitně specifikovat operátor pro porovnání: + +```php +$database->query('SELECT * FROM users WHERE', [ + 'age >' => 25, // použije operátor > + 'name LIKE' => '%John%', // použije operátor LIKE + 'email NOT LIKE' => '%example.com%', // použije operátor NOT LIKE +]); +// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%' +``` + +Nette automaticky ošetřuje speciální případy jako `null` hodnoty nebo pole. + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name' => 'Laptop', // použije operátor = + 'category_id' => [1, 2, 3], // použije IN + 'description' => null, // použije IS NULL +]); +// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL +``` + +Pro negativní podmínky použijte operátor `NOT`: + +```php +$database->query('SELECT * FROM products WHERE', [ + 'name NOT' => 'Laptop', // použije operátor <> + 'category_id NOT' => [1, 2, 3], // použije NOT IN + 'description NOT' => null, // použije IS NOT NULL + 'id' => [], // vynechá se +]); +// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL +``` + +Pro spojování podmínek se používá operátor `AND`. To lze změnit pomocí [zástupného symbolu ?or|#Hinty pro sestavování SQL]. + + +Pravidla ORDER BY +----------------- + +Řazení `ORDER BY` se dá zapsat pomocí pole. V klíčích uvedeme sloupce a hodnotou bude boolean určující, zda řadit vzestupně: + +```php +$database->query('SELECT id FROM author ORDER BY', [ + 'id' => true, // vzestupně + 'name' => false, // sestupně +]); +// SELECT id FROM author ORDER BY `id`, `name` DESC +``` + + +Vkládání dat (INSERT) +--------------------- + +Pro vkládání záznamů se používá SQL příkaz `INSERT`. + +```php +$values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', +]; +$database->query('INSERT INTO users ?', $values); +$userId = $database->getInsertId(); +``` + +Metoda `getInsertId()` vrátí ID naposledy vloženého řádku. U některých databází (např. PostgreSQL) je nutné jako parametr specifikovat název sekvence, ze které se má ID generovat pomocí `$database->getInsertId($sequenceId)`. + +Jako parametry můžeme předávat i [#speciální hodnoty] jako soubory, objekty DateTime nebo výčtové typy. + +Vložení více záznamů najednou: + +```php +$database->query('INSERT INTO users ?', [ + ['name' => 'User 1', 'email' => 'user1@mail.com'], + ['name' => 'User 2', 'email' => 'user2@mail.com'], +]); +``` + +Vícenásobný INSERT je mnohem rychlejší, protože se provede jediný databázový dotaz, namísto mnoha jednotlivých. + +**Bezpečnostní upozornění:** Nikdy nepoužívejte jako `$values` nevalidovaná data. Seznamte se s [možnými riziky |security#Klíče polí nejsou bezpečné API]. + + +Aktualizace dat (UPDATE) +------------------------ + +Pro aktualizacizáznamů se používá SQL příkaz `UPDATE`. + +```php +// Aktualizace jednoho záznamu +$values = [ + 'name' => 'John Smith', +]; +$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1); +``` + +Počet ovlivněných řádků vrátí `$result->getRowCount()`. + +Pro UPDATE můžeme využít operátorů `+=` a `-=`: + +```php +$database->query('UPDATE users SET ? WHERE id = ?', [ + 'login_count+=' => 1, // inkrementace login_count +], 1); +``` + +Příklad vložení, nebo úpravy záznamu, pokud již existuje. Použijeme techniku `ON DUPLICATE KEY UPDATE`: + +```php +$values = [ + 'name' => $name, + 'year' => $year, +]; +$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?', + $values + ['id' => $id], + $values, +); +// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978) +// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978 +``` + +Všimněte si, že Nette Database pozná, v jakém kontextu SQL příkazu parametr s polem vkládáme a podle toho z něj sestaví SQL kód. Takže z prvního pole sestavil `(id, name, year) VALUES (123, 'Jim', 1978)`, zatímco druhé převedl do podoby `name = 'Jim', year = 1978`. Podroběji se tomu věnujeme v části [Hinty pro sestavování SQL|#Hinty pro sestavování SQL]. + + +Mazání dat (DELETE) +------------------- + +Pro mazání záznamů se používá SQL příkaz `DELETE`. Příklad se získáním počtu smazaných řádků: + +```php +$count = $database->query('DELETE FROM users WHERE id = ?', 1) + ->getRowCount(); +``` + + +Hinty pro sestavování SQL +------------------------- + +Hint je speciální zástupný symbol v SQL dotazu, který říká, jak se má hodnota parametru přepsat do SQL výrazu: + +| Hint | Popis | Automaticky se použije +|-----------|-------------------------------------------------|----------------------------- +| `?name` | použije pro vložení názvu tabulky nebo sloupce | - +| `?values` | vygeneruje `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?` +| `?set` | vygeneruje přiřazení `key = value, ...` | `SET ?`, `KEY UPDATE ?` +| `?and` | spojí podmínky v poli operátorem `AND` | `WHERE ?`, `HAVING ?` +| `?or` | spojí podmínky v poli operátorem `OR` | - +| `?order` | vygeneruje klauzuli `ORDER BY` | `ORDER BY ?`, `GROUP BY ?` + +Pro dynamické vkládání názvů tabulek a sloupců do dotazu slouží zástupný symbol `?name`. Nette Database se postará o správné ošetření identifikátorů podle konvencí dané databáze (např. uzavření do zpětných uvozovek v MySQL). + +```php +$table = 'users'; +$column = 'name'; +$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table); +// SELECT `name` FROM `users` WHERE id = 1 (v MySQL) +``` + +**Upozornění:** symbol `?name` používejte pouze pro názvy tabulek a sloupců z validovaných vstupů, jinak se vystavujete [bezpečnostnímu riziku |security#Dynamické identifikátory]. + +Ostatní hinty obvykle není potřeba uvádět, neboť Nette používá při skládání SQL dotazu chytrou autodetekci (viz třetí sloupec tabulky). Ale můžete jej využít například v situaci, kdy chcete spojit podmínky pomocí `OR` namísto `AND`: + +```php +$database->query('SELECT * FROM users WHERE ?or', [ + 'name' => 'John', + 'email' => 'john@example.com', +]); +// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com' +``` + + +Speciální hodnoty +----------------- + +Kromě běžných skalárních typů (string, int, bool) můžete jako parametry předávat i speciální hodnoty: + +- soubory: `fopen('image.gif', 'r')` vloží binární obsah souboru +- datum a čas: objekty `DateTime` se převedou na databázový formát +- výčtové typy: instance `enum` se převedou na jejich hodnotu +- SQL literály: vytvořené pomocí `Connection::literal('NOW()')` se vloží přímo do dotazu + +```php +$database->query('INSERT INTO articles ?', [ + 'title' => 'My Article', + 'published_at' => new DateTime, + 'content' => fopen('image.png', 'r'), + 'state' => Status::Draft, +]); +``` + +U databází, které nemají nativní podporu pro datový typ `datetime` (jako SQLite a Oracle), se `DateTime` převádí na hodnotu určenou v [konfiguraci databáze|configuration] položkou `formatDateTime` (výchozí hodnota je `U` - unix timestamp). + + +SQL literály +------------ + +V některých případech potřebujete jako hodnotu uvést přímo SQL kód, který se ale nemá chápat jako řetězec a escapovat. K tomuto slouží objekty třídy `Nette\Database\SqlLiteral`. Vytváří je metoda `Connection::literal()`. + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + 'year >' => $database::literal('YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR()) +``` + +Nebo alternativě: + +```php +$result = $database->query('SELECT * FROM users WHERE', [ + 'name' => $name, + $database::literal('year > YEAR()'), +]); +// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR()) +``` + +SQL literály mohou obsahovat parametry: + +```php +$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) +``` + +Díky čemuž můžeme vytvářet zajímavé kombinace: + +```php +$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') +``` + + +Získání dat +=========== + + +Zkratky pro SELECT dotazy +------------------------- + +Pro zjednodušení načítání dat nabízí `Connection` několik zkratek, které kombinují volání `query()` s následujícím `fetch*()`. Tyto metody přijímají stejné parametry jako `query()`, tedy SQL dotaz a volitelné parametry. +Plnohodnotný popis metod `fetch*()` najdete [níže|#fetch()]. + +| `fetch($sql, ...$params): ?Row` | Provede dotaz a vrátí první řádek jako objekt `Row` +| `fetchAll($sql, ...$params): array` | Provede dotaz a vrátí všechny řádky jako pole objektů `Row` +| `fetchPairs($sql, ...$params): array` | Provede dotaz a vrátí asocitivní pole, kde první sloupec představuje klíč a druhý hodnotu +| `fetchField($sql, ...$params): mixed` | Provede dotaz a vrátí hodnotu prvního políčka z prvního řádku +| `fetchList($sql, ...$params): ?array` | Provede dotaz a vrací první řádek jako indexované pole + +Příklad: + +```php +// fetchField() - vrátí hodnotu první buňky +$count = $database->query('SELECT COUNT(*) FROM articles') + ->fetchField(); +``` + + +`foreach` - iterace přes řádky +------------------------------ + +Po vykonání dotazu se vrací objekt [ResultSet|api:Nette\Database\ResultSet], který umožňuje procházet výsledky několika způsoby. +Nejsnazší způsob, jak vykonat dotaz a získat řádky, je iterováním v cyklu `foreach`. Tento způsob je paměťově nejúspornější, neboť vrací data postupně a neukládá si je do paměti najednou. + +```php +$result = $database->query('SELECT * FROM users'); + +foreach ($result as $row) { + echo $row->id; + echo $row->name; + // ... +} +``` + +.[note] +`ResultSet` lze iterovat pouze jednou. Pokud potřebujete iterovat opakovaně, musíte nejprve načíst data do pole, například pomocí metody `fetchAll()`. + + +fetch(): ?Row .[method] +----------------------- + +Vrací řádek jako objekt `Row`. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek. + +```php +$result = $database->query('SELECT * FROM users'); +$row = $result->fetch(); // načte první řádek +if ($row) { + echo $row->name; +} +``` + + +fetchAll(): array .[method] +--------------------------- + +Vrací všechny zbývající řádky z `ResultSetu` jako pole objektů `Row`. + +```php +$result = $database->query('SELECT * FROM users'); +$rows = $result->fetchAll(); // načte všechny řádky +foreach ($rows as $row) { + echo $row->name; +} +``` + + +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- + +Vrátí výsledky jako asociativní pole. První argument určuje název sloupce, který se použije jako klíč v poli, druhý argument určuje název sloupce, který se použije jako hodnota: + +```php +$result = $database->query('SELECT id, name FROM users'); +$names = $result->fetchPairs('id', 'name'); +// [1 => 'John Doe', 2 => 'Jane Doe', ...] +``` + +Pokud uvedeme pouze první parametr, bude hodnotou celý řádek, tedy objekt `Row`: + +```php +$rows = $result->fetchPairs('id'); +// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...] +``` + +Pokud jako klíč uvedeme `null`, bude pole indexováno numericky od nuly: + +```php +$names = $result->fetchPairs(null, 'name'); +// [0 => 'John Doe', 1 => 'Jane Doe', ...] +``` + + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativně můžete jako parametr uvést callback, který bude pro každý řádek vracet buď samotnou hodnotu, nebo dvojici klíč-hodnota. + +```php +$result = $database->query('SELECT * FROM users'); +$items = $result->fetchPairs(fn($row) => "$row->id - $row->name"); +// ['1 - John', '2 - Jane', ...] + +// Callback může také vracet pole s dvojicí klíč & hodnota: +$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]); +// ['John' => 46, 'Jane' => 21, ...] +``` + + +fetchField(): mixed .[method] +----------------------------- + +Vrací hodnotu prvního políčka z aktuálního řádku. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek. + +```php +$result = $database->query('SELECT name FROM users'); +$name = $result->fetchField(); // načte jméno z prvního řádku +``` + + +fetchList(): ?array .[method] +----------------------------- + +Vrací řádek jako indexované pole. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek. + +```php +$result = $database->query('SELECT name, email FROM users'); +$row = $result->fetchList(); // ['John', 'john@example.com'] +``` + + +getRowCount(): ?int .[method] +----------------------------- + +Vrací počet ovlivněných řádků posledním dotazem `UPDATE` nebo `DELETE`. Pro `SELECT` je to počet vrácených řádků, ale ten nemusí být znám - v takovém případě metoda vrátí `null`. + + +getColumnCount(): ?int .[method] +-------------------------------- + +Vrací počet sloupců v `ResultSetu`. + + +Informace o dotazu +------------------ + +Pro ladicí účely můžeme získat informace o posledním provedeném dotazu: + +```php +echo $database->getLastQueryString(); // vypíše SQL dotaz + +$result = $database->query('SELECT * FROM articles'); +echo $result->getQueryString(); // vypíše SQL dotaz +echo $result->getTime(); // vypíše dobu vykonání v sekundách +``` + +Pro zobrazení výsledku jako HTML tabulky lze použít: + +```php +$result = $database->query('SELECT * FROM articles'); +$result->dump(); +``` + +ResultSet nabízí informace o typech sloupců: + +```php +$result = $database->query('SELECT * FROM articles'); +$types = $result->getColumnTypes(); + +foreach ($types as $column => $type) { + echo "$column je typu $type->type"; // např. 'id je typu int' +} +``` diff --git a/database/cs/exceptions.texy b/database/cs/exceptions.texy new file mode 100644 index 0000000000..023523c2ec --- /dev/null +++ b/database/cs/exceptions.texy @@ -0,0 +1,36 @@ +Výjimky +******* + +Nette Database používá hierarchii výjimek. Základní třídou je `Nette\Database\DriverException`, která dědí z `PDOException` a poskytuje rozšířené možnosti pro práci s chybami databáze: + +- Metoda `getDriverCode()` vrací kód chyby od databázového driveru +- Metoda `getSqlState()` vrací SQLSTATE kód +- Metody `getQueryString()` a `getParameters()` umožňují získat původní dotaz a jeho parametry + +Z `DriverException` dědí následující specializované výjimky: + +- `ConnectionException` - signalizuje selhání připojení k databázovému serveru +- `ConstraintViolationException` - základní třída pro porušení databázových omezení, ze které dědí: + - `ForeignKeyConstraintViolationException` - porušení cizího klíče + - `NotNullConstraintViolationException` - porušení NOT NULL omezení + - `UniqueConstraintViolationException` - porušení unikátnosti hodnoty + + +Příklad zachytávání výjimky `UniqueConstraintViolationException`, která nastane, když se snažíme vložit uživatele s emailem, který už v databázi existuje (za předpokladu, že sloupec email má unikátní index). + +```php +try { + $database->query('INSERT INTO users', [ + 'email' => 'john@example.com', + 'name' => 'John Doe', + 'password' => $hashedPassword, + ]); +} catch (Nette\Database\UniqueConstraintViolationException $e) { + echo 'Uživatel s tímto emailem již existuje.'; + +} catch (Nette\Database\DriverException $e) { + echo 'Došlo k chybě při registraci: ' . $e->getMessage(); +} +``` + + diff --git a/database/cs/explorer.texy b/database/cs/explorer.texy index 6dd5669964..6e9f1f2218 100644 --- a/database/cs/explorer.texy +++ b/database/cs/explorer.texy @@ -3,7 +3,7 @@ Database Explorer
-Nette Database Explorer je výkonná vrstva, která zásadním způsobem zjednodušuje získávání dat z databáze bez nutnosti psát SQL dotazy. +S Nette Database můžete pracovat dvěma způsoby - buď nechat SQL dotazy generovat automaticky (Explorer přístup), nebo je psát sami ([Direct přístup|direct-sql]). Explorer zásadním způsobem zjednodušuje přístup k datům. Stará se o práci se vztahy mezi tabulkami, takže se můžete soustředit na logiku vaší aplikace. - Práce s daty je přirozená a snadno pochopitelná - Generuje optimalizované SQL dotazy, které načítají pouze potřebná data @@ -12,9 +12,8 @@ Nette Database Explorer je výkonná vrstva, která zásadním způsobem zjednod
-Nette Database Explorer je nadstavbou nad nízkoúrovňovou vrstou [Nette Database Core |core], která přidává komfortní objektově-orientovaný přístup k databázi. - -Práce s Explorerem začíná voláním metody `table()` nad objektem [api:Nette\Database\Explorer] (jak ho získat je [popsáno tady |core#Připojení a konfigurace]): +.[note] +Práce s Explorerem začíná voláním metody `table()` nad objektem [api:Nette\Database\Explorer] (informace o vytvoření připojení a konfiguraci najdete na [samostatné stránce |guide#Připojení a konfigurace]): ```php $books = $explorer->table('book'); // 'book' je jméno tabulky @@ -76,7 +75,7 @@ Metody automaticky escapují parametry a uvozují identifikátory (názvy tabule - Řetězce vždy dosazujte přes **parametry**. ```php -where('name = ' . $name); // KATASTROFA: zranitelné vůči SQL injection +where('name = ' . $name); // KRITICKÁ ZRANITELNOST: SQL injection where('name LIKE "%search%"'); // ŠPATNĚ: komplikuje automatické uvozování where('name LIKE ?', '%search%'); // SPRÁVNĚ: hodnota dosazená přes parametr @@ -340,7 +339,7 @@ if ($book) { fetch(): ?ActiveRow .[method] ----------------------------- -Vrací jeden řádek a posune interní ukazatel na další. Pokud už neexistují další řádky, vrací `null`. +Vrací řádek a posune interní ukazatel na další. Pokud už neexistují další řádky, vrací `null`. ```php $books = $explorer->table('book'); @@ -350,8 +349,8 @@ while ($book = $books->fetch()) { ``` -fetchPairs(): array .[method] ------------------------------ +fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method] +--------------------------------------------------------------------------------------- Vrátí výsledky jako asociativní pole. První argument určuje název sloupce, který se použije jako klíč v poli, druhý argument určuje název sloupce, který se použije jako hodnota: @@ -360,7 +359,7 @@ $authors = $explorer->table('author')->fetchPairs('id', 'name'); // [1 => 'John Doe', 2 => 'Jane Doe', ...] ``` -Pokud je zadán pouze název sloupce pro klíč, bude hodnotou celý řadek, tedy objekt `ActiveRow`: +Pokud uvedeme pouze první parametr, bude hodnotou celý řadek, tedy objekt `ActiveRow`: ```php $authors = $explorer->table('author')->fetchPairs('id'); @@ -374,12 +373,16 @@ $authors = $explorer->table('author')->fetchPairs(null, 'name'); // [0 => 'John Doe', 1 => 'Jane Doe', ...] ``` -Jako parametr můžeme také uvést callback, který bude pro každý řádek vracet buď samotnou hodnotu, nebo dvojici klíč-hodnota. Pokud callback vrací pouze hodnotu, klíčem bude primární klíč řádku: + +fetchPairs(Closure $callback): array .[method] +---------------------------------------------- + +Alternativně můžete jako parametr uvést callback, který bude pro každý řádek vracet buď samotnou hodnotu, nebo dvojici klíč-hodnota. ```php $titles = $explorer->table('book') ->fetchPairs(fn($row) => "$row->title ({$row->author->name})"); -// [1 => 'První kniha (Jan Novák)', ...] +// ['První kniha (Jan Novák)', ...] // Callback může také vracet pole s dvojicí klíč & hodnota: $titles = $explorer->table('book') diff --git a/database/cs/guide.texy b/database/cs/guide.texy new file mode 100644 index 0000000000..3308670648 --- /dev/null +++ b/database/cs/guide.texy @@ -0,0 +1,194 @@ +Nette Database +************** + +.[perex] +Nette Database je výkonná a elegantní databázová vrstva pro PHP, která vyniká svou jednoduchostí použití a chytrými funkcemi. Nevyžaduje žádnou složitou konfiguraci nebo generování entit, s Nette Database můžete začít pracovat okamžitě. + +S Nette Database můžete pracovat dvěma způsoby - buď psaním SQL dotazů (Direct přístup), nebo nechat SQL generovat automaticky (Explorer přístup). + +
+
+ + +[Direct SQL] +============ +- Bezpečné parametrizované dotazy +- Přesná kontrola nad podobou SQL dotazů +- Když píšete komplexní dotazy s pokročilými funkcemi +- Optimalizujete výkon pomocí specifických SQL funkcí + +
+ +
+ + +[Explorer] +========== +- Vyvíjíte rychle bez psaní SQL +- Intuitivní práce s relacemi mezi tabulkami +- Oceníte automatickou optimalizaci dotazů +- Vhodné pro rychlou a pohodlnout práci s databází + +
+ +
+ + +Instalace +========= + +Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|best-practices:composer]: + +```shell +composer require nette/database +``` + + +Připojení a konfigurace +======================= + +Pro připojení k databázi stačí vytvořit instanci třídy [api:Nette\Database\Connection]: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password); +``` + +Parametr `$dsn` (data source name) je stejný, [jaký používá PDO |https://www.php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters], např. `host=127.0.0.1;dbname=test`. V případě selhání vyhodí výjimku `Nette\Database\ConnectionException`. + +Nicméně šikovnější způsob nabízí [aplikační konfigurace |configuration], kam stačí přidat sekci `database` a vytvoří se potřebné objekty a také databázový panel v [Tracy |tracy:] baru. + +```neon +database: + dsn: 'mysql:host=127.0.0.1;dbname=test' + user: root + password: password +``` + +Poté objekt spojení [získáme jako službu z DI kontejneru |dependency-injection:passing-dependencies], např.: + +```php +class Model +{ + // pro práci s vrstvou Database Explorer si předáme Nette\Database\Explorer + public function __construct( + private Nette\Database\Connection $database, + ) { + } +} +``` + +Více informací o [konfiguraci databáze|configuration]. + + +Dva přístupy k databázi +======================= + +S Nette Database můžete pracovat dvěma způsoby - buď psaním SQL dotazů (Direct přístup), nebo nechat SQL generovat automaticky (Explorer přístup). + +[Direct přístup|direct-sql] - SQL dotazy + +```php +// SELECT s parametrizovaným dotazem +$result = $database->query('SELECT * FROM users WHERE role = ?', 'admin'); + +// INSERT pomocí pole hodnot +$database->query('INSERT INTO users', [ + 'name' => 'John', + 'email' => 'john@example.com', + 'created_at' => new DateTime, +]); + +// UPDATE s podmínkou +$database->query('UPDATE users SET', [ + 'active' => true, + 'last_login' => new DateTime, +], 'WHERE id = ?', $userId); +``` + +Direct přístup nabízí pomocné nástroje pro bezpečné sestavování dotazů, ale zachovává vám plnou kontrolu nad jejich podobou. + +[Explorer přístup|explorer] - automatické generování SQL + +```php +// Práce s tabulkou +$users = $database->table('users'); + +// SELECT s podmínkou +$users->where('role', 'admin'); + +// Práce se vztahy +foreach ($users as $user) { + echo $user->name; + echo $user->role->description; // automatický JOIN + + // Výpis článků uživatele + foreach ($user->related('articles') as $article) { + echo $article->title; + } +} +``` + +Explorer přístup generuje a optimalizuje SQL dotazy automaticky. Stará se o efektivní načítání dat a práci se vztahy mezi tabulkami. + +Oba přístupy lze v aplikaci libovolně kombinovat podle potřeby. + + +Správa připojení +================ + +Při vytvoření objektu `Connection` dojde automaticky k připojení. Pokud chcete připojení odložit, použijte lazy režim - ten zapnete v [konfiguraci|configuration] nastavením `lazy`, nebo takto: + +```php +$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]); +``` + +Pro správu připojení k databázi slouží metody `connect()`, `disconnect()` a `reconnect()`. Metoda `connect()` naváže spojení s databází, pokud ještě není navázáno. Může vyhodit výjimku `Nette\Database\ConnectionException`. Metoda `disconnect()` odpojí se od databáze. Metoda `reconnect()` odpojí se a znovu připojí k databázi. Může vyhodit výjimku `Nette\Database\ConnectionException`. + +Kromě toho můžete sledovat události spojené s připojením pomocí události `onConnect`, což je pole callbacků, které se zavolají po navázání spojení s databází. + +```php +// proběhne po připojení k databázi +$database->onConnect[] = function($database) { + echo "Připojeno k databázi"; +}; +``` + + +Tracy Debug Bar +=============== + +Pokud používáte [Tracy |tracy:], aktivuje se automaticky panel Database v Debug baru, který zobrazuje všechny provedené dotazy, jejich parametry, dobu vykonání a místo v kódu, kde byly zavolány. + +[* db-panel.webp *] + + +Logování dotazů +--------------- + +Můžeme implementovat vlastní logování dotazů. Událost `onQuery` je pole callbacků, které se zavolají po každém provedeném dotazu: + +```php +$database->onQuery[] = function ($database, $result) use ($logger) { + $logger->info('Query: ' . $result->getQueryString()); + $logger->info('Time: ' . $result->getTime()); + + if ($result->getRowCount() > 1000) { + $logger->warning('Large result set: ' . $result->getRowCount() . ' rows'); + } +}; +``` + + +Podporované databáze +==================== + +Nette Database podporuje následující databáze: + +|* Databázový server |* DSN jméno |* Podpora v Explorer +| MySQL (>= 5.1) | mysql | ANO +| PostgreSQL (>= 9.0) | pgsql | ANO +| Sqlite 3 (>= 3.8) | sqlite | ANO +| Oracle | oci | - +| MS SQL (PDO_SQLSRV) | sqlsrv | ANO +| MS SQL (PDO_DBLIB) | mssql | - +| ODBC | odbc | - diff --git a/database/cs/mapper.texy b/database/cs/mapper.texy new file mode 100644 index 0000000000..93c7047ddf --- /dev/null +++ b/database/cs/mapper.texy @@ -0,0 +1,55 @@ +Konverze typů +************* + +.[perex] +Nette Database automaticky konvertuje hodnoty vrácené z databáze na odpovídající PHP typy. + + +Datum a čas +----------- + +Časové údaje jsou převáděny na objekty `Nette\Utils\DateTime`. Pokud chcete, aby byly časové údaje převáděny na immutable objekty `Nette\Database\DateTime`, nastavte v [konfiguraci|configuration] volbu `newDateTime` na true. + +```php +$row = $database->fetch('SELECT created_at FROM articles'); +echo $row->created_at instanceof DateTime; // true +echo $row->created_at->format('j. n. Y'); +``` + +V případě MySQL převádí datový typ `TIME` na objekty `DateInterval`. + + +Booleovské hodnoty +------------------ + +Booleovské hodnoty jsou automaticky převedeny na `true` nebo `false`. U MySQL se převádí `TINYINT(1)` pokud nastavíme v [konfiguraci|configuration] `convertBoolean`. + +```php +$row = $database->fetch('SELECT is_published FROM articles'); +echo gettype($row->is_published); // 'boolean' +``` + + +Číselné hodnoty +--------------- + +Číselné hodnoty jsou převedeny na `int` nebo `float` podle typu sloupce v databázi: + +```php +$row = $database->fetch('SELECT id, price FROM products'); +echo gettype($row->id); // integer +echo gettype($row->price); // float +``` + + +Vlastní normalizace +------------------- + +Pomocí metody `setRowNormalizer(?callable $normalizer)` můžete nastavit vlastní funkci pro transformaci řádků z databáze. To se hodí například pro automatický převod datových typů. + +```php +$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array { + // konverze typů + return $row; +}); +``` diff --git a/database/cs/reflection.texy b/database/cs/reflection.texy index eb2a063055..bade83ee3c 100644 --- a/database/cs/reflection.texy +++ b/database/cs/reflection.texy @@ -1,5 +1,5 @@ -Reflexe -******* +Reflexe struktury +***************** .{data-version:3.2.4} Nette Database poskytuje nástroje pro introspekci databázové struktury pomocí třídy [api:Nette\Database\Reflection\Reflection]. Ta umožňuje získávat informace o tabulkách, sloupcích, indexech a cizích klíčích. Reflexi můžete využít ke generování schémat, vytváření flexibilních aplikací pracujících s databází nebo obecných databázových nástrojů. @@ -7,7 +7,7 @@ Nette Database poskytuje nástroje pro introspekci databázové struktury pomoc Objekt reflexe získáme z instance připojení k databázi: ```php -$reflection = $connection->getReflection(); +$reflection = $database->getReflection(); ``` diff --git a/database/cs/security.texy b/database/cs/security.texy index 6319849831..01a6ef8242 100644 --- a/database/cs/security.texy +++ b/database/cs/security.texy @@ -157,4 +157,4 @@ $database->query('SELECT ?name FROM ?name', $column, $table); $database->query('SELECT ?name FROM users', $_GET['column']); ``` -Důležité: symbol `?name` používejte pouze pro důvěryhodné hodnoty definované v kódu aplikace. Pro hodnoty od uživatele použijte opět whitelist. +Důležité: symbol `?name` používejte pouze pro důvěryhodné hodnoty definované v kódu aplikace. Pro hodnoty od uživatele použijte opět whitelist. Jinak se vystavujete bezpečnostním rizikům, jako například dříve uvedený SQL enumeration nebo Mass Assignment Vulnerability. diff --git a/database/cs/transactions.texy b/database/cs/transactions.texy new file mode 100644 index 0000000000..e91070c2fb --- /dev/null +++ b/database/cs/transactions.texy @@ -0,0 +1,43 @@ +Transakce +********* + +.[perex] +Transakce zaručují, že se buď provedou všechny operace v rámci transakce, nebo se neprovede žádná. Jsou užitečné pro zajištění konzistence dat při složitějších operacích. + +Nejjednodušší způsob použití transakcí vypadá takto: + +```php +$database->beginTransaction(); +try { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); + $database->commit(); +} catch (\Exception $e) { + $database->rollBack(); + throw $e; +} +``` + +Mnohem elegantněji můžete to samé zapsat pomocí metody `transaction()`. Jako parametr přijímá callback, který vykoná v transakci. Pokud callback proběhne bez výjimky, transakce se automaticky potvrdí. Pokud dojde k výjimce, transakce se zruší (rollback) a výjimka se šíří dál. + +```php +$database->transaction(function ($database) use ($id) { + $database->query('DELETE FROM articles WHERE id = ?', $id); + $database->query('INSERT INTO audit_log', [ + 'article_id' => $id, + 'action' => 'delete' + ]); +}); +``` + +Metoda `transaction()` může také vracet hodnoty: + +```php +$count = $database->transaction(function ($database) { + $result = $database->query('UPDATE users SET active = ?', true); + return $result->getRowCount(); // vrátí počet aktualizovaných řádků +}); +```