Session v @layout počet zboží v košíku

mimacala
Člen | 113
+
0
-

Ahojte, prosím jak vykreslím obsah session v @layout latte ?
V presenteru je mi to jasné to si to spočtu a pošlu do renderu, ale jak to udělém v @layout ?
Chtěl bych zobrazovat počet položek v košíku, tedy v session.
Děkuji

m.brecher
Generous Backer | 736
+
+3
-

Ideální je plnit data pro layout v obecném abstraktním presenteru ze kterého finální presentery dědí. Na plnění layoutu nepoužít metodu render<Action>, nýbrž beforeRender(). Pozor, metody beforeRender() by měly volat beforeRender() parent presenteru, pro případ, že by se o úroveň výše také plnily nějaká data do layoutu.

use Nette\Application\UI\Presenter

abstract class BasePresenter extends Presenter
{
	.....

	public function beforeRender()
	{
		parent::beforeRender();		// není úplně nutné vždy, ale je dobré to tam mít
		$this->template->data = $data   // k dispozici v @layout.latte
	}
}

a finální presenter


final class MyPresenter extends BasePresenter
{
	.....

	public function renderDefault()
	{
		$this->template->actionData = $actionData   // k dispozici v šabloně akce
	}
}

Editoval m.brecher (13. 10. 2022 16:21)

mimacala
Člen | 113
+
0
-

Aha, prosím tedy,
asi jsem špatně napsal, mám @AppLayout pro aplikaci a @HomepageLayout po hlavnístranu s obchodem.

BasePresenter mi vloží $data do všech Layoutů nebo jen do hlavního @Layout, který nemám, protože používám výše zmíněné dva.
Děkuji

use Nette\Application\UI\Presenter

abstract class BasePresenter extends Presenter
{
	.....

	public function beforeRender()
	{
		parent::beforeRender();		// není úplně nutné vždy, ale je dobré to tam mít
		$this->template->data = $data   // k dispozici v @layout.latte
	}
}

m.brecher
Generous Backer | 736
+
+1
-

Ahoj, já víceméně všechny aplikace kde je veřejná část (Front) a administrace (Admin) stavím tak, že mám 3 layouty:

@layout.latte - obecný pro celý web

	@front-layout.latte - dědí z @layout.latte, určen pro Front část
	@admin-layout.latte - dědí z @layout.latte, určen pro Admin část

Layoutům odpovídají abstraktní presentery:

BasePresenter je na vrcholu - data do @layout.latte

	FrontPresenter extends BasePresenter - data pro @front-layout.latte
	AdminPresenter extends BasePresenter - data pro @admin-layout.latte

Když to postavím takhle, tak potom si můžu naplnit data do libovolného layoutu v odpovídajícím abstraktním presenteru.

Současně je vhodné i aplikaci rozčlenit na moduly Front a Admin a potom má každý modul svůj layout a svůj abstraktní společný presenter. Ale moduly není nutné mít, ale layouty a abstraktní presentery doporučuji takto zorganizovat. Ono se to vždycky hodí.

mimacala
Člen | 113
+
0
-

Skvělé, tohle jsou přesně informace, které potřebuji :)

Takže pokud si udělám abstraktní AppPresenter, tak ten mi bude posílat data do @AppLayout a nebude po mě chtít složku App s default.latte ?
AppPresenter ⇒ @AppLayout
HomepagePresenter ⇒ @HomepagaLayout

Vím, že se ptám hloupě, ale tohle jsou pro mě cenné zkušenosti :)

protože

<?php

namespace App\Presenters;

use Nette;
use Nette\Application\UI\Form;



abstract class AppPresenter extends Nette\Application\UI\Presenter
{

    public function beforeRender()
    {
        $this->template->kosik = "3";
  }


}

a v @AppLayout mi píše proměnnou {$kosik} jako neznámou
pořád totiž moc nechápu jak předávat ptoměnné to @layoutů
@m.brecher
Děkuji

Editoval mimacala (13. 10. 2022 19:32)

m.brecher
Generous Backer | 736
+
+1
-

@mimcala

Abstraktní presenter se dá skvěle využít na tři věci:

  • pohlídat přístupová práva do všech odvozených finálních presenterů – metoda startup()
  • zajistit data pro layout všech odvozených presenterů, dá se zde layout nastavit – metoda beforeRender()
  • registrovat komponenty které se používají v odvozených finálních presenterech – metoda CreateComponentXxx()

Takhle Ti to bude asi stačit, nepoužívej v nich metody action<Action>, nebo render<View>, ty se používají ve finálních presenterech.

Ve finálních presenterech se data jednotlivých akcí, nebo pro konkrétní šablony <View> do šablony dávají ideálně v render<View>. I ve finálním presenteru můžeš použít metodu beforeRender(), pokud potřebuješ dodat do všech šablon v tom presenteru nějaká společná data – to je ale spíše dost vyjímečné.

Pokud potřebuješ vykreslovat v layoutu košík uložený v session, tak doporučuji si košík vytvořit jako komponentu – tj. samostatnou class se svojí vlastní šablonou na vykreslování. Komponentu košík zaregistruješ v odpovídajícím abstraktním presenteru a do layoutu vykreslíš tagem {control kosik}. Komponenta si sama dodá do svojí vlastní template svoje data a žádný presenter se o to nestará. To je nejlepší postup. Měl bys tak komponentu košík, která nemá signály a jenom vykresluje košík ze session a komponentu přidat do košíku, která přidává ze stránek s produkty konkrétní produkt do košíku. Komponenta přidat do košíku by pracovala se signálem a vykreslovala by link pro přidání do košíku.

Snad jsem to popsal dost srozumitelně. Kdyby ne, pošlu nějaké ukázky kódů.

Editoval m.brecher (13. 10. 2022 23:10)

m.brecher
Generous Backer | 736
+
+3
-

Nejvyšší presenter pojmenuj BasePresenter, to je takový zavedený název v komunitě Nette. Nejvyšší layout @layout.latte

use Nette\Application\UI\Presenter;

abstract class BasePresenter extends Presenter
{
    public function beforeRender()
    {
        $this->setLayout('layout');     // nastaví @layout.latte
        $this->template->data = $data;  // data pro @layout.latte
   }
}

administrace


abstract class AdminPresenter extends BasePresenter
{
    public function startup()
    {
        parent::startup();
        // autorizace vstupu do administrace, je-li třeba...
    }

    public function beforeRender()
    {
        $this->setLayout('admin-layout');     // nastaví @admin-layout.latte
        $this->template->data = $data;  // data pro @admin-layout.latte
   }
}

eshop


abstract class EshopPresenter extends BasePresenter  // veřejný web nazvat třeba Eshop nebo Front
{
    public function beforeRender()
    {
        $this->setLayout('eshop-layout');     // nastaví @eshop-layout.latte
        $this->template->data = $data;  // data pro @eshop-layout.latte
   }
}

Jestli na homepage máš eshop tak bych homepage přejmenoval na eshop a pojem homepage bych vůbec nepoužíval. Ale názvy si udělej jak chceš, hlavně, aby se v tom dalo vyznat. Podle mého názoru je lepší eshopu říkat eshop a ne homepage a administraci říkat administrace a ne aplikace.

mimacala
Člen | 113
+
0
-

Aha, ,skvěle vysvětleno chápu, udělal jsem to podle tohoto, ale stejně mi to háže chybu, že nezná proměnnou :/
mám to řešené takto

<?php

namespace App\Presenters;

use Nette;
use Nette\Application\UI\Presenter;



abstract class HlavniPresenter extends Presenter
{

    public function beforeRender()
    {
        $this->setLayout('HlavnistranaLayout');
        $this->template->kosik = "3";
  }


tady mám HlavnistranaLayout, kde je proměnná, kterou mi hlásí, že to nezná

  <nav class="navbar navbar-dark navbar-expand-md sticky-top navbar-shrink py-3" id="mainNav">
        <div class="container"><a class="navbar-brand d-flex align-items-center" n:href="Hlavnistrana:"><span class="bs-icon-sm bs-icon-circle bs-icon-primary shadow d-flex justify-content-center align-items-center me-2 bs-icon" style="width: 40px;height: 40px;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-cpu" style="width: 26px;height: 26px;">
                        <path d="M5 0a.5.5 0 0 1 .5.5V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2A2.5 2.5 0 0 1 14 4.5h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14a2.5 2.5 0 0 1-2.5 2.5v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14A2.5 2.5 0 0 1 2 11.5H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2A2.5 2.5 0 0 1 4.5 2V.5A.5.5 0 0 1 5 0zm-.5 3A1.5 1.5 0 0 0 3 4.5v7A1.5 1.5 0 0 0 4.5 13h7a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 11.5 3h-7zM5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3zM6.5 6a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z"></path>
                    </svg></span><span>me75</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-1"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
            <div class="collapse navbar-collapse" id="navcol-1">
                <ul class="navbar-nav mx-auto">
                    <li class="nav-item"><a class="nav-link active" n:href="Hlavnistrana:">Domů</a></li>
                    <li class="nav-item"><a class="nav-link active" n:href="Produkty:">Obchod</a></li>
                    <li class="nav-item"><a class="nav-link active" n:href="Cenik:">Ceník</a></li>
                    <li class="nav-item"><a class="nav-link active" n:href="Jaktofunguje:">Jak to funguje</a></li>
                </ul><a class="btn btn-primary shadow" role="button" n:href="Cenik:">Registrovat</a><a class="btn btn-primary shadow" role="button" n:href="Prihlaseni:" style="margin-left: 17px;background: rgba(55,99,244,0);">Přihlásit</a>
                <a href="#" class="btn btn-primary mx-2" type="button" style="padding-right: 14px;padding-left: 14px;background: rgba(55,99,244,0);"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-cart3" style="font-size: 26px;">
                        <path d="M0 1.5A.5.5 0 0 1 .5 1H2a.5.5 0 0 1 .485.379L2.89 3H14.5a.5.5 0 0 1 .49.598l-1 5a.5.5 0 0 1-.465.401l-9.397.472L4.415 11H13a.5.5 0 0 1 0 1H4a.5.5 0 0 1-.491-.408L2.01 3.607 1.61 2H.5a.5.5 0 0 1-.5-.5zM3.102 4l.84 4.479 9.144-.459L13.89 4H3.102zM5 12a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm7 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-7 1a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm7 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"></path>
                    {$kosik}</svg></a>
            </div>
        </div>
    </nav>

Vyřešeno, děkuji, další presenter musí dědit HlavniPresenter .. moje školácká chyba :))

Editoval mimacala (15. 10. 2022 21:11)

Kamil Valenta
Člen | 758
+
+1
-

m.brecher napsal(a):

Ideální je plnit data pro layout v obecném abstraktním presenteru

Ačkoliv byla popsána funkční metoda, k označení za ideální asi krůček chybí. Ideální je abstraktní „BasePresentery“ nemít a traitami si vždy dotáhnout to, co je zrovna potřeba.

trait EshopKosik
{
    public function injectKosik()
    {
        $this->onStartup[] = function() {
            $this->template->kosik = 3;
        }
    }
}

class HomepagePreseter extends UI\Presenter
{
    use EshopKosik;
}

Problém BasePresenterů je, že je to trochu znásilněná dědičnost a co do Basu nafutruješ, už z něj u potomků nikdy neodpářeš. Takže pak hodně potomků tahá něco, co nechce, ale „chtějí to ti vedle“.

Editoval Kamil Valenta (15. 10. 2022 21:51)

Bulldog
Člen | 110
+
+2
-

Tak ale zrovna košík, který bude potřeba úplně všude je přesně ten UseCase, kdy BasePresenter použít ne? :D

Jakože chápu, že se může stát, že časem přibyde sekce, kde třeba košík nebudeš chtít, ale hej, řešit to takto všechno a za chvíli mi z těch trait exploduje hlava. Protože to máš traitu pro patičku s kontaktním formem, to máš traitu pro menu, to máš traitu pro košík, jinde zase pro zákaznický chat…

5 trait který musíš kopírovat do každýho presenteru jak vyšinutých. A jasně, uděláš jednu traitu, co je bude mít všechny v sobě, ale ruku na srdce. Jaký je pak rozdíl mezi tou traitou, kterou použiješ všude a BasePresenterem?

Já ho třeba používám minimálně na ověření, jestli má přístup uživatel do dané sekce (modulu).

Tedy v App\AdminModule\Presenters\BasePresenter mám asi toto:

<?php declare(strict_types=1);

namespace App\BackModule\Presenters;

use Nette\Application\UI\Presenter; as UiPresenter;
use Nette\Http\IResponse;

abstract class Presenter extends UiPresenter
{
    protected function startup()
    {
        $user = $this->getUser();

        if (!$user->isAllowed('Backend', 'view')) {
			// Or some other code like 401...
            $this->getHttpResponse()
                ->setCode(IResponse::S303_SEE_OTHER, 'Logged in as a client, admin was automatically logged out, or visited the page via a link without previous login');

            $this->redirect(':Sign:in', ['backLink' => $this->storeRequest('+30 minutes')]);
        }

        parent::startup();
    }
}

Editoval Bulldog (16. 10. 2022 0:11)

m.brecher
Generous Backer | 736
+
+1
-

@KamilValenta

Ahoj, v zásadě s Tebou souhlasím. Ale jsou věci, které se řeší úplně pro celý web a tam je úloha BasePresenteru.

Kupříkladu já požaduji, aby všechny final presentery měly k dispozici cestu do rootu aplikace $appDir. Kvůli některým šablonám, které je pohodlnější tahat absolutní cestou.

Nebo Base Presenter provádí přesměrování z duplicitního url www.domena.cz/www/ které na většině webhostingů nelze vyřešit jinak než .htaccess-em, který ale nevyřeší tu duplicitu.

Potom, ne vždy je čas na vše co je v @layoutu dělat ihned komponenty. Obvykle to nedřív nastřelím v BasePresenteru a @layoutu a když se to líbí, přesunu to do komponenty, nebo někdy zahodím.

A i když se @layout zjednoduší tím, že co se dá se umístí do vykreslovacích komponent, tak ty komponenty se někde musí registrovat. Registrovat komponenty v traitě považuji za naprosto nevhodné. Od toho je tu právě presenter.

Mám třeba některé pomocné utilitky, které jsem před týdnem z BasePresenteru přesunul do mojí první traity, ale jinak jsem traity zatím nepoužil. Obecně si myslím, že je lepší funkce řešit třídami nebo komponentami, ale některé pomocné utilitky, tam se traita fakt hodit může.

Aby se netahaly do abstraktního presenteru věci, které některý segment webu vůbec nepotřebuje – právě proto doporučuji mít nahoře BasePresenter, pod ním abstraktní presentery pro moduly, typicky FrontPresenter a Admin Presenter a pak už finální presentery, pokud možno malé a úzce specializované.

V podstatě jsem pod vlivem rad zkušenejších kolegů i na základě pokusů z refaktorace mých starších Nette věcí dospěl k názoru, že je ideální mít aplikaci pěkně rozčleněnou na malé specializované kousíčky, samozřejmě to nepřehnat. Takže komponenty layoutu jako menu/košík, atd… mít ve vykreslovacích komponentách, formuláře nemít v presenterech, ale umístit je do samostatných souborů jako Control a do presenterů je vkládat pomocí Factory. Model také dělám co nejčlenitější – co nejmenší úzce specializované modelové třídy, navzájem pokud možno nezávislé. Obvykle co presenter, to třída.

Traita není OOP, je to doplněk, který se občas hodí, ale časté použití může signalizovat nedostatky v architektuře.

Editoval m.brecher (16. 10. 2022 1:08)

Kamil Valenta
Člen | 758
+
0
-

@Bulldog
Naopak, s narůstajícím počtem víc a víc traity oceníš.
Jaký je rozdíl mezi BasePresenterem a traitou použitou všude? Právě v tom, že jednou (a nemusíme řešit, zda se to stane nebo ne, řešme jen kdy) to „všude“ přestane platit. Navíc dílčí traity umožňují do presenteru nakombinovat jen to potřebné, BasePresenter nalije vždy všechno.

@m.brecher Traita je samozřejmě součást OOP, jako třeba interface. Jaké nedostatky v architektuře signalizuje?

m.brecher
Generous Backer | 736
+
0
-

@KamilValenta Nic proti traitám nemám, sám jsem minulý týden po roce váhání jednu skutečně nasadil. Ale časem to co v ní mám nejspíš přesunu do nějaké obecné komponenty, ale dokud ji nemám vymyšlenou traita poslouží dobře.

Traita se skvěle hodí na doplnění nějaké utilitkové feature (nebusinessová logika) – typický příklad v Nette máme traitu SmartObject. Ta dodá do třídy doplnění těch feature, které by měly být v platformě (PHP), ale nejsou. Dříve to David dělal obecnou třídou Object, ze které doporučil, aby dědily všechny třídy. To je přesně ten případ, kdy je vložení této utilitky do hierarchie tříd těžkopádné a traita TO ŘEŠÍ ideálně.

Když ale od Tebe slyším, jak máš celou sadu trait, které doplňuješ do presenterů (?) tam, kde se to hodí, tak mě napadá, zda by se to neřešilo lépe komponentami. Musel bych ale vidět, co konkrétně tím řešíš, abych nekritizoval něco, co nevidím.

Jak jsem uvedl případ, kdy chci mít všude k dispozici $appDir bez ohledu na to, zda se všude použije – prostě kvůli komfortu, abych nemusel pořád někde myslet na to, zda je tam příslušná traita nebo ne. Nebo to přesměrování na kanonickou url – popisoval jsem výše. Dále registrace komponent pro celý web (layout – třeba menu). Tohle musí být buď v celém webu, nebo celém modulu ve všech presenterech. Traitama bych to musel vkládat všude, to je nesmysl, právě zde je ideální použít abstraktní presenter.

Traity by se neměly používat tak, jako kdysi v PHP include. Já jsem kdysi stavěl weby podobně jak píšeš, do stránek jsem dodával potřebné komponenty pomocí include souborů a weby jsem na tom postavil velice pěkné.

Při velkém zjednodušení můžeme říct, že architektura BasePresenter + final presentery je analogií layout + šablony, zatímco traita + presentery je analogií šablona + include hlavičky, patičky a komponent. Velmi zjednodušeně.

No, ale když zvolíš design založený na vkládání trait, tak se určitě pěkné aplikace také dají postavit. Já jsem podobnou architekturu dělal v PHO roky. Poučen dlouhodobou údržbou těchto projektů se nyní snažím naučit komponentový model.

Kamil Valenta
Člen | 758
+
0
-

Komponentami zdaleka nepokryješ vše, např. tu změnu layoutu, ověření přihlášeného uživatele, továrničku na menu. Přitom to vše může mít své traity.
Nějaká analogie s include je docela mimo, i přes snahu o zjednodušení. Chápu, že hájíš své workflow, ale přiznejme si, že „použití první traity po roce váhání“ není úplně nejlepší předpoklad pro objektivní zhodnocení daného přístupu.

Mrkni třeba sem: https://youtu.be/yIhKrIlhtGY?…
Zkus si tak napsat ze 3 weby a pak bude Tvé srovnání objektivní.

P.S. nedostatek v architektuře jsem nezachytil žádný…

mimacala
Člen | 113
+
0
-

Ahojte, prosím a jaké máte zkušenosti s řešením košíku ? co je nejlepší ?
Jde do session uložit více údajů ? kromě ID a množstí ?

public function actionPridatkosik(int $idProduktu, int $mnozstvi)
    {
        $kosik = $this->session->getSection('kosik');
        if($kosik->get("$idProduktu"))
        {
            foreach ($kosik as $key => $val) {
                if($key == $idProduktu)
                {

                    $kosik->set("$idProduktu",$val + $mnozstvi);
                }
            }

        } else {
            $kosik->set("$idProduktu","$mnozstvi");
        }

        if($kosik)
        {
            $this->flashMessage("Zboží bylo přidání do košíku");
            $this->redirect("Produkty:");
        }
    }

řeším právě problém s košíkem, že si vypíšu ze session název a množství, které můžu upravovat, ale pořeboval bych si uložit ještě cenu ideláně :)

Psaní funkce na výčet dat z DB podle id v session jen kvůli ceně a potom si to přeposílat do latte mi přijde jako práce navíc ?
jde něco jako $kosik->set(„$idProduktu“,„$mnozstvi“, „cena“,); takové pole s více hodnotami ?

Do latte si to ted posílám takto a v latte pak udělám jen foreach.

   public function renderDefault()
    {
        $this->setLayout('HlavnistranaLayout');
        $this->template->kosikData = $this->session->getSection('kosik'); // pošle ůdaje v session do latte
    }
m.brecher
Generous Backer | 736
+
0
-

@KamilValenta

asi hodně záleží na tom, jak aplikaci od základů stavíš a jak je rozsáhlá. Já od začátku počítám s hierarchií presenterů a komponentami a proto asi u mě potřeba trait nevzniká. Ale už Jsi mě malinko zviklal, že u malých aplikací se dá do presenterů přidávat funkcionalita traitami, které mohou částečně nahradit abstraktní presentery, nebo komponenty.

Já jsem si traitou jsem si vylepšil Presentery Nette, podobně jako v Nette je traita Nette\SmartObject, která vylepšuje některé drobné nedostatky PHP.

Nahradit abstraktní presentery traitami ale rychle narazí na omezení trait. Některé funkce, které já realizuji BasePresenterem traity inteligentně nezvládnou.

V BasePresenteru v startup() přesměrovávám na kanonickou url, protože interní přesměrování z /www/ na / .htaccessem vytvoří duplicitní url.

Kanonizace url BasePresenterem


abstract class BasePresenter extends Nette\Application\UI\Presenter
{
    #[Inject]
    public Canonizer $canonizer;

    startup()
    {
        parent::startup();
    	$this->canonizer->canonize();
    }
}


final class ArticlePresenter extends BasePresenter
{
        startup()
    {
        parent::startup();
    	..... 				// specific authorization for this presenter
    }
}

Kanonizace url traitou


trait Canonizer
{
    #[Inject]
    public Canonizer $canonizer;

    startup()
    {
        parent::startup();
    	$this->canonizer->canonize();
    }
}


final class ArticlePresenter extends Nette\Application\UI\Presenter
{
	use Canonizer;

	startup()				// shit! this will not work with trait :(
    {
        parent::startup();
    	..... 				// specific authorization for this presenter
    }
}

Traita kterou místo BasePresenteru vkládáme do všech presenterů má zásadní omezení – nelze v této architektuře škálovat setup presenterů – tj. provést společný setup v traitě a pak specifické setupy v presenteru – tak jako se běžně používá v architektuře BasePresenteru.

Další příklad. Setupuji parametr $appDir z .neon do BasePresenteru – pro pohodlné vkládání jakýchkoliv latte šablon či layoutů.

// konfigurace

decorator:
	App\Presenters\BasePresenter:
		setup:
			- $appDir = %appDir%

setupováné $appDir BasePresenterem


abstract class BasePresenter extends Nette\Application\UI\Presenter
{
    public string $appDir;

    .....

    public function beforeRender()
    {
		parent::beforeRender();
        $this->template->appDir = $this->appDir;  // $appDir for all templates or layouts :)
   }

}


final class ArticlePresenter extends BasePresenter
{
    .....

    public function beforeRender()
    {
		parent::beforeRender();
        $this->template->var = $this->var;  // $var for all presenter templates (actions)
   }

}

setupováné $appDir traitou


trait AppDir
{
    public string $appDir;

    .....

    public function beforeRender()
    {
		parent::beforeRender();
        $this->template->appDir = $this->appDir;  // $appDir for all templates or layouts :)
   }

}


final class ArticlePresenter extends Nette\Application\UI\Presenter
{
	use AppDir;

	public function beforeRender()
    {
		parent::beforeRender();
        $this->template->var = $this->var;  // shit! with trait this will not work :(
   }
}

Z příkladů je jasné, že některé funkce BasePresenteru nelze nahradit traitou.

Editoval m.brecher (16. 10. 2022 23:15)

Bulldog
Člen | 110
+
+1
-

@KamilValenta @mbrecher

přesunul jsem problém s používáním trait do samostatného vlákna

@mimacala
Do session můžeš uložit libovolnou serializovatelnou strukturu, takže i pole hodnot.

public function actionPridatkosik(int $idProduktu, int $mnozstvi)
{
    if ($mnozstvi < 1) {
        $this->error('Menuzete pridat tak malo kusu');
    }

    $product = $this->productRepository->getById($idProduktu);

    if (!$product) {
        $this->error('Zbozi neexistuje');
    }

    $kosik = $this->session->getSection('kosik');

    $vKosiku = $kosik->get((string)$idProduktu) ?? [
        'mnozstvi' => 0,
        'cena' => 0.0,
    ];

    $vKosiku['mnozstvi'] += $mnozstvi;
    $vKosiku['cena'] = $mnozstvi * $product->cena;

    $kosik->set((string)$idProduktu,$vKosiku);

    $this->flashMessage("Zboží bylo přidáno do košíku");
    $this->redirect("Produkty:");
}

Jenom dej pozor na to, aby byla opravdu serializovatelná. Teď nedávno jsem narazil na to, že mi Nette neuložilo do session data, protože nebyla serializovatelná, ale nevyhodilo žádnou hlášku, error nic. Prostě pro nevalidní data mi tam uložilo NUll a nestaralo se

Editoval Bulldog (16. 10. 2022 23:25)

Kamil Valenta
Člen | 758
+
+3
-

m.brecher napsal(a):
Z příkladů je jasné, že některé funkce BasePresenteru nelze nahradit traitou.

Ne, z příkladů je jasné, že jsi buď neviděl odkazované video, nebo jsi ignoroval můj příklad v tomto vlákně, nebo oboje.
Všechny uvedené příklady traitou napsat lze.

mimacala
Člen | 113
+
0
-

@Bulldog
Ahoj, prosímtě měl bych pár dotazů :)
v První řadě hodilo mi to error „Cannot use a scalar value as an array“ na řádku

  $vKosiku['mnozstvi'] += $mnozstvi;

myslím si, že je nějak špatně definován multidimen.. array, moc nevím, kde přesně hledat chybu :/.

A druhý dotaz je, že moc nepoužívám ternární operátory

=>Tento kód mi ověří, pokud existuje $idProduktu tedy produkt a pokud ano nabere ho do proměnné $vKosiku a pokud ne tak nastaví vše na nulu.
Prosím tedy „(string)“, v php 8.0 je nově typový systém proměnných, toto definuje text nebo co přesně to dělá před $idProduktu?

$vKosiku = $kosik->get((string)$idProduktu) ?? [
            'mnozstvi' => 0,
            'cena' => 0.0,
        ];

Moc děkuji za objasnění

Kdyby někdo četl,
vyřešil jsem to prozatím takto

  public function actionPridatkosik(int $idProduktu, int $mnozstvi, int $cena)
    {
        if ($mnozstvi < 1) { //pokud přidává málo kusů
            $this->error('Menuzete pridat tak malo kusu');
        }

        /*$product = $this->productRepository->getById($idProduktu);

        if (!$product) {
            $this->error('Zbozi neexistuje');
        }*/

        $kosik = $this->session->getSection('kosik'); // načte sekci



            if($kosik->get($idProduktu)){ // pokud již položka existuje
                foreach ($kosik as $produkt => $hodnoty) {
                    $vKosiku = ["mnozstvi" => $mnozstvi+$hodnoty["mnozstvi"], "cena" => $cena+$hodnoty["cena"]]; // vytvoří array s hodnot. produktů a přičte stávající hodnoty
                }

            } else { // pokud položka neexistuje
                $vKosiku = ["mnozstvi" => $mnozstvi, "cena" => $cena]; // vytvoří array s hodnot. produktů
            }

        $kosik->set($idProduktu,$vKosiku); // nastaví session.



        $this->flashMessage("Zboží bylo přidáno do košíku");
        $this->redirect("Produkty:");
    }

  public function ZobrazKosik()
    {
        $mnozstvi = 0;
       $section = $this->session->getSection("kosik");


         foreach ($section as $produkt => $hodnoty) { // debug proměnných

             echo("ProduktID:".$produkt." "."Množstí:".$hodnoty["mnozstvi"]." "."Cena:".$hodnoty["cena"]);
             $mnozstvi = $mnozstvi + $hodnoty["mnozstvi"];
         }


        return $mnozstvi; // počet položek v košíku

    }

Editoval mimacala (18. 10. 2022 22:14)

Bulldog
Člen | 110
+
0
-

@mimacala

v První řadě hodilo mi to error „Cannot use a scalar value as an array“ na řádku

Protože to vypadá, že máš historicky v té session uložený nějaký scalár. Takže to ten scalár načte a pak nový kód se z toho snaží přistupovat ke skaláru jako k poli. Musíš nejdřív celou session vymazat, než začneš zkoušet novou strukturu kódu.

Prosím tedy „(string)“, v php 8.0 je nově typový systém proměnných, toto definuje text nebo co přesně to dělá před $idProduktu?

Pokud se podíváš na definici tvé funkce, která má za úkol přidávat produkty do košíku public function actionPridatkosik(int $idProduktu, int $mnozstvi), tak uvidíme, že bere jako parameter $idProduktu a před ním je klíčové slovíčko int, což znamená, že v proměnné $idProduktu bude uloženo celé číslo.
Pokud se ale podíváme, co bere nad SessionSection metoda get, kterou voláš, tak uvidíme její předpis takový: public function get(string $name)
Což říká, že jako první parametr to bere string, což je řetězec, tedy série znaků.

Pokud bychom tedy zkusili udělat něco jako $kosik->get(4); tak dostaneme error

TypeError

Nette\Http\SessionSection::get(): Argument #1 ($name) must be of type string, int given, called in ...

To znamená, že my bereme jako identifikátor integer, ale session chce string.
No a přetypování integeru na string lze v PHP několika způsoby:

"$idProduktu"
"" . $idProduktu
(string) $idProduktu

takže zápis (string) $idProduktu je pouze explicitní přetypování integeru na string. Osobně doporučuju dělat jen tu poslední možnost. Samozřejmě se více napíšeš, ale aspoň je to hned na první pohled vidět a neuděláš chybu. Ostatní 2 způsoby se používají spíše na vložení proměnné do řetězce jako:

"There is $count apple"
"There is " . $idProduktu . "apple"

Ale i tyto způsoby nejsou úplně ok:

V prvním případě můžou nastat problémy jako že potřebuješ přidat těsně za proměnnou nějakou příponu, například potřebuješ říct, kolikátý daný závodník skončil:

"Skončil jsi na $placetém místě" // Proměnná je $place

Tady vidíš, že PHP není schopno rozeznat které písmena ještě jsou proměnná a která už ne. Na to se vytvořily závorky:

"Skončil jsi na {$place}tém místě" // Proměnná je $place

Tady už to v pohodě rozezná.
Co když ale potřebuješ napsat kolik sis vydělal dolarů?

"Vydělal sis ${$amount}"

tady taky nastane problém, protože nejdřív se vykoná vnitřek závorak a ten se vezme díky znaku dolaru jako další proměnná, takže v případě, že obsah proměnné amount je 10, tak se PHP bude snažit najít proměnnou s názvem $10. Takže musíme escapovat dolary:

$foo = 'ahoj';
$bar = 'foo';

echo "Your string ${$bar}"; 	// Vypíše  "Your string ahoj"
echo "Your string \${$bar}";	// Vypíše  "Your string $foo"

Díky tomuto fenoménu můžeme vytvořit proměnnou, která se jmenuje null :D

$a = null;
$$a = 'This is string saved to variable named null';

Problém může nastat i se závorkami, takže musíš zkoumat kdy kde jak co escapovat a to je zbytečný peklo.

Druhý způsob tedy vypadá lépe, protože můžeme místo uvozovek " použít apostrofy ', které berou vše jako znaky, takže se nic nemusí escapovat:

'Skončil jsi na ' . $place . 'tém místě'	// Skončil jsi na 15tém místě
'Vydělal sis $' . $amount					// Vydělal sis $50

Problém tady ale nastane při překladu těch řetězců představ si to ve formátu, kde máš proměnné třeba 3 v tom řetězci

'Tlupa ' . $animal . ' seděla uprostřed ' . $place . ' asi v ' . $time . 'hodin.' // Tlupa koček seděla uprostřed domu asi v 10hodin.

v tomhle případě musíš mít uloženy 4 různé řetězce a překládat je postupně, což je sebevražda. Takže konkatenovat řetězce by se mělo přes funkci sprintf:

sprintf('Tlupa %s seděla uprostřed %s asi v %shodin.', $animal, $place, $time); // Tlupa koček seděla uprostřed domu asi v 10hodin.

Všimni si, že zde je uvnitř řetězce povoleno %shodin, jelikož sprintf definuje zástupné symboly, které mají vždy znak % a jeden znak za tím, případně specifický formát výpisu, jako formátování čísla, nebo explicitní změnu pořadí argumentů, ale to se dočteš v odkaze, takže je jasně rozpoznatelné, že %s vypíše řetězec. A jediné, co zde musíme escapovat je znak %, pokud jej chceme přímo vypsat a to tak, že %% vypíše prostě znak %.
No a jako výhoda je, že máš 1 řetězec, který můžeš házet do překladů.

Takže obecně pro přetypování proměnné na řetězec bych doporučil zápis (string) $prom a pro výpis proměnné do řetězce funkci sprintf

Bulldog
Člen | 110
+
0
-

Tento kód mi ověří, pokud existuje $idProduktu tedy produkt a pokud ano nabere ho do proměnné $vKosiku a pokud ne tak nastaví vše na nulu.

Metoda get ze SessionSection ve výchozím stavu vrací null, pokud prvek nenajde. To znamená, že když prvek neexistuje, chceme vytvořit nový, který je naplněný těmi daty, které jsme dostali. To můžeme udělat nejzákladnějším způsobem takto:

public function actionPridatkosik(int $idProduktu, int $mnozstvi)
{
    // Získáme produkt z databáze a uděláme další ověření...
    $product = $this->productRepository->getById($idProduktu);

    // Získáme session sekci košík
    $kosik = $this->session->getSection('kosik');
    // Získáme obsah z košíku pro daný produkt
    $obsahKosiku = $kosik->get((string) $idProduktu);

    // ověříme, jestli je obsah null a tedy neexistovalo dané ID
    if ($obsahKosiku === null) {
        // pokud ano, uložíme do session NOVÉ pole s našimi prvky
        $kosik->set((string) $idProduktu, [
            'mnozstvi' => $mnozstvi,
            'cena' => $mnozstvi * $product->cena,
        ]);
    } else {
        // pokud to pole existovalo, chceme jej pouze upravit
        $obsahKosiku['mnozstvi'] += $mnozstvi;
        $obsahKosiku['cena'] += $mnozstvi * $product->cena;	// připočteme cenu za přidané zboží

        // a uložit
        $kosik->set((string) $idProduktu, $obsahKosiku);
    }

    // nakonec přesměrujeme
    $this->flashMessage("Zboží bylo přidání do košíku");
    $this->redirect("Produkty:");
}

// A přehledněji bez komentářů:
public function actionPridatkosik(int $idProduktu, int $mnozstvi)
{
    $product = $this->productRepository->getById($idProduktu);

    $kosik = $this->session->getSection('kosik');
    $obsahKosiku = $kosik->get((string) $idProduktu);

    if ($obsahKosiku === null) {
        $kosik->set((string) $idProduktu, [
            'mnozstvi' => $mnozstvi,
            'cena' => $mnozstvi * $product->cena,
        ]);
    } else {
        $obsahKosiku['mnozstvi'] += $mnozstvi;
        $obsahKosiku['cena'] += $mnozstvi * $product->cena;

        $kosik->set((string) $idProduktu, $obsahKosiku);
    }

    $this->flashMessage("Zboží bylo přidání do košíku");
    $this->redirect("Produkty:");
}

Jenže je tu duplicita kódu atd a dá se to vylepšit. Napříkad je doporučeno kvůli přehlednosti kódu co nejméně používat else statementy.
Takže nejdřív si vezmeme co je v else a podíváme se, jestli se to dá nějak skloubit s tím, co je v Ifu.
Vidíme, že if se dá přepsat z

$kosik->set((string) $idProduktu, [
	'mnozstvi' => $mnozstvi,
	'cena' => $mnozstvi * $product->cena,
]);

na

// Uvnitř ifu můžeme využít znovu proměnnou $obsahKosiku, jelikož víme, že je její obsah null a tedy ji na nic jiné nepotřebujeme
$obsahKosiku = [
	'mnozstvi' => $mnozstvi,
	'cena' => $mnozstvi * $product->cena,
];

$kosik->set((string) $idProduktu, $obsahKosiku);

A vidíme, že řádek $kosik->set((string) $idProduktu, $obsahKosiku); je stejný, jako v else statementu, takže je pomocí vytýkání můžeme vytáhnout vně ifu:

public function actionPridatkosik(int $idProduktu, int $mnozstvi)
{
    $product = $this->productRepository->getById($idProduktu);

    $kosik = $this->session->getSection('kosik');
    $obsahKosiku = $kosik->get((string) $idProduktu);

    if ($obsahKosiku === null) {
        $obsahKosiku = [
            'mnozstvi' => $mnozstvi,
            'cena' => $mnozstvi * $product->cena,
        ];
    } else {
        $obsahKosiku['mnozstvi'] += $mnozstvi;
        $obsahKosiku['cena'] += $mnozstvi * $product->cena;
    }

    $kosik->set((string) $idProduktu, $obsahKosiku);

    $this->flashMessage("Zboží bylo přidání do košíku");
    $this->redirect("Produkty:");
}

Ok když se podíváme dále do ifu (ne do else), tak toto

$obsahKosiku = [
    'mnozstvi' => $mnozstvi,
    'cena' => $mnozstvi * $product->cena,
];

se dá zapsat jako modifikování výchozí hodnoty. Respektive jde o nastavení proměnných tak, aby jsi s nimi dále v kódu jednotně pracoval:

// chceme v košíku přičítat a víme tedy, že košík na začátku může být pouze prázdný, tak jej naplníme výchozími hodnotami
$obsahKosiku = [
    'mnozstvi' => 0,	// na začátku je zde 0 kusů daného produktu
    'cena' => 0.0,		// Víme, že na začátku když je 0 kusů, tak musí logicky stát 0 mincí
];
// a tyto výchozí hodnoty upravíme:
$obsahKosiku['mnozstvi'] += $mnozstvi;
$obsahKosiku['cena'] += $mnozstvi * $product->cena;

A všimni si, že v tomto zápisu obsahu ifu, jsou opět 2 poslední řádky shodné s obsahem elsu, takže je opět vyhodíme ven pomocí vytýkání. Tady nám už zbyl prázdný else, tak ho vůbec psát nebudeme

public function actionPridatkosik(int $idProduktu, int $mnozstvi)
{
    $product = $this->productRepository->getById($idProduktu);

    $kosik = $this->session->getSection('kosik');
    $obsahKosiku = $kosik->get((string) $idProduktu);

    if ($obsahKosiku === null) {
		// Pokud byl obsah null, tedy neexistoval, tak ho naplníme výchozími hodnotami, abychom s tím pracovali jednotně
        $obsahKosiku = [
            'mnozstvi' => 0,
            'cena' => 0.0,
        ];
    }

    $obsahKosiku['mnozstvi'] += $mnozstvi;
    $obsahKosiku['cena'] += $mnozstvi * $product->cena;

    $kosik->set((string) $idProduktu, $obsahKosiku);

    $this->flashMessage("Zboží bylo přidání do košíku");
    $this->redirect("Produkty:");
}

Ok. To už je čitelnější.
Ale PHP 7 nám přineslo krásný syntax sugar ve formě ‚null coalescing operator (??)‘, který říká
$x = $y ?? $z;
Pokud proměnná $y existuje a je v ní cokoliv jiného, než null, tak do $x přiřaď obsah $y. Pokud ale $y neexistuje, nebo její obsah je null, tak do $x přiřaď obsah proměnné $z
Tedy následující kódy jsou totožné:

$y = null; 	// Simulujeme, že $y existuje, protože naše metoda get ze SessionSection vrátí vždy existující hodnotu, i když třeba null, tedy nám nemůže vyskočit not defined error.

$x = $y;

if ($x === null) {
  $x = $z;
}

je totožný s

$y = null;
$x = $y ?? $z;

Tedy celý IF byl nahrazen znaky ??
A v našem kódu je obsah proměnné $z ta výchozí hodnota. Respektive $z je vždy výchozí hodnota, pokud $y není nastavené, nebo je null. Takže naše $z a tedy výchozí hodnota je toto:

[
    'mnozstvi' => 0,
    'cena' => 0.0,
]

a když se tedy zbavíme ifu a uděláme z něj ternár pomocí Null coalescing operatoru, tak dostaneme výsledek

public function actionPridatkosik(int $idProduktu, int $mnozstvi)
{
    $product = $this->productRepository->getById($idProduktu);

    $kosik = $this->session->getSection('kosik');
    $obsahKosiku = $kosik->get((string) $idProduktu) ?? [
        'mnozstvi' => 0,
        'cena' => 0.0,
    ];

    $obsahKosiku['mnozstvi'] += $mnozstvi;
    $obsahKosiku['cena'] += $mnozstvi * $product->cena;

    $kosik->set((string) $idProduktu, $obsahKosiku);

    $this->flashMessage("Zboží bylo přidání do košíku");
    $this->redirect("Produkty:");
}

Tedy říkáme získej data z košíku a pokud neexistují dej tam výchozí pole.

Thats all. Další dotazy?

Editoval Bulldog (18. 10. 2022 23:45)

Bulldog
Člen | 110
+
0
-

Tedy tvoje poslední úpravy by ve zjednodušené verzi mely vypadat asi takto:

public function actionPridatkosik(int $idProduktu, int $mnozstvi, int $cena)
{
    if ($mnozstvi < 1) { //pokud přidává málo kusů
        $this->error('Menuzete pridat tak malo kusu');
    }

    $kosik = $this->session->getSection('kosik'); // načte sekci
    $vKosiku = $kosik->get((string) $idProduktu) ?? [
        'mnozstvi' => 0,
        'cena' => 0.0,
    ];

    // Tady víme, že v proměnné $vKosiku vzdy bude pole s cenou a mnozstvim z minula (z minula muze byt i vychozi hodnota, tedy nulove pole)
    $vKosiku['mnozstvi'] += $mnozstvi;  // += říká, že je to ekvivalent se zápisem        $vKosiku['mnozstvi'] = $vKosiku['mnozstvi'] + $mnozstvi;
    $vKosiku['cena'] += $cena;  // V případě, že bude cena, nebo mnozstvi 0 jako vychozi hodnota, tak se udela prostě 0 + $cena coz je stejne jako obsah puvodniho tveho elsu

    $kosik->set((string) $idProduktu, $vKosiku); // nastaví session.

    $this->flashMessage("Zboží bylo přidáno do košíku");
    $this->redirect("Produkty:");
}

a

public function ZobrazKosik()
{
    $mnozstvi = 0;
    $section = $this->session->getSection("kosik");

    foreach ($section as $produkt => $hodnoty) {
        if ($hodnoty['mnozstvi'] === 0) {
            continue;   // Muže se stát, že někdo odebere z košíku produkty a u takových nechceš aby se zobrazovaly jako 0 kusů daného produktu. Ale lepší je mazat rovnou ze session už při odebírání ;)
        }

        echo (
            sprintf('ProduktID:%d Množstí:%d Cena:%f', $produkt, $hodnoty['mnozstvi'], $hodnoty['cena'])
        );
        $mnozstvi += $hodnoty['mnozstvi'];
    }

    return $mnozstvi; // počet položek v košíku
}

Jenom prosím ještě počítej s 2 věcmi:

  1. statické řetězce je lepší nezapisovat jako "mnozstvi" ale jako 'mnozstvi', jelkož jak jsem již napsal, tak řetězce v uvozovkách se snaží vykonávat inline kód a tedy jsou statické řetězce na vykreslení pomalejší, jelikož se nejdřív projdou a otestuje se, jestli obsahují proměnné/výrazy. Řetězce v apostrofech tohle nedělají, takže se vypisují okamžitě, jelikož s nimi je nakládáno přímo jako s řetězci. Takže je dobré na to myslet. Budeš pak mít nepatrně rychlejší aplikaci a když náhodou přibyde něco, co musíš escapovat, tak na to nemusíš myslet.
  2. Metoda by neměla dělat žádné side efekty. Respektive tvůj kód by neměl provádět výpis pomocí echo, pokud zároveň něco počítá atp. Na to se udělaly návrhy kódů, aby byla jedna třída, která volá další a jejich odpovědi vypisuje. Té třídě, co to vypisuje se říká View. všechny ostatní by měly jen něco počítat (model) nebo spojovat view a model (Controller/Presenter)
mimacala
Člen | 113
+
0
-

Aha rozumím moc děkuji,
jak poté řešíte třeba následující problém, když přidám další položku v košíku přes js.
Nebo je lepší to řešit přes odeslání foromuláře ? to by ale obnovovalo vždycky stránku nebo voláte přes JS nějak na php ?

Děkuji

function Pricti(par) {
    var cap = document.getElementById(par).value;

    cap++;

    console.log(cap);
    document.getElementById(par).value = cap;

}
function Odecti(par) {

    var cap = document.getElementById(par).value;
    cap--;

    console.log(cap);
    document.getElementById(par).value = cap;

}
Kamil Valenta
Člen | 758
+
+3
-

Každé zboží, resp. jeho množství může mít v košíku svůj form a ten můžeš odesílat ajaxově.
Nebo vypsat všechna množství do jednoho formu a pod tím submit ve stylu „přepočítat“.

Varianta, která mně hodně žere, když někdo odesílá form při onchange. Protože než se stránka přenačte, stihnu změnit ještě množství u 1–2 zboží a to musím dělat pak znova…

Bulldog
Člen | 110
+
+1
-

Varianta, která mně hodně žere, když někdo odesílá form při onchange. Protože než se stránka přenačte, stihnu změnit ještě množství u 1–2 zboží a to musím dělat pak znova

Tohle řešíme tak, že sice odesílá formulář pomocí onchange, ale vykreslování formuláře tím není ovlivněno. Respektive každá změna se pošle na server, kde se zaloguje, ale o FE překreslování se stará JS (Každý produkt má u sebe někde informaci o ceně a kromě odeslání formuláře na server se ještě na klientovi přepočítá cena a zobrazí se, tedy aktualizace je jen odeslání dat na server bez čekání na odpověď, jelikož klient všechny potřebné údaje již dávno má.)

Kamil Valenta
Člen | 758
+
+1
-

@Bulldog jasně, to je v podstatě jedna z ajaxových variant… Já myslel ty případy, kdy někdo při onchange odesílá celý form s redirectem. Aby si tazatel nevybral tuhle variantu :)

Bulldog
Člen | 110
+
+1
-

To máš pravdu. To je peklo. A zvlášť, když odpověď není do 30ms, kdy nestihneš nic udělat, ale trvá to třeba vteřinu a bez preloaderu nic. To by si pak vraždil.

mimacala
Člen | 113
+
0
-

Takže vzhledem k tomu, že to teď mám v jednom formu, takže nejjednodušší řešení je přidat tlačítko na upravení.
Každopádně šlo by třeba přes ajax zavolat na php funkci v nette, kde přidávám zboží do session ? to by mohlo vyřešit problém s tám aby uživatel zbytečně klikal na talčítko navíc, akorát by se musela zase obnovit stránka, ale uživatel by to měl snažší.

Tedy šlo by zavolat na ajax aby zavolal na fukci pro přidání do session ? Jak by šel vyřešit preloader ? to zase obstarává js ?
Děkuji