Databáze & ORM
Nette Framework vám poskytuje vrstvu pro výrazně pohodlnější práci s databázemi. Co umí?
- snadno skládat SQL dotazy
- snadno získávat data
- pokládá efektivní dotazy a nepřenáší zbytečná data
Základem je obálka nad PDO nazvaná Nette\Database\Connection:
use Nette\Database\Connection;
$database = new Connection($dsn, $user, $password);
Po připojení se automaticky zobrazí panel na Debugger baru. Ten nás informuje o všech vykonaných SQL dotazech, době jejich vyřízení, počtu vrácených řádků, dokonce uvádí i jejich EXPLAIN podobu a místo, v kódu, kde byl dotaz volán.
Oproti PDO dovoluje Connection uvádět v příkazech
exec a query výčet parametrů:
$database->exec('INSERT INTO users', array( // parametrem může být pole
'name' => 'Jim',
'created' => new DateTime, // nebo objekt DateTime
'avatar' => fopen('image.gif', 'r'), // nebo soubor
), ...); // lze použít i vícenásobný insert
$database->exec('UPDATE users SET ? WHERE id=?', $data, $id);
$database->query('SELECT * FROM categories WHERE id=?', 123)->dump();
Práce s tabulkami
Pro složitější výběry lze použít nástroj, který vychází z výborné knihovny NotORM a nabízí čitelnější API. Databázi na obrázku lze procházet takto:
Příklad SQL databáze
foreach ($database->table('book')->order('title')->limit(5) as $book)
{
echo $book->title, ' (', $book->author->name, ')';
// ekvivalentní s $book['title'], ' (', $book['author']['name'], ')';
foreach ($book->related('book_tag') as $book_tag) {
echo $book_tag->tag->name . ', ';
}
}
V šabloně by se použil pochopitelně Latte-zápis:
{foreach $database->table('book')->order('title')->limit(5) as $book}
<h2>{$book->title} ({$book->author->name})</h2>
{foreach $book->related('book_tag') as $book_tag}
{$book_tag->tag->name}{sep}, {/sep}
{/foreach}
{/foreach}
Efektivita
Budete nadšeni, jak efektivně databázová vrstva pracuje. Žádné sloupce se zbytečně nepřenášejí vícekrát. Uvedený příklad pokládá tyto dotazy:
SELECT * FROM `book` ORDER BY title LIMIT 5 SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12)) SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23))
Při použití cache (zatím neimplementováno) se umí dotazovat jen na sloupce, které se skutečně použijí:
SELECT `id`, `title`, `author_id` FROM `book` ORDER BY title LIMIT 5 SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12)) SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3)) SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23))
Table Selection API
(převzato z NotORM API)
$table = $conn->table($tableName) |
Get representation of table $tableName |
$table->find($value) |
Search by primary key |
$table->where($where[, $parameters[, ...]]) |
Set WHERE (explained later) |
$table->order($columns) |
Set ORDER BY, can be expression ('column DESC, id DESC') |
$table->select($columns) |
Set retrieved columns, can be expression ('col, MD5(col) AS
hash') |
$table->limit($limit[, $offset]) |
Set LIMIT and OFFSET |
$table->group($columns[, $having]) |
Set GROUP BY and HAVING |
Výrazy lze zřetězit, např.
$table->where(...)->order(...)->limit(...). Více
podmínek where se spojuje operátorem AND.
$table->where("field", $value) |
Means field = $value |
$table->where("field", NULL) |
Means field IS NULL |
$table->where("field > ?", $val) |
Means field > $val |
$table->where("field", array(1, 2)) |
Means field IN (1, 2) |
$table->where("field", $conn->table($tableName)) |
Means field IN (SELECT $primary FROM $tableName) |
$table->where("field",
$conn->table($tableName)->select('col')) |
Means field IN (SELECT col FROM $tableName) |
$table->where(array("field" => "x", "field2" =>
"y")) |
Same as $table->where("field", "x")->where("field2",
"y") |
Pokud použijeme tečkovou notaci ("$table.$column"),
automaticky se vytvoří spojení s odkazovanou tabulkou.
Lze také vytvářet agregační dotazy:
$table->count("*") |
Get number of rows |
$table->count("DISTINCT $column") |
Get number of distinct values |
$table->min($column) |
Get minimum value |
$table->max($column) |
Get maximum value |
$table->aggregation("GROUP_CONCAT($column)") |
Run any aggregation function |
Získávání dat:
foreach ($table as $id => $row) |
Iterate all rows in result |
$row = $table->get($id) |
Get single row with ID $id from table |
$row = $table[$id] |
Get row with ID $id from the result |
$row = $table->fetch() |
Get next row from the result |
$array = $table->fetchPairs($key, $value) |
Fetch all values to associative array |
$array = $table->fetchPairs($key) |
Fetch all rows to associative array |
count($table) |
Get number of rows in result set |
Práce s jedním řádkem:
$data = $row[$column] nebo $row->$column |
Vrátí hodnotu atributu $column nebo jednu řádku z tabulky rodiče.
Např. $book['title'] == $book->title
nebo $book['author'] == $book->author ==
$book->ref('author') |
$rodic = $dite->ref($tableName) |
Vrátí jednu řádku z cizí tabulky (rodič). Např.
$book->ref('author')->name |
$tabulkaDeti = $rodic->related($tableName) |
Vrátí tabulku dětí již odfiltrovanou hodnotou primárního klíče
rodiče. Použije se atribut tabulky dětí odvozený od názvu tabulky rodiče.
Např. $author->related('book')->count() |
$tabulkaDeti = $rodic->related($tableName)
->through($column) |
Použije konkrétní atribut tabulky dětí pro filtrování.
Např. $author->related('book')
->through('maintainer_id')->count() |
(string) $row |
Vrátí hodnotu primárního klíče |
Komentáře 
zkrat | 25. 7. 2011, 11:32 | comment
Nette\Database funguje až ve verzi 2.0, jako věčina věcí v této dokumentaci. Pokud planuješ používat verzi 0.9 , tak k ní slouží dokumentace http://doc09.nette.org/cs/, ale doporučoval bych ti používat spíš verzi 2.0beta
Jan Tvrdík | 28. 7. 2011, 13:00 | comment
Tahle databázová vrstva má nahradit dibi používané v Nette 0.9?
Nette\Database je alternativou, nikoliv náhradou za dibi. Pokud
ti dibi vyhovuje, doporučuji ti ji používat dál.
edmund | 20. 8. 2011, 10:39 | comment
Tahle část dokumentace už je hotová nebo se ještě bude upravovat? Podle toho, co jsem zde vyčetl, je práce s propojenými tabulkami pohoda, ale jen za předpokladu, že je použit určitý způsob pojmenování sloupců a tabulek, které spolu souvisí. Bylo by možné to trochu podrobněji popsat? Jak pojmenovávat sloupce, aby to fungovalo podle návodu a jak to případně udělat, pokud z nějakého důvodu tuto konvenci nelze použít?
lupo112 | 23. 8. 2011, 16:14 | comment
Dokumentácia nie je hotová. V poslednej verzií už funguje cachovanie, len ho treba nastavit v neone (a z dokumentacie odstranit „zatím neimplementováno“).
Tomáš Kuba | 4. 10. 2011, 13:41 | comment
Jak na DB Cache: http://forum.nette.org/…tte-database
josef.sabl | 12. 10. 2011, 11:04 | question
Jakým způsobem se escapuje idnetifikátor? Např. název sloupce.
Viz. příklad:
$row = $this->getDatabase()
->table($this->table)
->where($paramName . ' = ?', $paramValue) //FIXME ! paramName not escaped
->fetch()
SQL injekce, $paramName není escapováno.
rixi | 2. 2. 2012, 10:37 | comment
Nette Framework pre pracu s databazou je mozne pouzivat aj s inymi vrstvami. Tu je perfektny prehlad SQL prikazov pre Nette\Database, NotORM, dibi a Doctrine: http://bit.ly/SQLcQ


michalbuk | 25. 7. 2011, 7:58 | question
Dobrý den, s Nette začínám, takže se možná zeptám hloupě, ale tahle databázová vrstva má nahradit dibi používané v Nette 0.9? Díky