Model

S tím, jak aplikace roste, brzy zjistíme, že na různých místech, v různých presenterech, potřebujeme provádět podobné operace s databází, například získávat nejnovější publikované články. Když aplikaci vylepšíme třeba tím, že u článků přidáme příznak, zda je rozepsaný, musíme potom projít i všechna místa v aplikaci, kde se články z databáze získávají, a doplnit podmínku where, aby se vybíraly jen články nerozepsané.

V ten moment se přímá práce s databází stává nedostatečnou a bude šikovnější si vypomoci novou funkcí, která nám publikované články bude vracet. A když později přidáme další podmínku, například že se nemají zobrazovat články s budoucím datem, upravíme kód jen na jednom místě.

Funkci umístíme třeba do třídy ArticleManager a nazveme ji getPublicArticles().

V adresáři app/model/ vytvoříme naši modelovou třídu ArticleManager, která se nám bude starat o články. Umístíme ji například do souboru ArticleManager.php.

namespace App\Model;

use Nette;

class ArticleManager
{
    use Nette\SmartObject;

    /** @var Nette\Database\Context */
    private $database;

    public function __construct(Nette\Database\Context $database)
    {
        $this->database = $database;
    }

    public function getPublicArticles()
    {
        return $this->database->table('posts')
            ->where('created_at < ', new \DateTime)
            ->order('created_at DESC');
    }
}

Třídu jsme vytvořili pomocí konstruktoru, ve kterém si necháme předat databázový Context.

Pokud bychom nevyužívali frameworku a síly DI containeru, museli bychom v našem případě nejprve vytvořit instanci Nette\Database\Connection, Nette\Database\Structure, Nette\Database\Conventions a Nette\Caching\Storages\FileStorage (které pro své instancování také potřebují nějaké závislosti, třeba jméno a heslo k databázi). To vše pouze pro vytvoření instance Nette\Database\Context, kterou bychom následně použili pro vytvoření instance ArticleManager.

...
// vyrobíme vše, co je třeba pro Context
$dbConnection = new Nette\Database\Connection(...);
$dbStructure = new Nette\Database\Structure(...);
$dbConventions = new Nette\Database\Conventions\DiscoveredConventions(...);
$dbCache = new Nette\Caching\Storages\FileStorage(...);

// vyrobíme Context
$dbContext = new Nette\Database\Context($dbConnection, $dbStructure, $dbConventions, $dbCache);

// vyrobíme ArticleManager
$articleManager = new App\Model\ArticleManager($dbContext)
...

Tolik psaní pro to, abychom dostali fungujicí ArticleManager? To asi nechceme. Proto použijeme DI container, který berme jako skřínku, která generuje tyto výrobní kódy a nám vrátí jen požadovaný objekt.

Jediné co potřebujeme, je říci DI kontejneru, aby nám vrátil požadovaný objekt. To můžeme zařídit v konfiguraci aplikace. V souboru app/config/config.neon v sekci services přidáme řádek s celým názvem třídy.

services:
    router: App\RouterFactory::createRouter
    - App\Model\ArticleManager

Tím Nette a jeho DI containeru říkáme: „Kdyby někdo chtěl instanci této třídy, tak víš kde ji hledat. Předej této instanci vše co potřebuje k životu a připrav nám ji pro použití v dalších třídách.“ Takto registrovaná třída se pak nazývá služba.

Přepneme se do HomepagePresenter.php, který upravíme tak, že se zbavíme závislosti na Nette\Database\Context a nahradíme za novou závislost na naší nové třídě.

namespace App\Presenters;

use Nette;
use App\Model\ArticleManager;


class HomepagePresenter extends Nette\Application\UI\Presenter
{
    /** @var ArticleManager */
    private $articleManager;

    public function __construct(ArticleManager $articleManager)
    {
        $this->articleManager = $articleManager;
    }

    public function renderDefault(): void
    {
        $this->template->posts = $this->articleManager->getPublicArticles()->limit(5);
    }
}

V sekci use máme App\Model\ArticleManager, tak si můžeme zápis v PHP kódu zkrátit na ArticleManager (nebojte, i v komentářích to funguje a vaše chytré IDE by si s tím mělo poradit).

V konstruktoru si požádáme o ArticleManager, který si přiřadíme do vlastnosti $articleManager a v metodě renderDefault zavoláme metodu getPublicArticles(). Nad výsledkem této funkce zavoláme ještě metodu limit(5);

Třída ArticleManager si v konstruktoru řekne o předání Nette\Database\Context a jelikož je tato třída v DI containeru zaregistrovaná, kontejner tuto instanci vytvoří a předá ji. DI za nás takto vytvoří instanci ArticleManagera a předá ho v konstrukotru třídě HomepagePresenter, který si o něj požádal. Taková matroška. :) Všichni si jen říkají co chtějí a nezajímají se o to, kde se co a jak vytváří. O vytvoření se postará Nette DI container.