Blog: Co se nevešlo na Twitter - Tag Nette Framework

Opensource v českých podmínkách

Možná jste zaregistrovali

David Grudl, tvůrce Nette Frameworku zkrátka řeší, jak se dostat do stavu, že bude Nette živit jeho a né naopak.

Na tom není nic divného, mám desítky nápadů co bych chtěl udělat do Kdyby, ale zkrátka na to není čas. Přesněji, není to pro mě priorita. Priorita je pro mě zaplatit nájem, mít na jídlo a občas si koupit nějakou novou hračku. A až potom můžu dělat dokumentaci do Kdyby. Kdyby mě totiž neživí. Ohromným způsobem mi pomohlo rozvinout se, získat kontakty a lepší práci, ale neživí mě.

Troufl bych si tvrdit, že ta situace je velice podobná, ovšem v trošku jiném měřítku. Nette je konec konců 1000x větší projekt.

Pokračovat ve čtení ...

Presentery v DI Containeru

V Nette Frameworku funguje instanciování presenterů tak, že v PresenterFactory se nějak přeloží název presenteru v “Nette tvaru”, třeba Front:Homepage na název třídy, třeba na FrontModule\HomepagePresenter (což závisí na konvenci a jde to samozřejmě změnit).

V tenhle moment známe název třídy, která se má instanciovat a spustit. Jenže tato třída má nějaké závislosti a je potřeba správně vytvořit instanci a tyto závislosti předat. Jak na to? Dříve to fungovalo tak, že se prostě přes reflexi kouklo na konstruktor a proběhl autowire.

Před pár měsíci (nebo roky?) byla přidána podpora pro “vytahování” instancí presenterů z DI Containeru. Což je strašně fajn, protože si pak můžete presentery zaregistrovat do DI Containeru. Analýza závislostí se pak provede compile-time, tedy právě jednou a výkon i čas sežere také právě jednou.

Pokračovat ve čtení ...

Kdyby/Monolog + syslog + papertrail

U každé aplikace, která vydělává nějaké peníze, dříve nebo později zjistíte, že potřebujete logovat někam co se děje a co kdo udělal. Někteří k tomu používají databázi a _audit tabulky, ale ty se nehodí na všechno. Někdy je prostě jednodušší to nasázet do souborů, a pak už se k nim nikdy nevrátit, protože to nikoho nebaví chodit kontrolovat :) Ale co si budem, v té databázi by to taky nikdo nekontroloval :)

Kombinaci nástrojů z titulku používáme v DameJidlo.cz i Rohlik.cz a budu ji rozhodně používat všude kde to půjde, pojďme si ukázat proč a jak :)

Pokračovat ve čtení ...

Kdyby/RabbitMq aneb asynchronní Kdyby/Events

Pokud právě čtete tento článek, tak jste jistě četli i Eventy a Nette Framework a pokud ne, tak doporučuji si na něj odskočit, já tu počkám.

Jak teď už všichni víme, Eventy jsou strašně silný nástroj, jenže i s Eventy se můžeme dostat do situace, že toho už sice nedělají příliš naše třídy, ale tentokrát celá aplikace. Takové poslání emailu, komunikace s platební bránou, poslání smsky, komunikace s externím pokladním API a kdo ví co ještě nějaký čas zabere.

Nebylo by fajn, kdyby se některé naše eventy zpracovaly asynchronně, na pozadí, abychom nezdržovali uživatele? Tak přesně od toho je RabbitMQ a tedy i Kdyby/RabbitMq.

Nebudu vás ale zatěžovat teorií, tu sepsal už Jakub Kohout i s přehledem několika šikovných nástrojů, které se vám budou hodit. Tak až si to přečtete, pojďme rovnou skočit rovnýma nohama do praxe.

Pokračovat ve čtení ...

The state of Kdyby 2014

Nedávno byla vydána verze Nette 2.2, tak si pojďme udělat rekapitulaci jak na tom je Kdyby.

Kdyby dělám především pro sebe, proto vyvýjím předně věci, které sám používám a když něco nepoužívám, tak to nevyvíjím. Na druhou stranu bych nerad, aby takové projekty vyloženě umíraly.

Pozor! Obsah je aktuální k 19.6.2014 a nebudu se vracet a tento článek doplňovat.

Pokračovat ve čtení ...

Poslední sobota a errata k přednášce Kdyby/Redis

Na Pražské březnové posobotě jsem měl přednášku o Kdyby/Redis, zde jsou slajdy s komentáři:

Kamera byla, takže až to Patrik zpracuje, bude i video

Proč errata?

Přednášku jsem si jako vždy připravoval den předem a během přípravy jsem se rozhodl udělat nějaké jednoduché benchmarky. Naprosto mi ale padla čelist, když jsem zjistil že filesystem mám na localhostu rychlejší než RedisStorage. Pojal jsem to statečně a rozhodl se vyzvat publikum, aby mi to pomohlo vyřešit.

Po přednášce jsme měli plodnou diskuzi a kluci se mi vysmáli, že ukládám cache doctrine metadat a annotací do Redisu. Protože používáme nejnovější stable PHP (tedy 5.5.něco) žil jsem v mylné představě že APC je mrtvé a tedy nad tím nemusím vůbec přemýšlet. Jenže! Ono není tak úplně mrtvé a nepoužitelné jak jsem si myslel.

Ještě během Davidovy přednášky jsem nainstaloval APCu, tedy uživatelskou cache z APC (ta část která neztratila smysl existence) a vylepšil Kdyby/Annotations aby na nich šla lépe konfigurovat cache.

Nakonec jsem tedy cache annotací a metadat přesměroval do apcu a místo ~10000 (slovy: deseti tisíců) requestů na prvnotní inicializaci stránky s kompilací containeru a načítání doctrine metadata jsem se dostal na špičkových ~200 requestů do Redisu. Na průměrnou stránku, která už má vygenerovanou cache mi požadavky z původních minimálně 200 spadly na ~80.

Vytížení Redisu víc než o polovinu padlo, aplikace se nepatrně zrychlila a zatím se ani jednou nezasekla na generování cache (což byl předtím problém). Strašák shardování se odkládá na neurčito :)

*PS: Juzno, dlužíš mi ještě to vysvětlení, jak udělat konzistentní hashování klíčů do shardování, které nebude potřeba přehashovávat ani při přidání dalších instancí. Teď to dělám takto

Takže ještě jednou závěr

Nad použitím session storage z Kdyby/Redis není třeba vůbec přemýšlet a prostě ji použijte, vyplatí se vždy. A ikdyž je cache malinko pomalejší než jsem doufal (požadavky do 0,3ms na request včetně overheadu mého storage), pořád je brutálně rychlá oproti filesystému pod zátěží.

Pokračovat ve čtení ...

Aop v Nette Frameworku

Znáte termín Aspektově orientované programování Stejně jako u “Kdyby/Events”:/blog/eventy-a-nette-framework, pointou je rozbít systém na menší logické celky, ovšem každý přístup to dělá maličko jinak.

Hranice mezi Eventy a AOP je strašlivě tenká a rozhodnout se který přístup v konkrétním případě použít nemusí být vůbec lehké. A aby to náhodou nebylo moc jednoduché, tak Eventy jsou teoreticky nahraditelné AOPčkem, ale naopak to nejde.

AOP má simulovat skládání různých chování (behaviour) do jednoho objektu bez mnohonásobné dědičnosti z venku, aniž by o tom tento objekt věděl. Kdežto událostí je si sám vědom, protože to on je vyvolává, ale už neví o listenerech, které na ně naslouchají.

Pokračovat ve čtení ...

NewRelic: monitoring aplikace na Nette Frameworku

Má Vaše aplikace víc než pět návštěv denně? Pak není od věci nějakým způsobem monitorovat, co se děje. Za tímhle účelem vznikají nejrůznější placené i opensource řešení. Některé lepší, některé horší. Několik měsíců zpátky jsem řešil, jaký monitoring nasadím na svoje sexy VPS od Wedosu (na kterém běží i tento blog).

Nebudu to protahovat, zvolil jsem nakonec NewRelic, který mi poradil Honza Doleček. Jeho jediné mínus je, že je docela drahý. Ale po měsíci trial verze a tričku zdarma už se mi nechtělo nikam migrovat.

Pak jsem dlouho monitoring neřešil a teď máme NewRelic i v Damejidlo.cz. Na své VPS mám pár malých webíků, ale tady už začíná být kritické, mít vše pod dohledem.

Před pár dny jsem objevil killer feature NewRelicu a o tu bych se s Vámi chtěl zde podělit. Je to jeho PHP API. Abych to trošku rozvedl, NewRelic se instaluje tak, že do phpčka zavedete modul a nastavíte IDčko aplikace. V ten moment začne rozsíření odesílat data na jejich servery a já se můžu kochat krásnými grafy :)

newrelic-overview

A to prý je Doctrine2 pomalá ;)

Stejným způsobem, prostou instalací balíku, jde rozchodit i monitoring systému.

newrelic-pizza-overview

Náš dev server se moc nenadře :)

S chytrými frameworky je ale problém..

a určitě Vám hned dojde jaký, při pohledu na tento screenshot.

newrelic-index

NewRelic nerozlišuje adresy, protože všechno jde na index.php

Další “problém” je, že Nette Framework sám řeši všechny chyby a výjimky a NewRelic se tak vůbec v tomto nedostane ke slovu. Když tedy server začne spamovat logy laděnkama, dozvím se to až když mi přijde email, který ani navíc přijít nemusí.

A tady přichází k řeči PHP API, na které mě též upozornil Honza. Chtěl jsem to mít cool, tak jsem řešení založil “na svém Event systému”:/blog/eventy-a-nette-framework.

Tohle API pokrývá rozlišení jednotlivých requestů, logování errorů a výjimek a umí taky označit požadavek jako “proces na pozadí”((background job)). Což není špatné rozlišit, protože vydatně používáme CLI scripty přes Symfony Consoli, pouštené cronem a nechceme, aby se nám pletly do frontend requestů. Byť snad všechny zatím roztřídil správně, jistota je jistota :)

Kompletní řešení tedy vypadá takto

namespace NewRelic;

use Kdyby;
use Nette;
use Nette\Application\Application;
use Nette\Application\Request;
use Nette\Diagnostics\Debugger;

class NewRelicProfilingListener extends Nette\Object implements Kdyby\Events\Subscriber
{
    public function getSubscribedEvents()
    {
        return array(
            'Nette\\Application\\Application::onStartup',
            'Nette\\Application\\Application::onRequest',
            'Nette\\Application\\Application::onError'
        );
    }

    public function onStartup(Application $app)
    {
        if (!extension_loaded('newrelic')) {
            return;
        }

        // registrace vlastního loggeru na errory
        Debugger::$logger = new Logger;
        Debugger::$logger->directory =& Debugger::$logDirectory;
        Debugger::$logger->email =& Debugger::$email;
    }

    public function onRequest(Application $app, Request $request)
    {
        if (!extension_loaded('newrelic')) {
            return;
        }

        if (PHP_SAPI === 'cli') {
            // uložit v čitelném formátu
            newrelic_name_transaction('$ ' . basename($_SERVER['argv'][0]) . ' ' . implode(' ', array_slice($_SERVER['argv'], 1)));

            // označit jako proces na pozadí
            newrelic_background_job(TRUE);

            return;
        }

        // pojmenování požadavku podle presenteru a akce
        $params = $request->getParameters();
        newrelic_name_transaction($request->getPresenterName() . (isset($params['action']) ? ':' . $params['action'] : ''));
    }

    public function onError(Application $app, \Exception $e)
    {
        if (!extension_loaded('newrelic')) {
            return;
        }

        if ($e instanceof Nette\Application\BadRequestException) {
            return; // skip
        }

        // logovat pouze výjimky, které se dostanou až k uživateli jako chyba 500
        newrelic_notice_error($e->getMessage(), $e);
    }
}

A ještě Logger

namespace NewRelic;

use Nette;

class Logger extends Nette\Diagnostics\Logger
{
    public function log($message, $priority = self::INFO)
    {
        $res = parent::log($message, $priority);

        // pouze zprávy, které jsou označené jako chyby
        if ($priority === self::ERROR || $priority === self::CRITICAL) {
            if (is_array($message)) {
                $message = implode(' ', $message);
            }
            newrelic_notice_error($message);
        }

        return $res;
    }
}

Pokud máte v aplikaci Kdyby/Events, tak je rozběhání listeneru otázkou tří řádků konfigurace

services:
    newRelicListener:
        class: NewRelic\NewRelicProfilingListener
        tag: [kdyby.subscriber]

Co se týče logování chyb, má NewRelic do laděnky ještě světelné míle daleko. To co tam je teď, připomíná spíše brášku log/error.log. Pro laděnky si tedy stále musím dojít do logu na server. Je ale super vidět prolnutí chybovosti vzhledem k počtu požadavků. Určitě tedy stojí za to, posílat chyby do NewRelicu.

Na druhou stranu, chudý rozbor chyb je vynahrazený luxusním profilerem, který automaticky loguje requesty, které trvají déle než by měly a velice přesně, až možná doterně, upozorňuje na úzká hrdla aplikace.

Výsledek

Nyní už uvidím jednotlivé requesty podle presenteru a akce

newrelic-presenters

Requesty seskupené podle presenterů

stejně tak procesy, které probíhají na pozadí

newrelic-background-jobs

Procesy na pozadí

a také všechny chyby, které se v aplikaci vyskytnou.

newrelic-errors

Procento chyb vzhledem k requestům

Naštěstí jich tam moc není :)

Za mě můžu NewRelic jedině doporučit. U větších aplikací je to must have. Jak monitorujete svoje aplikace vy?

Pokračovat ve čtení ...

Eventy a Nette Framework

Vyčleňuji právě svoji integraci Doctrine do Nette Frameworku a jedna její část řeší údálosti.

Doctrine má na události jednoduchý systém - existuje třída EventManager, do které se registrují listenery a když se “něco stane”, vyvoláme nad ní událost a ta se předá příslušným listenerům. Pro detaily si můžete odskočit do podrobné dokumentace.

Nette Framework má také události. Používáte je nejspíše každý den ve formulářích, když nastavujete $form->onSuccess[] = $callback;.

A mě napadlo: co kdybych to sjednotil?

(Pro plné pochopení článku je nutné znát použití obou systémů, tak si to skočte přečíst, já tu počkám)

Pokračovat ve čtení ...

Nette Framework: kanonizace utm parametrů


POZOR: IE9 a Safari 5+ zahazují fragment při opakovaném redirectu. Takže bohužel tato technika není 100% spolehlivá.


Na diskuse.jakpsatweb.cz se objevil zajímavý dotaz - jak přesměrovat request s utm_ parametry na request bez nich a zároveň je přesunout do fragmentu

Důvod je jednoduchý, pokud vám budou na web směřovat odkazy s utm_ parametry i bez nich, vyhledávače by si mohly myslet, že máte na webu duplicitní obsah, protože je přístupný přes více různých adres. Takový vyhledávač by se mohl nezaujatému pozorovateli jevit velice stupidní, protože “každý přece ví”, že utm_ parametry jsou jen na analýzu návštěvnosti. Ale jistota je jistota.

Prý to jde udělat jednoduchým regulárem v .htaccess, ale co my, co z mod_rewrite máme opruzeniny? My si napíšeme helper

class HttpHelpers extends Nette\Object
{
    public static function utmCanonicalize(Nette\Http\Request $httpRequest, Nette\Http\Response $httpResponse)
    {
        if ($httpRequest->isAjax() || (!$httpRequest->isMethod('GET') && !$httpRequest->isMethod('HEAD'))) {
            return;
        }

        $utm = array();
        foreach ($params = $httpRequest->getQuery() as $name => $value) {
            if (substr($name, 0, 4) === 'utm_') {
                unset($params[$name]);
                $utm[$name] = $value;
            }
        }

        if ($utm) {
            $url = clone $httpRequest->getUrl();
            $url->setQuery($params);
            $url->setFragment(http_build_query($utm));
            $httpResponse->redirect($url, Nette\Http\IResponse::S301_MOVED_PERMANENTLY);
            exit(0);
        }
    }
}

Asi úplně nejčistější místo pro takový kód v aplikaci by bylo BasePresenter::canonicalize(), ale to už máme za sebou injectování závislostí i akci - zbytečná režie kvůli takové “hlouposti”. Chtělo by to trochu dříve. Přidáme ho tedy do app/bootstrap.php

// ...
$container = $configurator->createContainer();
HttpHelpers::utmCanonicalize($container->httpRequest, $container->httpResponse);
// ...

Nyní si zkuste otevřít tuto adresu

/blog/nette-framework-kanonizace-utm-parametru?utm_source=self&utm_medium=experiment”:/blog/nette-framework-kanonizace-utm-parametru?utm_source=self&utm_medium=experiment

A uvidíte kód v akci :)

Pokračovat ve čtení ...

Píšeme rozšíření (compileru) pro Nette Framework

Napsali jste užitečný kus kódu, o který se chcete podělit s Nette komunitou? Výborně! Ukážeme si, jak usnadnit použití takového kódu ostatním.

Vaše rozsíření nejspíše bude obsahovat nějaké užitečné třídy a tyto třídy bude chtít programátor používat v presenteru, nebo jimi třeba nahradit výchozí služby. Například já jsem psal rozsíření pro Redis. Toto rozšíření obsahuje třídu Redis klienta RedisClient, úložiště pro cache RedisStorage, žurnál RedisJournal a úložiště pro session RedisSessionHandler.

Třídy RedisStorage, RedisJournal a RedisSessionHandler vyžadují ke své funkčnosti instanci RedisClient. Jejich registrace do DIC pomocí configu by vypadala takto:

common:
    services:
        redisClient: RedisClient()
        nette.cacheJournal: RedisJournal(@redisClient)
        cacheStorage: RedisStorage(@redisClient, @nette.cacheJournal)

        session:
            setup:
                - setStorage(RedisSessionHandler(@redisClient))

Asi by ale nebylo ideální, kdyby si kvůli použití rozšíření musel programátor plnit config balastem, který tam být nemusí.

Řešit se to dá velice elegantně napsáním vlastního rozsíření compileru. Takové rožšíření vlastně bude jen registrovat služby stejně jako neon config, ale bude to dělat pomocí metod třídy ContainerBuilder. Je to sice výrazně ukecanější než čistý neon config, ale získáme tím ohromnou flexibilitu.

Úplně stupidní přepis do CompilerExtension by vypadal takto

class RedisExtension extends Nette\DI\CompilerExtension
{
    public function loadConfiguration()
    {
        $builder = $this->getContainerBuilder();

        // metoda prefix dá před název služby název rozsíření
        // v tomto případě tak vznikne služba s názvem `redis.client`
        $builder->addDefinition($this->prefix('client'))
            ->setClass('RedisClient');

        $builder->removeDefinition('nette.cacheJournal');
        $builder->addDefinition('nette.cacheJournal')
            ->setClass('RedisJournal');

        $builder->removeDefinition('cacheStorage');
        $builder->addDefinition('cacheStorage')
            ->setClass('RedisStorage');

        $builder->getDefinition('session')
            ->addSetup('setStorage', array(
                new Nette\DI\Statement('RedisSessionHandler')
            ));
    }
}

Takové rozšíření se pak zaregistruje v app/bootstrap.php

$configurator->onCompile[] = function (Configurator $config, Compiler $compiler) {
    $compiler->addExtension('redis', new RedisExtension());
};

Nyní se rozšíření dá rozumně používat, ale šlo by to udělat lépe. Například budeme chtít změnit port, nebo adresu, kde Redis běží.

Každé registrované rozšíření, získá vlastní sekci v configu. Všimněte si jména, jaké jsem použil v příkladu nahoře, v metodě addExtension().

production:
    redis:
        host: 127.0.0.1
        port: 6379
        timeout: 10
        database: 0

Všechno, co pod stejnou sekcí napíši v configu, získám v rozšíření pomocí metody getConfig(). Nejčastější chyba je, zanořovat sekci redis do services, nebo do parameters, na to si dejte pozor!

class RedisExtension extends Nette\DI\CompilerExtension
{
    public function loadConfiguration()
    {
        $builder = $this->getContainerBuilder();
        $config = $this->getConfig();

        $builder->addDefinition($this->prefix('client'))
            ->setClass('Kdyby\Redis\RedisClient', array(
                'host' => $config['host'],
                'port' => $config['port'],
                'database' => $config['database'],
                'timeout' => $config['timeout']
            ));

Dále bych taky chtěl mít nějaké rozumné výchozí hodnoty, s tím nám také pomůže metoda getConfig()

$config = $this->getConfig(array(
    'host' => 'localhost',
    'port' => 6379,
    'timeout' => 10,
    'database' => 0
));

Nyní, když některé, nebo klidně všechny parametry v sekci redis v configu vynechám, tak se použijí výchozí hodnoty.

Další cukrlátko by mohlo být vytvoření funkce register(), která by nám ušetřila psaní v app/bootstrap.php.

Místo

$configurator->onCompile[] = function (Configurator $config, Compiler $compiler) {
    $compiler->addExtension('redis', new RedisExtension());
};

vytvoříme statickou funkci, která nám zkrátí zápis na prosté

RedisExtension::register($configurator);

Hotové a funkční rozšíření compileru pro Redis, si můžete prohlédnout zde.

No a kdybychom chtěli například RedisClient použít v presenteru, tak použijeme oblíbené inject*() metody.

class MyPresenter extends BasePresenter
{
    /** @var RedisClient */
    private $redisClient;

    public function injectRedis(RedisClient $client)
    {
        $this->redisClient = $client;
    }

    public function actionDefault()
    {
        $this->redisClient->get(...);
    }
}

Update 2014-04-29: Pozor, nově v Nette jdou registrovat rozšíření i přímo v configu, což je efektivnější, protože se pak nemusejí vůbec načítat na každý request, ale pouze když se aplikace kompiluje. Pomocná funkce register pak není vůbec potřeba.

extensions:
    redis: Kdyby\Redis\DI\RedisExtension

Pokračovat ve čtení ...

Nette Framework: SMTP od Google Apps

Skoro každá aplikace potřebuje odesílat emaily. Určitě se vám už stalo, že emaily nedorazily tak, jak by měly. Emaily odeslané pomocé funkce mail() v PHP totiž dost často neprojdou agresivnými spamovými filtry.

Pokud nemáte zdroje nastavovat si kvalitně “SMTP server”((to je to, co ty emaily reálně odesílá)), nebo ten co používáte není úplně vyhovující, stojí za zvážení Google Apps. Je možné mít k jedné doméně až 10 schránek zdarma. Nám bude stačit pro začátek jedna.

Nejprve je nutné si vytvořit účet pro doménu, kde máme web a ze které budeme odesílat emaily. Po vyplnění formuláře budete muset potvrdit vlastnictví domény.

Dalším krokem bývá nastavení MX záznamů domény, abychom emaily mohli také přijímat.

google-apps-mx

(aktuální MX záznamy jdou zkopírovat z průvodce v Google Apps)

Použití v Nette

Následujících pár řadků nám nastaví SMTP mailer

production:
    nette:
        mailer:
            smtp: true
            host: smtp.gmail.com
            secure: ssl
            username: no-reply@kdyby.org
            password: ****

a můžeme začít emaily odesílat

/**
 * @var Nette\Mail\IMailer
 */
private $mailer;

public function injectMailer(Nette\Mail\IMailer $mailer)
{
    $this->mailer = $mailer;
}

public function registrationFormSubmitted($form)
{
    // ...

    $message = new \Nette\Mail\Message();
    $message->setSubject('Registrace')
        ->setFrom('bot@kdyby.org')
        ->addTo($registrationEmail)
        ->setHtmlBody($registrationEmailTemplate);

    $this->mailer->send($message);
}

Pokračovat ve čtení ...

Přednáška: Roman Vykuka - Nastavení Nette Frameworku

V Developer Hubu nedávno přednášel Roman Vykuka na téma konfigurace Nette Frameworku. Na záznam se můžete podívat na YouTube

“Čistý vývoj”-nazi ve mně ovšem potřebuje opravit pár drobných nepřesností, kterých se Roman, ve své přednášce, dopustil. Slovíčkařit nebudeme, nervozita dělá svoje.

Tohle prosím Romane neber jako útok, ale jako návrhy na vylepšení. Kdybych u té přednášky byl, tak bych ti to řekl osobně, takto to nejde :)

Autowiring

Roman ukazoval Autowiring, na dost nešťastném příkladu. Do statické metody si předával celý DI Container.

class Authorizator extends Nette\Security\Permission
{
    // ...

    /**
     * @return Nette\Security\IAuthorizator
     */
    public static function create(Nette\DI\Container $container)
    {
        $cache = new Nette\Caching\Cache($container->cacheStorage);

        // pokud existuje authorizator v cachi, tak načíst
        if ($cache->load('authorizator') !== NULL) {
            return $cache->load('authorizator');
        }

        // jinak vytvořit novou a uložit do cache
        return $cache['authorizator'] = new static(
            $container->model->role->fetchAll(),
            $container->model->permission->fetchAll(),
            $container->model->resource->fetchAll()
        );
    }
}

Kód jsem maličko upravil, aby dával smysl. Neznám konkrétní implementaci, takže jsem to co nejvíce zjednodušil.

services:
    authorizator:
        factory: Nisa\Security\Authorizator::create()

Předávat si celý DI Container je omluvitelné pouze ve dvou případěch

  • pokud se rozhodnete, že v presenterech budete používat $this->context, což nezáří čistotou, ale je to velice praktické
  • pokud hackujete DI Container a optimalizujete skládání služeb, nebo něco souvisejícího

“Správně” by to mělo vypadat takto nějak

class Authorizator extends Nette\Security\Permission
{
    // ...

    /**
     * @return Nette\Security\IAuthorizator
     */
    public static function create(
        Nette\Caching\IStorage $cacheStorage,
        Nisa\Model\Roles $roles,
        Nisa\Model\Permissions $permissions,
        Nisa\Model\Resource $resources)
    {
        // ...
    }
}

Ještě bych dodal, že statické továrničky jsou jinak naprosto v pořádku.

Práce s kontejnerem v aplikaci

Kód z prvního slajdu bohužel fungovat nebude vůbec. Jsem pro zjednodušování v zájmu snadnějšího vysvětlení, ale tady jsme si to zjednodušili až moc.

class MyPresenter extends BasePresenter
{
    public function __construct(Nette\Mail\IMailer $mailer)
    {
        $this->mailer = $mailer;
        $articles = $this->context->model->articles;
    }

    protected function createComponentArticleEditor()
    {
        return $this->context->createArticleEditor('test');
    }
}

Použivání konstruktoru presenteru pro předávání závislostí je samozřejmě naprosto v pořádku. Jak říkal Roman, PresenterFactory sama zavolá metody injectPrimary nad presenterem a předá do ní DI Container. Problém je v tom, že technicky se volá až po konstruktoru. Není tedy možné sahat na $this->context a vytahovat z něj služby, pokud do presenteru ještě nebyl předán.

Komponenty v DIC

Roman také ukazuje, jak se dají vytvářet komponenty pomocí DIC. Stejné nadšení jsem při uvedení feature měl také. Jenomže používání továrniček v DIC přináší několik problémů, na které ale v jednoduché aplikaci většinou nenarazíte.

Asi nejhorší problém je, že takovéto komponenty se nedají moc pěkně skládat. Protože do presenteru se nedá injektnout továrnička, nedá se injektnout ani nikam jinam, ani do komponenty samotné. Jsme tedy odkázáni na používání $this->context v presenteru a $this->presenter->context v komponentách. V modelových třídách je to prakticky neřešitelné. Jediné co se dá udělat, tak předávat si celý DIC do modelových tříd. Ale z toho už jsme doufám vyrostli.

V současné době je jediným řešením si napsat factory nebo builder třídu, která bude vytvářet konkrétní typ komponenty. Nepovažuji to za problém, protože se to dá celkem jednoduchým způsobem vylepšit. Roman tohle téma zmiňoval, ale nevysvětlil proč je to problém.

Dynamické načítání konfigů

Používat NeonAdapter na načítání neon souborů je trochu zbytečné. Pokud v modules.neon nepoužíváš sekce, tak ti stačí základní Nette\Utils\Neon::decode().

Také bych zvážil přesunutí logiky načítání konfiguračních souborů modulů, do CompilerExtension. Třeba do toho tvého základního pro CMS.

Otázky na konec

Co se týče používání $this->context v presenteru, tak jak jsem psal výše, je to pohodlné a neodsuzuju to. Ale z hlediska čistoty bych raději používal inject*() metody.

Závěrem

Koukni na Composer, pomůže ti se skládáním závislostí modulů ;)

Díky za přednášku, kde ti mám dát follow?

Pokračovat ve čtení ...

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.

Pokračovat ve čtení ...