Jak získat data z url v Controleru

vosy
Člen | 525
+
0
-

Ahoj,
mám takovouto routu:

$router->addRoute('admin/emaily/[<paginationPage>]', 'Admin:Emaily:seznam');

Chtěl bychs e zeptat jak dosahnu v Controleru na proměnnou „paginationPage“ ?

Díky

Šaman
Člen | 2634
+
+1
-
actionSeznam($paginationPage)
{}

# Pokud definuješ typ, tak ho Nette pohlídá, jinak akceptuje všechno.
renderSeznam(int $paginationPage)
{}

Btw. je to to samé, jako v Sandboxu/Tutoriálu parametr id. Jestli mi teda v pátek v noci něco neuniká :)

Editoval Šaman (15. 10. 2022 2:29)

vosy
Člen | 525
+
0
-

Ahoj, ja se assi blbě vyjádřil.

To co píšeš je pro „Presenter“ s tím jsem v pohodě, ja myslel v „Komponentě“.
Jestli je něco jako:

class OrderByControl extends Control
{
...
..

public function render(...)
{
$presenter = $this->getPresenter();
$page = $presenter->paginationPage;
}
...
..
}
m.brecher
Generous Backer | 735
+
0
-

Píšu z hlavy tak snad to bude správně:

class OrderByControl extends Control
{
...
..

public function render(...)
{
$presenter = $this->getPresenter();
$page = $presenter->getParameter('paginationPage');
}
...
..
}
m.brecher
Generous Backer | 735
+
+1
-

Obecně platí, že jak Jsi definoval routu:

$router->addRoute('admin/emaily/[<paginationPage>]', 'Admin:Emaily:seznam');

Tak parametr paginationPage je parametr presenteru. Měl bys aplikaci navrhovat logicky a co nejjednoduššeji. V tomto případě to znamená, že by to měl být presenter, který ten parametr bude používat.

Pokud by se mělo stránkování používat v komponentě – např. by Jsi chtěl zapouzdřit vykreslování stránkovacího menu do vykreslovací komponenty, tak zde by bylo ideální, kdyby presenter parametr paginationPage sám předal do komponenty.

dakur
Člen | 493
+
+2
-

@vosy Dosáhneš na ni přes $this->getPresenter()->getParameter('paginationPage'), ale myslím, že to není dobrý nápad dělat, protože pak vlastně vytváříš skrytou závislost.

Raději si ji do komponenty předej způsobem, jaký je popsaný v článku, který poslal výše Marek. Tam se sice nemluví o parametru, ale o službě, princip je ale obdobný. Jen ten parametr se nevytáhne sám z DIC, ale musí se tam z presenteru předat – viz $id/$pollId v tom článku.

m.brecher
Generous Backer | 735
+
0
-

@vosy a @dakur když se bude z presenteru předávat parametr paginationPage, což je pravděpodobně int, tak může vzniknout TypeError, pokud předáváme z presenteru do komponenty – ať už konstruktorem, nebo metodou $componentFactory->create(). Nedávno jsem to řešil:

class PaginatorControl extends Control
{
	public function __construct(private ?int $paginationPage, ...)
	{}
	.....
}

interface PaginatorControlFactory		// registrovat ve službách di
{
	public function create(): PaginatorControl;
}

Předání $paginationPage z presenteru do factory komponenty

class MyPresenter extends BasePresenter
{
	public function __construct(private PaginatorControlFactory $paginatorFactory, ...)
	{}

	public function createComponentPaginator(): PaginatorControl
	{
		$paginationPage = $this->getParameter('paginationPage');	// a) TypeError, vyžadován int, ale toto je string

		$paginationPage = (int)$this->getParameter('paginationPage');  // b) nic moc, PHP null přetypuje na 0 !!!

		$paginationPage = $this->getParameter('paginationPage');
		$paginationPage === null ? null : (int)$paginationPage;		// c) ošetření PHP přetypování na int :) - nečitelný kód

		return $this->paginatorFactory->create($paginationPage);
	}
}

Případ c) funguje dobře i pro $paginationPage = null, což by tak mělo být – pro stránku 1 je obvykle vhodné parametr v url vůbec nemít (null). Kód null ? null : (int)$paginationPage není dobře čitelný a tak já jsem si udělal vylepšení presenterů Nette šikovnou traitou:

trait SmartNette
{
    public function getParameterInt(string $name): ?int
    {
        $param = $this->getParameter($name);
        return $param === null ? null : (int)$param;
    }

    public function flashMessageArray(array $messages, string $type = 'info'): void
    {
        foreach ($messages as $message){
            $this->flashMessage($message, $type);
        }
    }
}

kterou přidáme do BasePresenteru pomocí use.

Proč vlastně máme s obyčejným předáním int parametru z presenteru do komponenty takové neočekávané obtíže? Vždyť máme ve frameworku vychytané předání parametru do akce, automatické předání a správné přetypování parametru z url do persistentních parametrů. Chyba je v tom že a) PHP neumí správně přetypovat null na int, použitelné přetypování null na int je zase null. b) pokud bychom chtěli mechanismy Nette získat správně přetypovaný parametr tak bychom šli přes akci a definici int property:

class MyPresenter extends BasePresenter
{
	private ?int $paginationPage;
	.....

	public function action<Action>(?int $paginationPage)
	{
		$this->paginationPage = $paginationPage;  // dočasné uschování, aby šlo použít v createComponentPaginator()
	}

	public function createComponentPaginator(): PaginatorControl
	{
		return $this->paginatorFactory->create($this->paginationPage);
	}
}

Řešení předat parametr autowiringem přes akci je nečitelný a nespolehlivý – komponentu můžeme použít ve více akcích

Použití #[Persistent] parametru se správným typem řeší vše – ALE – a) na toto není primárně určený, b) dělá víc než potřebujeme – persistuje v celém presenteru, což může vadit.

Nette jednoduše NEMÁ moderní odpověď na legitimní potřebu vývojářů předat pohodlně a čitelně int parametr z url presenterem do komponenty – včetně správného přetypování null.

Proto jsem si ped pár dny udělal traitu SmartNette, abych to příště nemusel řešit.

A co takhle systémové řešení – přidat dva atributy pro property presenteru?

Podobné problémy jako výše uvedeno by ideálně vyřešilo rozšíření Nette o jeden/dva šikovné moderní atributy ve stylu #[Persistent], což by nemuselo být složité a řešilo by to tyto drobné autowiringové problémy v presenteru.

Plánuji, že bych na to napsal drobné RFCčko, ale musím si to pořádně rozmyslet, aby to mělo hlavu a patu.

V hlavě mám tuto myšlenku na dva šikovné atributy:

// jako #[Persistent] injektuje z url a přetypuje, ale bez persistence – předání int parametru do komponenty
#[Local]
private ?int $paginationPage;

// jako #[Persistent] injektuje z url a přetypuje, persistence omezena na aktuální akci presenteru
#[ActionPersistent]
private ?int $parentId;

#[ActionPersistent] je moderní odpovědí na potřebu „protlačit“ parametr do post url formuláře (<form action=„…“ …>), ale pouze v rámci signálu formuláře – mimo zpracování formuláře – v jiné akci presenteru je tento parametr je nežádoucí. Potřeboval jsem to např., když jsem formulářem vytvářel nový článek do konkrétní kategorie ($categoryId = $parentId). Použil jsem z nouze #[Persistent], ale není to úplně ono.

BONUS – moderní, čitelný kód presenterů ve stylu di Nette

Uvedení parametru url, který není lokálně zpracován v akci jako property v url zapadá do trendu moderního PHP a moderních frameworků – presenter se k závislosti na url parametru „veřejně přizná“, na místě k tomu určeném, jedním vrzem se správně natypuje a je ready to use pro použití v komponentě nebo jinde.

@DavidGrudl – mám dotaz, zda se v užším týmu vývojářů Nette Frameworku uvažuje o podobném doplnění atributů ke stávajícímu #[Persistent]? Nette 4.0 je výzvou, ale tohle je bezproblémové, žádný BC break, to se dá přidat kdykoliv.

Editoval m.brecher (17. 10. 2022 18:06)

dakur
Člen | 493
+
+3
-

Otázka čitelnosti ternary operátoru je asi subjektivní záležitost, mně třeba toto přijde naprosto čitelné a používám to naprosto běžně. Když se na to podívám, vím ve dvou řádcích přesně, co to dělá, a to je pro mě podstatné.

protected function createComponentPaginator(): Paginator
{
	$page = $this->getParameter('paginationPage');
	return $this->paginatorFactory->create($page === null ? null : (int) $page);
}
Marek Znojil
Člen | 76
+
+1
-

Já si takové vstupní parametry dle potřeby přetypuji na začátku metody akce. Jak píše @dakur, ten řádek navíc mě nezabije a je to stále přehledné.