Skip to content

Commit

Permalink
Database translated to Czech, typos fixed in English version. This re…
Browse files Browse the repository at this point in the history
…solves nette#82
  • Loading branch information
Jan Straka committed May 8, 2014
1 parent 3c48391 commit 7a69e2f
Show file tree
Hide file tree
Showing 8 changed files with 455 additions and 134 deletions.
2 changes: 1 addition & 1 deletion cs/@docmenu.texy
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
- [AJAX]
- [Rozšíření PHP |PHP Language Enhancements]
- [HTTP request & response]
- [Databáze & ORM |database]
- [Databáze |database], [Table |database-table], [Selection |database-selection], [ActiveRow |database-activerow]
- [Sessions |sessions]
- [E-maily |mailing]
- [Zpracování obrázků |images]
Expand Down
143 changes: 143 additions & 0 deletions cs/database-activerow.texy
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
Database: ActiveRow
*******************

Každý řádek, který vrátí [Selection | api:Nette\Database\Table\Selection] je reprezentován instancí [ActiveRow | api:Nette\Database\Table\ActiveRow]. Tato třída poskytuje standardní interface pro přístup k vybraným sloupcům. Data může číst jako členské proměnné, nebo použít přístup přes klíče pole. Pokud sloupec neexistuje, je vyhozena výjimka [MemberAccessException | api:Nette\MemberAccessException]. Jestli sloupec existuje, můžeme ověřit pomocí standardní konstrukce `isset`.

/--code php
echo $book->id;
echo $book['title'];

isset($book->id);
isset($book['title']);
\--

Pozor, `isset` vrátí FALSE i když sloupec ve výsledku existuje, ale má hodnotu NULL.


Vztahy
---------

Hlavním principem v Nette\Database\Table je oddělené načítání dat z různých tabulek. Toto chování je zajišťováno samotnou Nette\Database, příbuzná data jsou z databáze vybrána pro všechny řádky najednou. Máme dva hlavní druhy vztahů: "has one" a "has many".

.[note]
Budeme používat DiscoveredReflection, která z databáze načte vztahy mezi tabulkami přímo z databáze.

Relace Has one
--------------
Relace has one je velmi běžná. Kniha *má jednoho* autora. Kniha *má jednoho* překladatele. Řádek, který je ve vztahu has one získáme pomocí metody [ref() | api:Nette\Database\Table\ActiveRow::ref()]. Ref() přijímá dva argumenty: jméno cílové tabulky a název spojovacího sloupce. Viz příklad:

/--code php
$book = $context->table('book')->get(1);
$book->ref('author', 'author_id');
\--

V příkladu výše vybíráme souvisejícího autora z tabulky `author`. Primární klíč tabulky `author` je hledán podle sloupce `book.author_id`. Metoda ref() vrací instanci ActiveRow nebo NULL, pokud hledaný záznam neexistuje. Vrácený řádek je instance ActiveRow, takže s ním můžeme pracovat stejně jako se záznamem knihy.

/--code php
$author = $book->ref('author', 'author_id');
$author->name;
$author->born;

// nebo přímo
$book->ref('author', 'author_id')->name;
$book->ref('author', 'author_id')->born;
\--

Kniha má také jednoho překladatele, jeho jméno získáme snadno.
/--code php
$book->ref('author', 'translator_id')->name
\--

Tento přístup je funkční, ale pořád trochu zbytečně těžkopádný, nemyslíte? Databáze už obsahuje definice cizích klíčů, tak proč je nepoužít automaticky. Pojďme to vyzkoušet.

Pokud přistoupíme k členské proměnné, která neexistuje, ActiveRow se pokusí použít jméno této proměnné pro relaci "has one". Čtení této proměnné je stejné jako volání metody ref() pouze s jedním parametrem. Tomuto parametru budeme říkat **klíč**. Tento klíč bude použit pro vyhledání cizího klíče v tabulce. Předaný klíč je porovnán se sloupci, a pokud se shoduje, cizí klíč na daném sloupci je použit pro čtení dat z příbuzné tabulky. Viz příklad:

/--code php
$book->author->name;
// je stejné jako
$book->ref('author')->name;
\--

Instance ActiveRow nemá žádný sloupec author. Všechny sloupce tabulky book jsou prohledány na shodu s *klíčem*. Shoda v tomto případě znamená, že jméno sloupce musí obsahovat klíč. Tedy v příkladu výše sloupec `author_id` obsahuje řetězec "author" a tedy odpovídá klíči "author". Pokud chceme přistoupit k záznamu překladatele, obdobným způsobem použijeme klíč "translator", protože bude odpovídat sloupci `translator_id`. Více o logice párování klíčů si můžete přečíst v kapitole [Joining expressions | database-joining-expressions].

/--code php
echo $book->title . ": ";
echo $book->author->name;
if ($book->translator) {
echo " (translated by " . $book->translator->name . ")";
}
\--

Pokud chceme získat autora více knih, použijeme stejný přístup. Nette\Database za nás z databáze záznamy autorů a překladatelů pro všechny knihy najednou.

/--code php
$books = $context->table('book');
foreach ($books as $book) {
echo $book->title . ": ";
echo $book->author->name;
if ($book->translator) {
echo " (translated by " . $book->translator->name . ")";
}
}
\--

Tento kód zavolá pouze tyto tři dotazy do databáze:
/--code sql
SELECT * FROM `book`;
SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column
SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column
\--


Relace Has many
---------------

Relace "has many" je pouze obrácená "has one". Autor napsal *několik* knih. Autor přeložil *několik* knih. Tento typ relace je obtížnější, protože vztah je pojmenovaný ("napsal", "přeložil"). Instance ActiveRow má metodu [related() | api:Nette\Database\Table\ActiveRow::related()], která vrací pole souvisejících záznamů. Záznamy jsou opět instance ActiveRow. Viz příklad:

/--code php
$author = $context->table('author')->get(11);
echo $author->name . " napsal:";

foreach ($author->related('book.author_id') as $book) {
echo $book->title;
}

echo "a přeložil:";
foreach ($author->related('book.translator_id') as $book) {
echo $book->title;
}
\--

Metoda [related() | api:Nette\Database\Table\ActiveRow::related()] přijímá popis spojení jako dva argumenty, nebo jako jeden argument spojený tečkou. První argument je cílová tabulka, druhý je sloupec.
/--code php
$author->related('book.translator_id');
// je stejné jako
$author->related('book', 'translator_id');
\--

Můžeme použít heuristiku Nette\Database založenou na cizích klíčích v databázi a použít pouze **klíč**. Klíč bude porovnán s cizími klíči, které odkazují do aktuální tabulky (tabulka `author`). Pokud je nalezena shoda, Nette\Database použije tento cizí klíč, v opačném případě vyhodí náležitou výjimku: [MissingReferenceException | api:Nette\Database\Reflection\MissingReferenceException] nebo [AmbiguousReferenceKeyException | api:Nette\Database\Reflection\AmbiguousReferenceKeyException]. Více o logice párování klíčů si můžete přečíst v kapitole [Joining expressions | database-joining-expressions].

Metodu related může samozřejmě volat na všechny získané autory a Nette\Database načte všechny odpovídající knihy najednou.

/--code php
$authors = $context->table('author');
foreach ($authors as $author) {
echo $author->name . " napsal:";
foreach ($author->related('book') as $book) {
$book->title;
}
}
\--

Příklad uvedený výše spustí pouze tyto dva dotazy do databáze::

/--code sql
SELECT * FROM `author`;
SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id vybraných autorů
\--

{{toc: title}}

{{themeicon: icon-database.png}}
{{care: Jan Škrášek|449}}
{{composer: nette/database}}
159 changes: 159 additions & 0 deletions cs/database-selection.texy
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
Database: Selection
===================

[api:Nette\Database\Table\Selection] nám umožňuje vybírat a filtrovat řádky databázových tabulek. Její síla spočívá v psaní podmínek, které automaticky spojí tabulky pomocí JOIN. Už nikdy nebude potřeba spojovat tabulky ručně. .[perex]

Filtrování
----------
Pojďme se podívat na základní API Selection. Jednoduché podmínky můžeme vytvořit zavoláním metody [where() |api:Nette\Database\Table\Selection::where()]. Otazníky v dotazu budou nahrazeny obsahem proměnných.

/--code php
$selection->where('name = ?', $name);
$selection->where('born > ?', $datetime);
\--

Nette\Database automaticky přidá vhodné operátory podle toho, jaká data dostane:
/--code php
$name = 'Jon Snow';
$ids = array(1, 2, 3);
$null = NULL;

$selection->where('name', $name); // name = 'Jon Snow'
$selection->where('id', $ids); // id IN (1, 2, 3)
$selection->where('born', $null); // born IS NULL
\--

Vícenásobné volání metody `where()` spojí podmínky pomocí operátoru `AND`. Pokud potřebujeme operátor `OR`, musíme podmínku napsat najednou.

/--code php
// id = 1 AND name = 'Jon Snow'
$selection->where('id', 1)->where('name', $name);
$selection->where('id = ? AND name = ?', 1, $name);

// id = 1 OR name = 'Jon Snow'
$selection->where('id = ? OR name = ?', 1, $name);
\--

Opatrně, pokud nepřidáme operátor pro sloupec, může předaná hodnota ovlivnit zamýšlené chování. .[warning]

Zástupný symbol (otazník) funguje i bez sloupcového operátoru. Následující volání jsou prakticky stejná.
/--code php
$selection->where('id = ? OR id = ?', 1, 2);
$selection->where('id ? OR id ?', 1, 2);
\--

Selection správně zpracovává i záporné podmínky a umí pracovat také s poli.

/--code php
$selection->where('NOT id', 1);
$selection->where('id NOT', 1); // to samé

$ids = array();
$selection->where('id', $ids); // id IS NULL AND FALSE
$selection->where('id NOT', $ids); // id IS NULL OR FALSE
$selection->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE)

// toto způsobí výjimku, tato syntax není podporovaná
$selection->where('NOT id ?', $ids);
\--


Filtrování hodnotou z jiné tabulky
----------------------------------

Často potřebujeme filtrovat výsledky pomocí podmínky, která zahrnuje jinou databázovou tabulku. Tento typ podmínek vyžaduje spojení tabulek, s Nette\Database už je ale nikdy nemusíme psát ručně.

Řekněme, že chceme vybrat všechny knihy, které napsal autor jménem "Jon". Musíme napsat pouze jméno "spojovacího klíče" relace a název sloupce spojené tabulky. "Spojovací klíč" je odvozen od jména sloupce, který odkazuje na tabulku, se kterou se chceme spojit. V našem příkladu (viz databázové schéma) je to sloupec `author_id`, ze kterého stačí použít část - `author`. `name` je název sloupce v tabulce `author`. Můžeme vytvořit podmínku také pro překladatele knihy, který je připojen sloupcem `translator_id`.

/--code php
$selection = $context->table('book');
$selection->where('author.name LIKE ?', '%Jon%');
$selection->where('translator.name', 'David Grudl');
\--

Logika vytváření "spojovacího klíče" je dána implementací [IReflection |api:Nette\Database\IReflection]. Doporučujeme použití [DiscoveredReflection |api:Nette\Database\Reflection\DiscoveredReflection], které analyzuje cizí klíče a umožňuje jednoduše pracovat se vztahy mezi tabulkami.

Vztah mezi knihou a autorem je 1:N. Obrácený vzhtah je také možný, nazýváme ho **backjoin**. Podívejte se na následující příklad. Chceme vybrat všechny autory, kteří napsali více než tři knihy. Pro vytvoření obráceného spojení použijeme `:` (dvojtečku). Dvojtečka znamená, že jde o vztah "hasMany" (a je to logické, dvě tečky jsou více než jedna). Bohužel Selection není dostatečně chytré a musíme mu pomoci s agregací výsledků a předat mu nějaký `GROUP BY` výrok, také podmínka musí být zapsaná jako výrok `HAVING`.

/--code php
$selection = $context->table('author');
$selection->group('author.id')
->where('COUNT(:book.id) > 3');
\--

Možná jste si všimli, že spojovací výraz odkazuje na book, ale není jasné, jestli spojujeme přes `author_id` nebo `translator_id`. Ve výše uvedeném příkladu Selection spojuje přes sloupec `author_id`, protože byla nalezena shoda se jménem zdrojové tabulky - tabulky `author`. Pokud by neexistovala shoda a existovalo více možností, Nette vyhodí výjimku [AmbiguousReferenceKeyException |api:Nette\Database\Reflection\AmbiguousReferenceKeyException].

Abychom mohli spojovat přes `translator_id`, stačí přidat volitelný "parametr" do spojovacího výrazu.

/--code php
$selection = $context->table('author');
$selection->group('author.id')
->having('COUNT(:book(translator).id) > 3')
\--

Teď se podívejme na složitější příklad na skládání tabulek.

Chceme vybrat všechny autory, kteří napsali něco o PHP. Všechny knihy mají štítky, takže chceme vybrat všechny autory, kteří napsali knihu se štítkem "PHP".

/--code php
$selection = $context->table('author');
$selection->where(':book:book_tags.tag.name', 'PHP')
->group('author.id')
->having('COUNT(:book:book_tags.tag.id) > 0');
\--




API
-------------
Podívejme se na různé možnosti filtrování a omezování pomocí Selection:
.[wide]

| `$table->where($where[, $param[, ...]])` | Nastaví WHERE
| `$table->order($columns)` | Nastaví ORDER BY, může být výraz `('column DESC, id DESC')`
| `$table->select($columns)` | Nastaví vrácené sloupce, může být výraz `('col, MD5(col) AS hash')`
| `$table->limit($limit[, $offset])` | Nastaví LIMIT a OFFSET
| `$table->group($columns)` | Nastaví GROUP BY
| `$table->having($having)` | Nastaví HAVING

Můžeme použít tzv. fluent interface, například `$table->where(...)->order(...)->limit(...)`. Více `where` podmínek je spojeno pomocí operátoru `AND`. Pro operátor `OR` musíme použít pouze jedno volání `where()`:

/--code php
$table->where('born > ? OR born IS NULL', $datetime);
\--

Možné argumenty metody [where() |api:Nette\Database\Table\Selection::where()]:

.[wide]
| `$table->where("field", $value)` | field = $value
| `$table->where("field", NULL)` | field IS NULL
| `$table->where("field > ?", $val)` | field > $val
| `$table->where("field", array(1, 2))` | field IN (1, 2)
| `$table->where("field", $conn->table($tableName))` | field IN (SELECT $primary FROM $tableName)
| `$table->where("field", $conn->table($tableName)->select('col'))` | field IN (SELECT col FROM $tableName)

Můžeme vytvořit také agregaci výsledků:

.[wide]
| `$table->count("*")` | Vrátí počet řádků
| `$table->count("DISTINCT $column")` | Vrátí počet odlišných hodnot
| `$table->min($column)` | Vrátí minimální hodnotu
| `$table->max($column)` | Vrátí maximální hodnotu
| `$table->aggregation("GROUP_CONCAT($column)")` | Pro jakoukoliv jinou agregační funkci

Čtení dat:

.[wide]
| `foreach ($table as $id => $row)` | Iteruje přes všechny řádky výsledku
| `$row = $table->get($id)` | Vrátí jeden řádek s ID $id
| `$row = $table->fetch()` | Vrátí následující řádek výsledku
| `$array = $table->fetchPairs($key, $value)` | Vrátí všechny výsledky jako asociativní pole
| `$array = $table->fetchPairs($key)` | Vrátí všechny řádky jako asociativní pole
| `count($table)` | Vrátí počet řádků výsledku

{{toc: title}}

{{themeicon: icon-database.png}}
{{care: Jan Škrášek|449}}
{{composer: nette/database}}
Loading

0 comments on commit 7a69e2f

Please sign in to comment.