Přístup k parametrům z requestu v presenteru a komponentách
- David Grudl
- Nette Core | 8035
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 | 8035
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 | 1087
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. 17:53)
- David Grudl
- Nette Core | 8035
Jo, to se bude líp psát i číst, dobrý důvod zavést další atribut pro persistetní parametry.
- Pavel Kravčík
- Člen | 1153
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 | 634
@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. 22:58)
- dsar
- Backer | 52
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 | 1153
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 | 634
@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. 22:32)
- m.brecher
- Generous Backer | 634
@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 | 634
@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. 3:43)
- Pavel Kravčík
- Člen | 1153
@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 | 8035
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 | 634
@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. 18:54)
- David Grudl
- Nette Core | 8035
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 | 8035
Už je to k otestování ve vývojové verzi:
composer require nette/application:^3.1.x-dev
- Pavel Kravčík
- Člen | 1153
@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. 14:14)
- dakur
- Člen | 469
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 | 634
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.