Přístup k parametrům z requestu v presenteru a komponentách
- David Grudl
- Nette Core | 8218
K parametrům z requestu se v presenterech a komponentách přistupuje nejčastěji skrze metody:
function renderDefault(int $id, string $slug = '')
{
//
}
Výhodou je pohodlí a především typová kontrola.
Pro přístup k parametru odjinud slouží metoda
getParameter('id')
, ale tam se ztrácí elegance a také typová
kontrola. Napadlo mě proto přidat anotaci, která by tohle vyřešila:
use Nette\Application\Attributes\Request;
class MyPresenter extends Nette\Application\UI\Presenter
{
#[Request]
public int $id;
function foo()
{
$this->model->getSomething($this->id);
}
}
V podstatě je to jakási neperzistetní obdoba anotace Persistent.
Máte k tomu nějaké připomínky nebo nápady?
- David Grudl
- Nette Core | 8218
Marek Bartoš napsal(a):
Já bych bral
#[RequestParameter(persistent: false)]
Jelikož to nebude mít konstruktor, nic by nemělo bránit to psát i takto.
- Marek Bartoš
- Nette Blogger | 1260
Tím jsem chtěl říct, že by bylo fajn mít konzistentní zápis pro
persistentní a ne-persistentní parametry. Tedy i
#[RequestParameter(persistent: true)]
a ne-persistentní variantu
mít výchozí.
Editoval Marek Bartoš (29. 9. 2023 17:53)
- David Grudl
- Nette Core | 8218
Jo, to se bude líp psát i číst, dobrý důvod zavést další atribut pro persistetní parametry.
- Pavel Kravčík
- Člen | 1194
Takže by mohlo fungovat i tohle, pokud přijdou parametry z ajaxu jako string a přetypují se?
use Nette\Application\Attributes\Request;
class MyPresenter extends Nette\Application\UI\Presenter
{
#[Request]
public int $articleId;
function handleBoo(int $articleId)
{
//getEntityByID
}
}
- m.brecher
- Generous Backer | 863
@DavidGrudl
Ano, toto je užitečná feature Nette a určitě ji každý občas použije.
V Nette dokonce již máme přístup k parametrům requestu pomocí atributu a sice atribut #[Persistent]:
#[Persistent]
public string $lang;
Atribut #[Persistent] nedělá nic jiného než navrhovaný atribut #[Request] s tím rozdílem, že parametru navíc přidá persistenci.
Plánovaný atribut #[Request] tak bude v logické dvojici s atributem #[Persistent], budeme mít pro přístup k request parametrům kompletní sadu dvou atributů:
#[Persistent]
string $lang; // persistentní parametr requestu
#[Request]
public int $id; // nepersistentní parametr requestu
Přehledné, logické.
Jediné co je zde matoucí je označení nepersistentního parametr requestu jako #[Request], zatímco persistentní parametr, který je také parametrem requestu označení Request nemá.
Protože tyto dva atributy tvoří logickou dvojici, navrhoval bych dát atributu #[Request] takový název, který by se komplementárně hodil k již existujícímu názvu #[Persistent], což by mohlo být třeba #[NonPersistent] nebo #[Local].
Potom bychom měli v záhlaví presenteru parametry pěkně logicky pod sebou:
#[Persistent]
string $lang;
#[Local]
public int $id;
// nebo:
#[NonPersistent]
public int $id;
Asi úplně nejlepší by bylo přidat do obou atributů slovo Parameter, potom by kód byl absolutně nejčitelnější:
#[PersistentParameter]
string $lang;
#[LocalParameter]
public int $id;
Atribut #[PersistentParameter] by mohl být aliasem k již existujícímu atributu #[Persistent], takže by to nemusel hned být BC a až časem by se původní název atributu mohl vyřadit
Editoval m.brecher (29. 9. 2023 22:58)
- dsar
- Backer | 53
I partially agree with m. brecher. What I liked about Presenters here in Nette is that it's (mostly) abstracted-away from the concept of Request and Response.
Furthermore having #[Persistent] and #[Request] (or #[RequestParameter]) is non-orthogonal.
The contrary of #[Persistent] would be #[Volatile] or #[Mutable]
- Pavel Kravčík
- Člen | 1194
Pavel Kravčík napsal(a):
Takže by mohlo fungovat i tohle, pokud přijdou parametry z ajaxu jako string a přetypují se?
use Nette\Application\Attributes\Request; class MyPresenter extends Nette\Application\UI\Presenter { #[Request] public int $articleId; function handleBoo(int $articleId) { //getEntityByID } }
V některých projektech jsem si oblíbil v něco podobného:
naja.makeRequest('GET', {link dateAdd!, dateFrom => 'DATE_FROM', dateTo => 'DATE_TO'}.replace('DATE_FROM', dateFrom).replace('DATE_TO', dateTo));
Pokud bych si mohl vynutit, že to bude přetypováno na int třeba atributem, bylo by to super.
- m.brecher
- Generous Backer | 863
@PavelKravčík
Pokud bych si mohl vynutit, že to bude přetypováno na int třeba atributem, bylo by to super.
Definice typu parametru requestu se provádí typehintem v akci nebo v deklaraci parametru jako property presenteru. Nedává moc smysl deklarovat parametr requestu $id jako property a v akci nepoužít $this->id, ale deklarovat parametr duplicitně přímo v akci – jak máš v ukázce kódu.
Mě dává smysl takovéto použití:
use Nette\Application\Attributes\Request;
class MyPresenter extends Nette\Application\UI\Presenter
{
#[Request]
public int $articleId;
function handleBoo()
{
getEntityByID($this->id);
}
}
Editoval m.brecher (1. 10. 2023 22:32)
- m.brecher
- Generous Backer | 863
@DavidGrudl
Po diskuzi v Českých Budějovicích souhlasím, že se název atributu #[Local] nehodí a naopak nejlépe zvolený mě připadá #[Request]. Podstatná informace je, že se jedná o parametr requestu. Nekomplementárnost názvu persistentního parametru #[Persistent] by se dala řešit přejmenováním #[Persistent] na #[PersistentRequest].
V této souvislosti upozorňuji, že není v Nette presenterech uspokojivě dořešena otázka persistentního parametru pro držení stavu formuláře. Pokud dnes chci držet v presenteru pro formulář parametr, který by v rámci jedné akce byl persistentní v opakovaných submitech formuláře (např. dynamické přidávání prvků do formuláře), použiji pro to #[Persistent]. Jenže tak jak je #[Persistent] nyní implementován, bude takový parametr persistentní napříč všemi akcemi presenteru, což není žádoucí chování. Ideální by byl atribut na míru formuláře persistentní právě v jedné akci presenteru – v té, ve které je použit. V budoucnu by tak mohly v Nette být 3 různé parametry předané do presenteru pomocí atributů, kde by bylo ideální, aby měly všechny v názvu #[…Request], atribut speciálně pro stav formuláře by se mohl jmenovat třeba #[FormPersistentRequest]
- m.brecher
- Generous Backer | 863
@dsar
it would be better to have #[RequestParameter] or even better #[Parameter] with (persistent: true)
the word Parameter is redundant, in case of attributes there is clear from the syntax that it is parameter, second half of your post is interesting fresh idea, so I propose to consider these variants:
// persistent:
#[Request(persistent: true)]
public string $lang
// or
#[RequestPersistent]
public string $lang
// or
#[PersistentRequest]
public string $lang
// non persistent:
#[Request]
public int $id
Editoval m.brecher (2. 10. 2023 3:43)
- Pavel Kravčík
- Člen | 1194
@mbrecher
Definice typu parametru requestu se provádí typehintem v akci nebo v deklaraci parametru jako property presenteru. Nedává moc smysl deklarovat parametr requestu $id jako property a v akci nepoužít $this->id, ale deklarovat parametr duplicitně přímo v akci – jak máš v ukázce kódu.
Ano, to je jasný v presenteru. Chtěl bych to v komponentě. Ale je to trochu úkrok stranou od původního vlákna.
/?foo-dateFrom=10&foo-dateTo=20&do=foo-ping
bych chtěl
obojí dostat jako int (přijde jako string z ajaxu). Pokud bych to dokázal
vynutit parametrem v komponentě, bylo by to super.
- David Grudl
- Nette Core | 8218
Když tak nad tím přemýšlím, jelikož je to náhrada za
getParameter()
, adekvátní anotace by byla:
#[Parameter]
public int $id;
Kdo by to považoval za příhodné, mohl by psát
#[Persistent, Parameter]
, ale samozřejmě samostatné Persistent
bude fungovat dál.
- m.brecher
- Generous Backer | 863
@PavelKravčík @DavidGrudl
Ano, to je jasný v presenteru. Chtěl bych to v komponentě.
V ukázce kódu co Jsi poslal není komponenta, ale presenter s akcí, proto jsem tu poznámku nepochopil.
Ano, v komponentě by se úplně stejně hodilo mít atribut #[Parameter] (poslední Davidův návrh na název), který by fungoval analogicky jako již existující atribut #[Persistent] v komponentě, jenom by parametr nebyl persistentní.
Dnes persistentní parametr v komponentě co se týče typování funguje výtečně, předpokládám, že by se analogicky implementoval i #[Parameter]:
// dnes #[Persistent] přetypuje hezky
class MyControl extends Nette\Application\UI\Control
{
#[Persistent]
public int $id;
public function handleSomething()
{
$this->id // integer
$this->getParameter('id') // také integer
}
}
// zítra #[Parameter] by přetypoval stejně
class MyControl extends Nette\Application\UI\Control
{
#[Parameter]
public int $id;
public function handleSomething()
{
$this->id // integer
$this->getParameter('id') // také integer
}
}
Předpokládám, že plánovaný atribut bude jak v presenteru, tak v komponentách.
Editoval m.brecher (2. 10. 2023 18:54)
- David Grudl
- Nette Core | 8218
Předpokládám, že plánovaný atribut bude jak v presenteru, tak v komponentách.
Název tohoto vlákna a první věta úvodního postu by v tom mohla udělat trošku jasno
- David Grudl
- Nette Core | 8218
Už je to k otestování ve vývojové verzi:
composer require nette/application:^3.1.x-dev
- Pavel Kravčík
- Člen | 1194
@DavidGrudl: Otestoval jsem a funguje to ok (s DI 3.1.6).
Akorát trochu mi zlobí jedna věc, možná to někdo řeší elegantněji.
Chápu, že je to asi nesmysl v JS a není to extra důležité a dá se to
snadno obejít (int|string
), ale kdyby měl někdo nějaký nápad
nebo by Nette tenhle případ přetypovalo
(intval('PARAM_FROM')===0
) bylo by to super.
<?php
declare(strict_types=1);
use Nette\Application\Attributes\Parameter;
class Foo extends \Nette\Application\UI\Control
{
#[Parameter]
public int $from = 10;
#[Parameter]
public int $to = 20;
#[Parameter]
public int $limit = 0;
public function handlePing(): void
{
//output (dump $this->from, $this->to, $this->limit)
}
public function handleBing(int $from, int $to): void
{
//output (dump $this->from, $this->to, $this->limit)
}
}
// JS doplňuje hodnoty intFrom = 40, intTo = 50
Na téhle variantě se mi nelíbí '0'
jako zástupný znak
(s tím zástupným znakem poslední úryvek funguje):
naja.makeRequest('GET', {link ping!, from => '0', to => '1'}.replace('0', intFrom).replace('1', intTo));
//output: 40, 50, 0
Pokud nahradím za něco lepšího, funguje ok:
naja.makeRequest('GET', {link ping!, from => 'PARAM_FROM', to => 'PARAM_TO'}.replace('PARAM_FROM', intFrom).replace('PARAM_TO', intTo));
//output: 40, 50, 0
Akorát se mi nelíbí možnost, upsat se:
naja.makeRequest('GET', {link ping!, from => 'PARAM_FROM', too => 'PARAM_TO'}.replace('PARAM_FROM', intFrom).replace('PARAM_TO', intTo));
//output: 40, 20, 0
Takže by se mi líbilo vynutit si poslání parametrů (např
2 z 3 výše handleBing
) a vynutit jeho přetypování:
naja.makeRequest('GET', {link bing!, from => 'PARAM_FROM', too => 'PARAM_TO'}.replace('PARAM_FROM', intFrom).replace('PARAM_TO', intTo));
// output: ERR - must be int, string given
// očekávaný output: ERR - Missing parameter $to required
Finální kód by se mi líbil:
naja.makeRequest('GET', {link bing!, from => 'PARAM_FROM', to => 'PARAM_TO'}.replace('PARAM_FROM', intFrom).replace('PARAM_TO', intTo));
// output: 40, 50, 0
Editoval Pavel Kravčík (19. 10. 2023 14:14)
- dakur
- Člen | 493
Jo, tohle jsme taky řešili, že potřebujeme v nette vygenerovat linky s placeholdery a potom je replacovat až v js. My máme všechny parametry jako value objecty, takže potom místo elegantního:
public function actionDefault(CampaignId $campaignId, MaterialId $materialId, PageId $pageId) {}
musíme používat:
public function actionDefault(CampaignId $campaignId, MaterialId $materialId, string $pageId) {
// a restournout to do VO ručně
$pageId = PageId::of($pageId);
}
aby byl router schopen sestavit placeholder link:
$this->link('Something:actionDefault', [
SomethingPresenter::PARAM_CAMPAIGN_ID => $campaignId,
SomethingPresenter::PARAM_MATERIAL_ID => $materialId,
SomethingPresenter::PARAM_PAGE_ID => '%placeholder%',
]);
Kdyby na to byl v nette nějaký mechanismus, bylo by to super, ale jak píšeš, je to asi spíš nice-to-have.
- m.brecher
- Generous Backer | 863
Návrh – implementovat parametr s atributem #[Parameter] jako stavový
Nový atribut #[Parameter] zpřehlední kód a zajistí správné typování. Navrhuji doplnit důležitou feature z hlediska existujícího komponentového modelu Nette. Parametry deklarované pomocí #[Parameter] by měly být automaticky zahrnuty do stavu akce presenteru (stavu komponenty).
#[Parameter]
public ?string $param = null;
by měl být zahrnut do aktuálního stavu akce stejně jako:
public function actionUpdate(?string $param = null)
{}
Argumenty:
Presenter
V presenteru je parametr s atributem #[Parameter] obdobou parameru akce. Není sice v akci deklarovaný, je ale deklarovaný v presenteru a v aktuální akci jej lze použít (libovolné akci). Je proto ze své podstaty součástí aktuálního stavu presenteru , kam patří presenter, akce a parametry akce (aktuální view).
V aktuální verzi Nette parametr #[Parameter] součástí aktuálního stavu presenteru není a signál presenteru, nebo submit formuláře parametr vymaže. To je v rozporu s principem komponentového modelu Nette, že signály komponent NEMĚNÍ aktuální stav akce presenteru.
Proto by bylo vhodné, aby parametr s atributem #[Parameter] byl součástí aktuálního stavu presenteru, a NEMĚNIL se signálem komponenty/submitu formuláře.
Je to navíc i chybějící užitečná funkcionalita pro formuláře. Pokud potřebujeme v parent komponentě formuláře řídící parametr pro formulář (přidání inputu, …), potom je jediné řešení deklarovat jej jako #[Persistent], což má vedlejší efekt – persistenci, která je na obtíž. Tento návrh to elegantně řeší.
Komponenta
V komponentě je situace s #[Parameter] parametrem obdobná jako v presenteru. Rozdíl je v tom, že komponenty nemají akce, ale mají stejně jako presentery stav, který by podřízené komponenty, včetně formulářů neměly měnit. Součástí stavu komponenty jsou stavové parametry komponenty – nyní persistentní parametry komponenty.
Pokud potřebujeme, aby subkomponenty neměnily aktuální stav komponenty, použijeme v komponentě #[Persistent] parametry. Stejně jako v presenteru ale tento atribut dělá víc než obvykle potřebujeme. Zajistí, že subkomponenty nemohou parametr měnit, ale přidají nadbytečnou funkci – persistence v presenteru, parametr se předá i do signálů nadřazené komponenty – to může být nežádoucí.
Tedy i v komponentách by bylo vhodné, aby #[Parameter] parametry byly součástí aktuálního stavu komponenty. Subkomponenty by je nemohly měnit, ale do odkazů nadřazených komponent by se nepřenášely.
Ajax
V ajaxovém payloadu se z Nette aplikace odesílá vedle položky snippets i položka state, kde jsou v současné době přenášeny persistentní parametry. Stavové parametry se mohou hodit v javascriptu zpracovávajícím ajaxový response. Proto si myslím, že by se #[Parameter] parametry měly předávat i v payloadu jako součást stavu presenteru/komponenty.
- Ages
- Člen | 128
Možná jsem to špatně pochopil, ale pokud by toto mohl být alternativní způsob zápisu parametrů, jak vyřeším případný redirect v Presenteru?
final class OrderPresenter extends Presenter
{
#[Parameter]
public int $orderId = 0;
...
public function actionFoo(): void
{
bdump($this->orderId);
}
public function actionBar(): void
{
$entity = $this->model->getById($this->orderId);
$this->redirect('foo', $entity->id); // Toto vyhodí vyjímku: Nette\Application\UI\InvalidLinkException
}
}
- m.brecher
- Generous Backer | 863
@Ages
jak vyřeším případný redirect v Presenteru?
Nezkoušel jsem to, ale řekl bych, že se to vyřeší tím, že se paralelně k deklaraci parametru pomocí #[Parameter] uvede parametr jako parametr příslušné akce:
public function actionFoo(int $orderId = 0): void
{
bdump($this->orderId);
}
Je to v podstatě ten samý problém, který popisuji zde: https://forum.nette.org/…komponentach#…
Atribut #[Parameter] je výborná myšlenka, ale není v této chvíli příliš funkční – problém se submitem formuláře, problém s redirectem. Aby se dal bezproblémově používat, je potřeba, aby byl frameworkem považován za parametr aktuální akce.
- Ages
- Člen | 128
@mbrecher
Jasně toto funguje.
Skutečně jsem to asi špatně pochopil,nemá jít o náhradu parametrů
v metodě ale pouze o alternativní přístup k parametru mimo metodu.
Jen mě překvapilo, že v Latte mohu ten parametr přidat i když není definovaný na vstupu akce, ale redirect toto nepovoluje.
final class OrderPresenter extends Presenter
{
#[Parameter]
public int $orderId = 0;
...
public function actionFoo(): void
{
bdump($this->orderId);
}
public function actionBar(): void
{
$entity = $this->model->getById($this->orderId);
$this->redirect('foo', $entity->id); // Toto vyhodí vyjímku: Nette\Application\UI\InvalidLinkException
}
}
/--latte
<a n:href="foo, $id">Akce</a> <! -- Toto nevyvolá chybu -->
\--
- Marek Bartoš
- Nette Blogger | 1260
@Ages Ty se spoléháš na pořadí, namísto na názvy parametrů, což může fungovat pro parametry funkce, ale už ne pro properties. Plus by měly být parametry odkazu pole.
$this->redirect('foo', ['orderId' => $entity->id]);
Ten vygenerovaný odkaz v Latte ti skutečně funguje? Výchozí chování při neplatném odkazu je myslím warning, ne exception.
Editoval Marek Bartoš (6. 2. 10:24)
- Ages
- Člen | 128
Tak, teď mi to je už jasné :-D
Toto veče vyzkouším?
$this->redirect('foo', ['orderId' => $entity->id]);
V latte jsem to měl uvedené s názvem parametru, jen jsem to sem dopsal
ve zkrácené verzi.
Pak by tedy bylo vše ok a chovalo by se to v obou případech stejně.
@MarekBartoš děkuji za vysvětlení.
- m.brecher
- Generous Backer | 863
@Ages
Skutečně jsem to asi špatně pochopil, nemá jít o náhradu parametrů v metodě ale pouze o alternativní přístup k parametru mimo metodu.
Je to tak. Praktická využitelnost takovéto funkce je ovšem otázkou. Má neočekávané chování, že když se deklaruje parametr pomocí #[Parameter] a odešle v nějaké akci formulář, tak parametr zmizí – podobně jako u toho přesměrování. Očekávané chování by bylo, že je to syntakticky jiný způsob deklarace parametru akce.
Copak v presenteru, tam jednoduše použiju starý zápis a je to vyřešeno, ale v komponentách akce nemáme a tak když chci zajistit, aby nějaký parametr přežil submit formuláře, tak mám jedinou možnost použít #[Persistent]. Ten ovšem vše komplikuje, protože přidává nežádoucí persistenci, což vadí, musí se to potom pečlivě ošetřovat v odkazech, jinak komponenty dělají psí kusy. Takže atribut #[Parameter], který by byl plnokrevným stavovým atributem v Nette vyloženě chybí a v komponentách ho není de facto jak nahradit. Protože použít #[Persistent] je jenom dočasné řešení.
Editoval m.brecher (6. 2. 13:05)
- Marek Bartoš
- Nette Blogger | 1260
Jestli se #[Parameter] nechová stejně jako parametr action/render metody, tak bych to považoval za bug a bylo by fajn založit na to issue, ideálně s jednoduchou demonstrací problému