Komponenty pomocí Dependency Injection

Od té doby, co máme v Nette předělaný Dependency Injection Container, vynořily se hromady dotazů: “Jak dostanu do formuláře translator?”, nebo: “Jak mám získat v komponentě nějaké řádky z databáze?” Ve skutečnosti je to velice snadné. Stačí si uvědomit, že služby nejsou to jediné, co může v konstruktoru vyžadovat jiné služby.

Články pomocí komponent

Ale pěkně od začátku. Jak tedy, úplně nejjednodušeji, získám například záznam z databáze? Protože dibi všichni známe, budu předpokládat, že máme jako službu @db, instanci DibiConnection. Nejprve po staru.

class ArticleControl extends Nette\Application\UI\Control
{
    public function render($id)
    {
        $this->template->article = $this->presenter->context->db->query("SELECT * FROM articles WHERE id = %i", $id);
        $this->template->render();
    }
}

Snadné, že? Ale moc to nedodržuje princip Dependency Injection. Pokud by taková komponenta už v konstruktoru přijala @db, tak si na něj nemusí sahat přes presenter. Proč je takový přístup lepší si můžete přečíst v dokumentaci Nette a nebo na Zdrojáku

class ArticleControl extends Nette\Application\UI\Control
{
    /** @var DibiConnection */
    private $db;

    public function __construct(DibiConnection $db)
    {
        parent::__construct(); // tenhle řádek je velice důležitý
        $this->db = $db;
    }

    public function render($id)
    {
        $this->template->article = $this->db->query("SELECT * FROM articles WHERE id = %i", $id);
        $this->template->render();
    }
}

To je mnohem lepší. Ale protože naše aplikace využívá modely jako logickou vrstvu, dotaz přesuneme do modelu.

class Articles extends Nette\Object
{
    /** @var DibiConnection */
    private $db;

    public function __construct(DibiConnection $db)
    {
        $this->db = $db;
    }

    public function find($id)
    {
        return $this->db->select('*')->from('articles')->where('id = %i', $id)->fetch() ?: NULL;
    }
}

Kód byl přesunut do modelu, kam patří. Zároveň bylo zaručeno, že když dibi nic nenajde, tak vrátí NULL. A teď zbývá model použít v komponentě.

class ArticleControl extends Nette\Application\UI\Control
{
    /** @var Articles */
    private $articles;

    public function __construct(Articles $articles)
    {
        parent::__construct();
        $this->articles = $articles;
    }

    public function render($id)
    {
        $this->template->article = $this->articles->find($id);
        $this->template->render();
    }
}

Jak by teď vypadala šablona téhle komponenty?

{if $article}
    <h1>{$article->title}</h1>
    <div class="article">{$article->content}</div>
    ...
{else}
    <p>Je nám líto, ale článek neexistuje, nebo byl smazán.</p>
{/if}

Díky tomu, že model vrací NULL, když nic nenajde a dibi umírá víceméně pouze na syntaktických chybách, nikdy nemůže vzniknout jiný stav, než že článek buď existuje a model ho najde a vrátí, nebo neexistuje a vrátí pouze NULL. Nemusíme se proto bát, že by vyskočila chyba a přestala se nám načítat stránka, uprostřed vykreslování šablony.

Takovouto komponentu si teď můžeme připojit do presenteru pomocí továrničky a předáme jí model, který máme zaregistrovaný jako službu pod názvem articles. Je samozřejmě možné použít i nějaký model loader.

protected function createComponentArticle()
{
    return new ArticleControl($this->context->articles);
}

Vykreslit takovouto komponentu jde pak velice snadno, třeba takto

{control article $presenter->id}

Příklad počítá s tím, že v persistentním parametru presenteru jménem $id bude ID článku. Pro úplnost, na takový presenter budeme generovat odkaz nejjednodušeji takto:

{link Article:show id => $article->id}

Služby v továrničkách

A co teď s tím translatorem v tom formuláři? To je snadné, prostě mu ho předáme!

protected function createComponentUserForm()
{
    $form = new Nette\Application\UI\Form();
    $form->addText('name', 'Name');
    $form->addText('surname', 'Surname');

    // předáme translator
    $form->setTranslator($this->context->translator);

    // připojíme formulář
    return $form;
}

A je vystaráno! Už nikdy žádný Environment! :)

Kam dál?

Více o vytváření modelů v dibi si můžete přečíst v článku Model: Entity-Repository-Mapper. Velice zajímavá debata o ORM pro Nette je i na fóru a pokud by někoho zajímalo více o NotORM může pokračovat v podnětném čtení v dalším vláknu

Mám tu chybu? Fix me

Autor:

comments powered by Disqus