Jak v Nette 3.0 použít BasePresenter?

nettolog
Člen | 68
+
0
-

Prosím o radu.
V dokumentaci pro verzi 2.1 se zmiňují helpery: https://latte.nette.org/cs/#…
které se mají vkládat do metody createTemplate().

To ale v Nette 3.0 bohužel nefunguje:

Declaration of App\Presenters\BasePresenter::createTemplate($class = NULL) must be compatible with Nette\Application\UI\Control::createTemplate(): Nette\Application\UI\ITemplate

Dokumentace pro 3.0 se o tomto vůbec nezmiňuje. Jak mohu toto zprovoznit v Nette 3.0?
Už na to koukám 3 hodiny, ale vůbec nevím, kde může být problém.

Polki
Člen | 553
+
+1
-

Možná bych trochu zrefaktoroval a sáhl po Funkcích nebo Filtrech

Editoval Polki (20. 4. 2020 23:55)

nettolog
Člen | 68
+
+1
-

Díky, to je rozhodně v plánu…
Já jsem trochu ztroskotal už na té metodě createTemplate. Ale už jsem to rozchodil, nově je potřeba ITemplateFactory.

abstract class BasePresenter extends Nette\Application\UI\Control
{
	/** @var Nette\Application\UI\ITemplateFactory */
	private $templateFactory;

	protected function createTemplate(): Nette\Application\UI\ITemplate
	{
		$template = $this->templateFactory->createTemplate();
		return $template;
	}
}

Editoval nettolog (20. 4. 2020 23:58)

Polki
Člen | 553
+
+1
-

Metodu createTemplate vůbec nemusíš řešit :) Tvůj kód by mohl vypadat nějak takto:

declare(strict_types=1);

namespace App\Presenters;

use Nette\Application\UI\Presenter;

abstract class BasePresenter extends Presenter {


    protected function startup(): void {
        parent::startup();

        $this->template->addFilter('name', [$this, 'filter']);
        // nebo
        $this->template->addFunction('name', [$this, 'funct']);
    }

    public function filter($param) {
        return $param . ' paramFiltered';
    }

    public function funct($param) {
        return $param . ' Function done';
    }
}

Editoval Polki (21. 4. 2020 0:07)

CZechBoY
Člen | 3608
+
+1
-

No registrace v createTemplate metodě má asi jedinou výhodu: registrace probíhá lazy až když je potřeba template.
V kodu co poslal @Polki se nejspíš template vytváří vždy, ikdyž by nebyl potřeba.

@nettolog Ta hláška co si poslal je jen změna signature metody, kdy se přidal nativní return typehint, ale to už si vyřešil sám a správně.

petr.pavel
Člen | 533
+
0
-

Nešlo by definovat si vlastní třídu, která implementuje ITemplateFactory a která registruje všechny filtry atp.? Čekal bych, že když ji zaregistruješ jako službu, Nette bude všude automaticky používat ji. Tím zajistíš, že budeš mít stejně vybavenou template, ať už pro presenter nebo pro komponentu či e-maily.

Polki
Člen | 553
+
0
-

@CZechBoY Pravda, pokud chceš mít vše lazy, tak přepsat metodu createTemplate je asi nejlepší řešení. Lidi to třeba ještě dělají v beforeRender aby to bylo až těsně před, jelikož když se dojde až na render metodu, tak by teoreticky mělo nastat pouze vykreslení. Pořád je ale nejčistější createTemplate jen jsem chtěl nastínit, že není třeba.

@petrpavel Jasně, že jde. Jenom si představ, co by si uvnitř musel ošetřovat a registrovat za defaultní filtry apod. Lepší by bylo podědit Nette\Bridges\ApplicationLatte\TemplateFactory a v ní přepsat tu metodu createTemplate takže by třída vypadala takto:

namespace App\Model;

use Nette\Bridges\ApplicationLatte\Template;
use Nette\Bridges\ApplicationLatte\TemplateFactory;
use Nette\Application\UI\Control;
use Nette\Application\UI\ITemplate;

class MyTemplateFactory extends TemplateFactory
{
    public function createTemplate(Control $control = null): ITemplate {
        /** @var Template $template */
        $template = parent::createTemplate($control);
        $template->addFilter('filter', [$this, 'filter']);
        // nebo
        $template->addFunction('funct', [$this, 'funct']);
        return $template;
    }

    public function filter($param) {
        return $param . ' paramFiltered';
    }

    public function funct($param) {
        return $param . ' Function done';
    }
}

a v configu by byla zaregistrovaná takto:

services:
	latte.templateFactory: App\Model\MyTemplateFactory

Problém tohoto řešení je, že nechceš vždy všude registrovat všechny tyto filtry. Pro komponenty budeš mít třeba více filtrů, než pro presentery, protože když máš aplikaci dobře navrženou, tak se o vykreslování částí stránky starají převážně komponenty a presentery jsou jen takový prostředník, který ví, kterou komponentu kdy vykreslit. V takovém případě nechceš zbytečně registrovat filtry a funkce do presenterů, když to není třeba a ani je nevyužiješ. Takže toto řešení by se mělo používat jen v případech, kdy si si jistý, že danou funkci/filter na 100% využiješ všude.

EDIT Stále chápu, že je otrava registrovat každou funkri/filtr zvlášť v jednotlivých presenterech. Například v modulech, kde nechceš nějaký base presenter společný pro všechny moduly, ale funkcí máš mraky. Na to vznikl Univerzální filter Tedy nadefinuješ ty filtry v jedné třídě a pak tam, kde tuto skupinu filtrů potřebuješ zaregistruješ v metodě createTemplate pouze tuto skupinu filtrů.

Editoval Polki (21. 4. 2020 11:23)

petr.pavel
Člen | 533
+
0
-

Polki napsal(a):

Lepší by bylo podědit Nette\Bridges\ApplicationLatte\TemplateFactory a v ní přepsat tu metodu createTemplate takže by třída vypadala takto:

Přesně. Pro jednoduché případy ani nemusíš definovat pojmenované metody, stačí anonymní fce:

<?php
$template->addFunction('funct', function($param) {
  return $param . ' Function done';
});
?>

Problém tohoto řešení je, že nechceš vždy všude registrovat všechny tyto filtry.
EDIT Stále chápu, že je otrava registrovat každou funkri/filtr zvlášť v jednotlivých presenterech. …Univerzální filter … zaregistruješ v metodě createTemplate pouze tuto skupinu filtrů.

Souhlas. Pokud chceš mít z nějakého důvodu registraci filtrů specifickou pro části aplikace (moduly nebo komponenty), třeba protože je apka univerzální a různí zákazníci mají nainstalované různé části. Každopádně správné místo je createTemplate a ne startup (volá se vždycky) ani beforeRender (dělení odpovědnosti).

Jinak nevidím problém v tom registrovat vždycky všechny – vykonává se jen registrace (např. toho callbacku na univerzální filtr). A získáš tím přehlednější kód, než kdybys na jednotlivých místech, kde šablonu používáš, registroval jen to, co použiješ.

CZechBoY
Člen | 3608
+
+4
-

Ja treba pouzivam nejaky zakladni/obecny (napr. formatovani casu) filtry tak, ze je registruju v konfiguraci. Potom pridavam specificky filtry v presenterrch/komponentach v metode createTemplate.

Polki
Člen | 553
+
0
-

@petrpavel Z praxe anonymní funkce = zlo
Vysvětlení:

  1. Znemožňuje znovupoužitelnost
  2. Nedají se testovat (nebo velmi obtížně)
  3. Razantně snižují čitelnost.
  4. Následné úpravy funkčností jsou složité (nelze funkci ve změti vyhledat podle jména a při vnitřních úpravách je potřeba řešit uzávěry apod., čímž narůstá kód)
  5. Jsou pomalejší na výkon. (cca o 40%)

Obecně se anonymní funkce hodí maximálně tak na inline funkce (s jedním příkazem), které ještě ideálně hodnotu nijak extrémně nemění. A i tady bych si to asi rozmyslel.

@CZechBoY Ano tak by to mělo podle mě být ideálně.

F.Vesely
Člen | 367
+
0
-

@Polki muzes prosim dat odkaz na nejake info, kde bych videl, ze jsou anonymni funkce o cca 40% pomalejsi na vykon? To by me opravdu zajimalo.

Jinak rict takhle obecne, ze jsou anonymni funkce zlo je podle me mimo, vzdycky zalezi na pouziti. Nekde sice snizuji citelnost, ale nekde ji zase zvysuji. Napriklad kdyz pouzivam array_map, array_filter, atd.

Znovupouzitelne take jsou, kdyz si ji ulozis do promenne.

Editoval F.Vesely (21. 4. 2020 14:15)

nettolog
Člen | 68
+
0
-

Děkuju za cenné rady a tipy. Těch funkcí/filtrů mám docela dost a je to takový specifický projekt, kdy je používám napříč presentery, takže mi docela umístění v basepresenteru vyhovuje a je super, že je to lazy-loading.

Editoval nettolog (21. 4. 2020 22:33)

Polki
Člen | 553
+
-4
-

@FVesely Nemám moc čas hledat nějaké dopodrobna rozebrané články jak to funguje a proč to pomalejší je zkus pogooglit sám.
Toto je jeden z prvních odkazů jak relevantní je nevím nečetl jsem (2 odstavec).

Pokud se ti nechce hledat, nebo nevěříš zkus si spustit:

<?php

function profileTime(string $name, callable $callback): void
{
    $start = microtime(TRUE);

    for($i = 0; $i < 100000; $i++){
        $callback($i);
    }

    $end = microtime(TRUE);
    echo 'The code with ' . $name . ' function took ' . ($end - $start) . ' seconds to complete. <br />';
}

function mul2(int $x): int
{
    return $x * 2;
}

profileTime('anonymous', function (int $x): int
{
    return $x * 2;
});

profileTime('regular', 'mul2');

na tom to jde pěkně vidět.

Pokud jde o znovupoužitelnost tak proměnná ano, ale zase nastává problém předání funkce mezi funkcemi, mezi třídami a tak vůbec prostě můžeš, když ji uložíš do proměnné, zavolat vícekrát, ale pořád ji musíš uchovávat v proměnné, kterou si musíš předávat a IDE ti nenapoví například nad návratovým typem té anonymní funkce, takže nemůžeš(můžeš ale nenapoví ti) dělat například zřetězení, jak je tomu například u formulářových prvků…

Pokud jde o přehlednost, tak za mě jednoznačně 0. Třeba u těch polí. Když zavolám:

$result = array_map([$helper, 'trim'], $array);

Tak mi to přijde rozhodně přehlednější(hned vím, že se nad každým prvkem zavolá trim), než když napíšu třeba toto (kód pro trim převzat z oficiální Nette Strings.php knihovny):

array_map(function ($subject) {
    $charlist = preg_quote(" \t\n\r\0\x0B\u{A0}", '#');
    $replacement = '';
    $pattern = '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du';
    $func = 'preg_replace';
    $args = [$pattern, $replacement, $subject, -1];
    $messages = [
        0 => 'Internal error',
        1 => 'Backtrack limit was exhausted',
        2 => 'Recursion limit was exhausted',
        3 => 'Malformed UTF-8 data',
        4 => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point',
        6 => 'Failed due to limited JIT stack space', // PREG_JIT_STACKLIMIT_ERROR
    ];

    $res = Callback::invokeSafe($func, $args, function (string $message) use ($args): void {
        // compile-time error, not detectable by preg_last_error
        throw new Exception($message . ' in pattern: ' . implode(' or ', (array) $args[0]));
    });

    if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars
        && ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], true))
    ) {
        throw new Exception(($messages[$code] ?? 'Unknown error')
                . ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code);
    }
    return $res;
}, $array);

Prostě přehlednost na jednoduchý trim je tatam a pokud si nenapíšu komentář, co že ta anonymní funkce dělá, tak za půl roku budu úplně v pasti. Musel bych do detailu studovat, co ta funkce vlastně dělá.

A to se nezmiňuji o tom, že na polích u toho array_map se právě projeví ta vlastnost pomalosti to je ta nejrizikovější oblast.

Nicméně takové vědomosti jsou zlato, za které lidi dost platí. :) Během příštího měsíce budu o takových věcech (správný návrh kódu + optimalizace) dělat kurzy. Nechci sem však tahat reklamu, tak nenapíšu odkaz Sorry budeš muset hledat. :)

F.Vesely
Člen | 367
+
+6
-

@Polki Prave, ze jsem nebyl linej a hledal jsem, tak jsem se zeptal…

Vzal jsem tvuj kod, zvysil jsem pocet opakovani na 1000000, pridal jsem tam jeste tridu s normalni i statickou metodou a pustil ho na http://sandbox.onlinephpfunctions.com/…075a9387bebe, abych mohl vyzkouset ruzne verze PHP. Tu tridu jsem tam pridal z toho duvodu, ze to tak pouzivas v kodu s array_map.

PHP 7.4
anonymous: 0.049726009368896
  regular: 0.066621065139771
class pub: 0.085750102996826
class sta: 0.12086892127991
PHP 7.3.12
anonymous: 0.052736043930054
  regular: 0.062775850296021
class pub: 0.086485862731934
class sta: 0.12645602226257
PHP 7.2.25
anonymous: 0.062008857727051
  regular: 0.075112104415894
class pub: 0.10055708885193
class sta: 0.13450694084167
PHP 7.1.33
anonymous: 0.065821170806885
  regular: 0.086335897445679
class pub: 0.10121202468872
class sta: 0.13502097129822

Ano, tvuj priklad v 1 z 6 pripadu daval regular rychleji nez anonymous, ale pri tak male namerene hodnote to neni nijak vypovidajici. Navic jsi meril neco, co ani v kodu nepouzivas. Ty pouzivas metodu v tride a ne globalni funkci. Jsem docela rad, ze tam neni to zpomaleni o cca 40% , protoze v Nette knihovnach se anonymni funkce dost pouzivaji a 40% by byl fakt turbo boost. :)

Ohledne znovupouzitelnosti jsem reagoval hlavne na tve Znemožňuje znovupoužitelnost, coz neni pravda, daji se znovupouzit. Samozrejme je blbost je pouzivat tak, jak jsi popsal. Opet plati, ze zalezi na pouziti. V ramci jedne metody mi IDE v pohode napovida.

Co se tyce prehlednosti, psal jsem, ze zalezi na pouziti, ze nekde ji snizuji a nekde zase zvysuji. Tos nijak v podtaz nebral a proste jsi vzal uplnej extrem. Coz samozrejme umim taky vzit extrem z druhe strany $ids = array_map(fn($post) => $post->id, $posts);. Tohle mi rozhodne prijde citelnejsi, nez mit nekde metodu a muset se do ni proklikavat, abych ji videl ci upravil.

Dekuji za upozorneni na kurz, kdyz budu mit cas, tak se prihlasim. Zkus zvazit, jestli opravdu budes zastavat tak extremni nazory ohledne anonymnich funkci (jsou zlo, znemoznujou znovupouzitelnost, razantne snizuji citelnost, jsou o cca 40% pomalejsi) a nedas jim aspon malou sanci ve tvem kodu. :)

Editoval F.Vesely (22. 4. 2020 1:11)

Polki
Člen | 553
+
0
-

@FVesely Momentálně mám svůj laptop v servisu a jako náhradní jsem dostal nějakou šunku, která má výkon jako lenochod, takže mi takové malé opakování stačilo.

Tvůj kód výše jsem pustil na svém stroji 10×, udělal průměr a hodnoty, které mi vyšly jsou zde:

PHP 7.3.12
anonymous: 1.9683794021606
  regular: 1.1340770721436
class pub: 1.2802527189255
class sta: 1.279154920578

Na takové měření moc nelze používat online emulátory. Kdo ví, jak jsou uvnitř dělané a výsledek pak je zkreslený. Zkoušel jsem to spustit i na produkčním serveru a tam jsou hodnoty podobné, akorát o řád nižší. Nepřu se, že s určitými optimalizacemi na straně PHP by mohly výsledky dopadnout jinak, to určitě mohly.

Psal jsem taky, cituji: Obecně se anonymní funkce hodí maximálně tak na inline funkce (s jedním příkazem), které ještě ideálně hodnotu nijak extrémně nemění. A i tady bych si to asi rozmyslel.
Tedy tvůj lambda výraz je ještě v pohodě. V praxi jsem se ovšem s takovým použitím setkal poskromnu a lidi tam cpou 150 řádkové metody… A pokud jde o příklad, jak jsi psal, tak při takovém použití se zase setkávám často s tím, že v kódu je něco jako: $post_ids = array_map(fn($post) => $post->id, $posts);, jinde $ord_ids = array_map(fn($orders) => $order->id, $orders); dále $book_ids = array_map(fn($book) => $book->id, $books); atd.. To je ještě v poho, i když očividně nějaký DRY princip těmto lidem nic neříká. No problém nastane ve chvíli, kdy se zjistí, že nově nechtějí mít jako identifikátor sloupeček id, ale UUID ale zároveň id sloupec chtějí v databázi nechat (například kvůli ušetření paměti ve vazbách atd..)
V tu chvíli přichází na řadu dlouhý refaktoring a lidi se bijí do hlavy, proč jsem to neudělal hned na začátku jinak. Mluvím z 14 let dlouhé praxe nevymýšlím si. Takových případů je mraky. Prostě se nám osvědčilo anonymní funkce vůbec nepoužívat a vyřešilo se tím spousta věcí.

To jsou taky důvody, proč jsme schopní srazit čas výkonu skriptu na výpis jednoduché statistiky nad 13000 uživateli z 10s na 23ms.

Krásný příklad na přehlednost je například tento:

$music()->
   filter(function ($value, $key) {
     return (strtolower($key) == 'music type' && strtolower($value) == 'jazz')
   })->
   pluck('title')->
   sortBy('title')->
   each(function($song) {
     return ucfirst(strtolower($song['title']));
   });

předělaný na

$music()->
   filter(jazzonly())->
   pluck('title')->
   sortBy('title')->
   each(casefix());

Opět. nahoře jsou inline funkce, které se dají zapsat tvým způsobem, ale čitelnost, nebo zjistit co dělají? 0. Nehledě na testování, nebo nedej bůh úpravy. Zatímco druhý příklad je jasný a výřečný. Není zde nic navíc, co by rušilo kód a hlavně, podle názvu funkce hned víš, co se děje. – převzato z článku zde.

Věřím, že existuje jasný příklad, kde se použít dají a měly by se. My však takový příklad, kde by bylo na 100% jasné, že to je v pohodě ještě nenašli. To by sis musel být jo sakra jistý, že ti sedí právě tam a nechceš ji jinde, aby si nemusel složitě předávat proměnné a to mi trochu připomíná Davidovu pohádku o singletonu

Editoval Polki (22. 4. 2020 9:37)

F.Vesely
Člen | 367
+
+1
-

@Polki Tak ja nevim, ted rano jsem to pustil u sebe na notasu a 2 produkcnich serverech a davalo to moje vysledky. :)))

Asi uz to tady nema cenu rozebirat, zbytecne bychom se oba dva opakovali a presvedcovali se tu navzajem. Je mi jasne, ze ja anonymni funkce neprestanu a ty zase nezacnes pouzivat. Slo mi zde hlavne o to, aby si nekdo neprecetl tvuj komentar a nezacal rychle vsude prepisovat anonymni funkce, protoze na Nette foru cetl, ze jsou o cca 40% pomalejsi.

David Matějka
Moderator | 6445
+
0
-

@Polki tipuju, že si zapomněl vypnout xdebug :) benchmarky musíš spouštět s -n parametrem

Polki
Člen | 553
+
0
-

@DavidMatějka xdebug Nemám nainstalovaný. Tedy jsem jej nezapomněl vypnout. Schválně jsem si jej zkusil doinstalovat a výsledky jsou stejné.

Nedávno jsem dokonce narazil někde na stránkách https://wiki.php.net/ že používání anonymních funkcí a closures spotřebovává o 16% více systémových prostředků (myšleno tím, že se zavolá o 16% více instrukcí), než klasický callback z pojmenované funkce/metody. Měli to měřeno pomocí Valgrindu.

Kde jsem to tam viděl už bohužel nemám čas pohledat. No a jak psal Vesely. Toto tema je i o něčem úplně jiném. Kdo chce si to na určitých příkladech odzkouší sám. Problém v tom nevidím. Stejně by měl každý když si něco přečte si to nejdřív sám vyzkoušet a ne všemu slepě věřit.

David Matějka
Moderator | 6445
+
+2
-

@Polki asi jsem našel ten zdroj, který zmiňuješ – jen říká to, že Closures jsou o 16% méně náročné, nikoliv naopak https://wiki.php.net/…fromcallable a pro lepší výkon je tak lepší použít Closure::fromCallable

zkusil jsem to přidat do toho benchmarku, co zmiňuje @FVesely a dostalo se to pak na časy anonymní funkce

Polki
Člen | 553
+
0
-

@DavidMatějka Tak jsem se asi přehlédl poslední dobou je toho moc a nemám čas si věci zkoušet tak to mám v listu.

Nicméně zkusil jsem si ty closures přidat do toho kódu a časy jsou zde:

PHP 7.3.12
anonymous: 1.8734617948532
  regular: 1.2123475551605
class pub: 1.2271042823792
class sta: 1.2516534090042

Fakt netuším, kde dělám chybu :D
Nemáte na svých testovacích stanicích nějaký accelerator, nebo něco co optimalizuje kód?

Editoval Polki (22. 4. 2020 12:44)

David Matějka
Moderator | 6445
+
+1
-

@Polki netuším, co děláš špatně – zkoušel jsem to v několika prostředích a všude je volání closure rychlejší. zkusil si to spouštět s tím -n přepínačem? imho tam budeš mít nějakou extension, která to zpomaluje

David Grudl
Nette Core | 8080
+
+12
-

Pardon, že vstupuju do diskuse, fakt tu řešíte, že něco trvá nulanulanulanulajednaosm a něco jiného nulanulanulanulajednadva? Myslím, že jste právě touto diskusí zabili víc času, než trvalo vykonání všech closures za celou existenci tohoto webu :)

Používat PHP a diskutovat o výkonu je to stejné, jako být upoutaný na vozíku a probírat, ve kterých botech se líp zaběhne maraton.