Přístup k parametrům z requestu v presenteru a komponentách

David Grudl
Nette Core | 8035
+
+8
-

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?

hrach
Člen | 1831
+
+2
-

Na prvni pohled mi nesedi jmeno. Mozna neco jako ByRequest/FromRequest

mystik
Člen | 276
+
+4
-

Nebo #[RequestParameter]

Marek Bartoš
Nette Blogger | 1087
+
+3
-

Já bych bral #[RequestParameter(persistent: false)]

David Grudl
Nette Core | 8035
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
	}
}
David Grudl
Nette Core | 8035
+
0
-

Můžeš to rozvést?

m.brecher
Generous Backer | 634
+
0
-

@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
+
0
-

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
+
0
-

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
+
0
-

@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
+
0
-

@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]

dsar
Backer | 52
+
+2
-

#[Request] is excessively non-orthogonal with #[Persistent].

At this point it would be better to have #[RequestParameter] or even better #[Parameter] with (persistent: true) for the persistent one and deprecate #[Persistent] (or having it as alias)

m.brecher
Generous Backer | 634
+
0
-

@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
+
+1
-

@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.

dsar
Backer | 52
+
0
-

m.brecher napsal(a):
the word Parameter is redundant, in case of attributes there is clear from the syntax that it is parameter

It's not clear from the syntax, since we may have other attributes such as CrossOrigin (and in future AllowedFor)

David Grudl
Nette Core | 8035
+
+11
-

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
+
0
-

@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
+
+4
-

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
+
+2
-

Už je to k otestování ve vývojové verzi:

composer require nette/application:^3.1.x-dev
Pavel Kravčík
Člen | 1153
+
0
-

@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
+
+1
-

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
+
0
-

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.