Jak odlehčit presenter od závislostí – best practise

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Bogi
Člen | 24
+
+1
-

Můj presenter má příliš mnoho závislostí v konstruktoru, které jsou většinu času nepotřebné (např. Mailer, generátor faktur, třídy potřebné ve chvíli, kdy se změní stav něčeho apod.). Presenter má totiž na starost vykreslování datagridu s objednávkami a zároveň řeší všechny akce datagridu. Tento presenter je tak přetížený a projevuje se to v pomalé rychlosti načítání. Jaký by byl nejlepší způsob, jak takový kód refaktorovat?

Ukázka kódu pro představu:

<?php
class OverviewPresenter extends BasePresenter
{
    /** @var Model\OrderManager @inject*/
    public $orderManager;

    /** @var Model\AnotherManager @inject*/
    public $anotherManager;

    /** @var Model\Mailer @inject*/
    public $mailer;

    /** @var Model\InvoiceManager @inject*/
    public $invoiceManager;

    /* A dalsi zavislosti, ktere vetsinu casu nejsou potreba... */


    public function createComponentOverviewGrid()
    {
        $grid = new DataGrid($this);

        /* Definice sloupcu gridu... */
        ...

        $grid->addGroupAction('Vykonej akci 1')->onSelect[] = [$this, 'akce1'];
        ...
        $grid->addGroupAction('Vykonej akci 10')->onSelect[] = [$this, 'akce10'];
    }


    public function akce1()
    {
        $this->orderManager->doSomething();
        ...
    }

    ...

    public function akce10()
    {
        $this->mailer->sendSomePrettyMails();
        ...
    }
}

Problémem jsou všechny ty metody, které řeší všechny akce datagridu a nejspíš by neměly být všechny v jednom presenteru. Jak se takové věci dělají „správně“?

Tomáš Votruba
Moderator | 1114
+
+1
-

Co mě napadá z hlavy:

  1. Z best-practise bych začal používat constructor over @inject – tady si můžeš přečíst proč.
  2. U komponenty bych použil továrničku. To $this v konstruktoru vypadá na neplechu. Komponenta už v sobě presenter má by default. Zajímalo by mě, proč ho potřebuje.
  3. Co se výkonu týče, závislostmi to nejspíš nebude. Možná aktivitou v metodách – Mailer samotný bych vyčlenil do samostatného presenteru (MailingPresenter), protože půjde o nějakou časově náročnou akci, která se pustí na pozadí (v cronu, jednou za čas)
  4. Vždycky můžeš zkusit Blackfire – tak rychle najdeš úzké hrdlo :) Mě s ním pomohl @JanTvrdík a šlape to jak hodinky.
CZechBoY
Člen | 3608
+
+1
-

DataGrid konkretniho typu (napr. na objednavky) muzes vyclenit do dalsi tovarni tridy a zbavis se tak vytvareni komponenty v presenteru. Je to trosku na zamysleni, protoze ti ta tovarna bude jen k jednomu pouziti a neni znovupouzitelna.

Je tedy otazkou co je pomaly. Jestli ten bordel neni uz v BasePresenteru.
Posilani emailu nejspis nejakej cas zabere a mohl bys to resit treba tim odesilacim cronem.
Jak zjistis ktera akce ti konkretne dela problem tak zkus ten Blackfire, i v te verzi zadarmo je pouzitelny.

Bogi
Člen | 24
+
0
-

Díky vašim odpovědím jsem zjistil, že úzké hrdlo opravdu není velké množství závislostí, ale samotné vytvoření a vykreslení datagridu. Pokud to chápu správně, když vyčlením vytvoření datagridu do tovární třídy namísto presenteru, tak tím nedosáhnu zrychlení, ale dosáhnu přehlednějšího kódu.
Našel jsem jeden problém, a to, že služba Mailer a ještě některé další měly ve svých konstruktorech kromě inicializací proměnných ještě další operace, které tam být nemusely a byly výpočetně náročné. A pokud si to interpretuju správně, tak jelikož se jedná o služby, jejich konstruktory jsou volány vždy i v případě, že se služba nevyužije.

@TomášVotruba k Tvému bodu 2: Vlastně nevím, proč se datagridu předává v konstruktoru $this, viděl jsem to tak v příkladu použití, tak jsem to zkopíroval (jedná se o Ublaboo datagrid).

CZechBoY
Člen | 3608
+
0
-

Jen upresnim vytvareni sluzeb: sluzba se vytvori jen pokud ji nekdo vyzaduje – napr. aktualni presenter nebo nejaka jeho zavislost (jina sluzba).

duke
Člen | 650
+
0
-

Bogi napsal(a):

Díky vašim odpovědím jsem zjistil, že úzké hrdlo opravdu není velké množství závislostí, ale samotné vytvoření a vykreslení datagridu. Pokud to chápu správně, když vyčlením vytvoření datagridu do tovární třídy namísto presenteru, tak tím nedosáhnu zrychlení, ale dosáhnu přehlednějšího kódu.
Našel jsem jeden problém, a to, že služba Mailer a ještě některé další měly ve svých konstruktorech kromě inicializací proměnných ještě další operace, které tam být nemusely a byly výpočetně náročné. A pokud si to interpretuju správně, tak jelikož se jedná o služby, jejich konstruktory jsou volány vždy i v případě, že se služba nevyužije.

Pokud chceš vytváření služby udělat co nejvíce lazy, můžeš použít feature nette, která je obdobou generovaných továrniček, jen místo metody create použiješ metodu get. Já tomu říkám holdery, byť si nejsem jist, zda je to nejpřesnější název (má-li kdo lepší, ať doporučí). Např. máš službu Foo, kterou chceš instanciovat, jen když je to nutné, a tak si nadefinuješ holder:

interface IFooHolder
{
	/** @return Foo */
	public function get();
}

A ten zaregistruješ v config.neon:

services:
	- Whatever\Namespace\IFooHolder

Tam, kde chceš pak službu Foo líně používat, si vyžádáš jako závislost IFooHolder a pak s tím pracuješ takto:

	$foo = $this->fooHolder->get();
Bogi
Člen | 24
+
0
-

@CZechBoY Pravda, teď jsem si to vyzkoušel a opravdu se nevytváří služba, pokud není někde v rámci vyřízení aktuálního requestu uvedena v závislostech. To je super, už chápu proč tedy není problém mít v aplikaci klidně velké množství služeb, pokud jsou používány správně.

@duke O tom generování továrniček pomocí Nette už jsem četl, ale bohužel jsem ho nepoužíval. Ale ty holdery jsou super postup, začnu to tak dělat tam, kde to bude mít smysl. Děkuji!