You are browsing the unmaintained documentation for old Nette 2.3. See documentation for current Nette.

Database Explorer

Podívejme se na základy práce s vrstvou Database Explorer v Nette Frameworku.

Nette Database Explorer (dříve Nette Database Table, NDBT) zjednodušuje a optimalizuje výběr dat z databáze. Hlavní myšlenkou je načítání dat pouze z jedné tabulky a tak, aby se tyto dotazy pokládaly jen jednou. Data jsou načtena do ActiveRow instancí. Data z jiných tabulek připojených vazbami jsou načteny samostatnými požadavky – o to se stará samotná vrstva Database Explorer.

Pojďme si vyzkoušet jednoduchý příklad. Potřebujeme z databáze vybrat knihy a jejich autory. To je jednoduchý příklad vazby 1:N. Často používaná implementace vybírá data jedním SQL dotazem se spojením tabulek pomocí JOINu. Druhou možností je vybrat data odděleně, jedním dotazem knihy, a poté pro každou knihu vybrat jejího autora (např. pomocí foreach cyklu). To může být optimalizováno do dvou požadavků do databáze, jeden pro knihy a druhý pro autory – a přesně takto to dělá Nette Database Explorer.

Výber dat začněme od tabulky a to zavoláním metody $context->table() nad objektem Nette\Database\Context. Jak ho nejsnadněji získat je popsáno tady, pokud však používáme Nette Database Explorer samostatně, lze jej vytvořit i ručně.

$selection = $context->table('book'); // jméno tabulky je "book"

Nad výběrem můžeme jednoduše iterovat a projít tak všechny knihy. Řádky jsou vráceny jako instance ActiveRow a data z nich můžeme přímo číst.

$books = $context->table('book');
foreach ($books as $book) {
    echo $book->title;
    echo $book->author_id;
}

Výběr jednoho konkrétního řádku se provádí pomocí metody get(), která vrací přímo instanci ActiveRow.

$book = $context->table('book')->get(2); // vrátí knihu s id 2
echo $book->title;
echo $book->author_id;

Vazby mezi tabulkami

Jak jsme zmínili na začátku kapitoly, Database Explorer za nás spravuje vazby mezi tabulkami. S těmito vazbami můžeme pracovat dvěma způsoby:

  1. Filtrování záznamů, které vrátí Selection. Jak jsme zmínili na začátku kapitoly, Selection vrací data z jedné databázové tabulky. Může ale použít JOIN několika tabulek pro filtrování dat. Například pokud chceme vybrat jen ty autory, kteří napsali více než dvě knihy. Pro více podrobností se podívejte do kapitoly Selection.
  2. Získání souvisejících dat pro vybrané ActiveRows. Vybírání dat z více tabulek najednou jsme zavrhli, bohužel vypsání pouze author_id nám nestačí. Potřebujeme získat celý řádek z tabulky author, ideálně také jako instanci ActiveRow. Získávání tohoto typu závislostí má na starosti ActiveRow. Pro více podrobností se podívejte do kapitoly ActiveRow.

V níže uvedených příkladech budeme pracovat s databázovým schématem na obrázku. Jsou v něm vazby OneHasMany (1:N) a ManyHasMany (M:N). Vztah OneHasMany je dvojitý, kniha musí mít autora a může mít překladatele (translator_id může mít hodnotu NULL).

Struktura databáze pro uvedené příklady

V následujícím příkladu získáváme související data pro vybrané knihy. Property author v ActiveRow instanci book obsahuje další ActiveRow instanci reprezentující autora knihy. Získání instancí book_tag se provádí voláním metody related(), která vrací kolekci těchto instancí. V cyklu získáme jméno tagu z instance ActiveRow uložené v property tag u právě procházené vazby v instanci bookTag. Pro detailnější informace o získávání souvisejících dat se podívejte do kapitoly ActiveRow.

$books = $context->table('book');

foreach ($books as $book) {
    echo 'title:      ' . $book->title;
    echo 'written by: ' . $book->author->name;

    echo 'tags: ';
    foreach ($book->related('book_tag') as $bookTag) {
        echo $bookTag->tag->name . ', ';
    }
}

Příjemně vás překvapí, jak efektivně databázová vrstva pracuje. Výše uvedený příklad provede konstantní počet požadavků, podívejte se na tyto čtyři dotazy:

SELECT * FROM `book`
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))

Pokud použijete cache (ve výchozím nastavení je zapnutá), nebudou z databáze načítány žádné nepotřebné sloupce. Po prvním dotazu se do cache uloží jména použitých sloupců a dále budou z databáze vybírány pouze ty sloupce, které skutečně použijete:

SELECT `id`, `title`, `author_id` FROM `book`
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))

Ruční vytvoření Nette\Database\Context

Pokud jsme si vytvořili databázové spojení pomocí aplikační konfigurace, nemusíme se o nic starat. Vytvořila se nám totiž i služba typu Nette\Database\Context, kterou si můžeme předat pomocí DI.

Pokud ale používáme Nette Database Explorer samostatně, musíme instanci Nette\Database\Context vytvořit ručně.

// $storage obsahuje implementaci Nette\Caching\IStorage, např.:
$storage = new Nette\Caching\FileStorage($tempDir);
$connection = new Nette\Database\Connection($dsn, $user, $password);
$structure = new Nette\Database\Structure($connection, $storage);
$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure);
$context = new Nette\Database\Context($connection, $structure, $conventions, $storage);