RFC - tichý konflikt parametrů akce s parametry signálu

- m.brecher
- Generous Backer | 926
Ahoj!
Nette fórum obsahuje řadu vláken věnovaných problémům se signály. Jedním z opakujících se problémů jsou kolize parametrů – signál buď nedostane parametr, který očekává, nebo naopak přepíše parametr akce. Poslední podrobně zdokumentovaný případ popsal @SamuelThorn zde:
https://forum.nette.org/…i-nazvu-ajax
Parametry akcí i signálů presenteru se ukládají do stejného pole Component::$params. Konflikt navíc nevzniká jako chyba. Framework jej vyřeší tichým přepsáním hodnoty, což výrazně ztěžuje jeho odhalení i následné debugování.
Test aktuálního chování:
final class HomePresenter extends BasePresenter
{
public function actionHome(?int $count = null): void
{
bdump('action__' . $count);
}
public function handlePing(int $count): void
{
bdump('signal__' . $count);
}
}
latte:
<a data-ajax n:href="ping!, count: 2">ping</a>
url:
http://localhost/dev/tex/admin?count=1
dump reload stránky:
'action__1'
dump po kliknutí na signál ping:
'action__2'
'signal__2'
očekávané chování:
'action__1'
'signal__2'
Závěr testu:
Parametr signálu potichu přepíše parametr akce. Parametry akce a signálu představují dva nezávislé zdroje dat. Architektonicky žádoucí je proto jejich oddělený scope, aby se vzájemně nemohly ovlivňovat.
Popis řešení
Nette prefixuje parametry signálu component-parameter. Pokud je handler signálu přímo v presenteru, pak prefix chybí a parametry signálů se tlučou s parametry akcí.
Navrhuji přidat speciální prefix pro parametry signálu presenteru a tím vytvořit izolovaný scope od parametrů akce. Prefix musí fungovat obousměrně. Navrhuji dvě změny v nette codebase:
a) Nette\Application\LinkGenerator::createRequest() – úplně na konci metody
// začátek změn
// if ($args && $component !== $refPresenter) {
// $prefix = $component->getUniqueId() . UI\Component::NameSeparator;
if ($args && !str_contains($signal, UI\Component::NameSeparator)) {
$uniqueId = $component === $refPresenter ? '__signal' : $component->getUniqueId();
$prefix = $uniqueId . UI\Component::NameSeparator;
// konec změn
foreach ($args as $key => $val) {
unset($args[$key]);
$args[$prefix . $key] = $val;
}
}
Jsme v místě, kde Nette prefixuje parametry signálu komponenty. Pokud komponenta není presenter, přidá ke jménu parametru prefix, pokud je presenter nechá parametr bez prefixu. Přidáme speciální prefixování ‚__signal‘ i pro parametry presenteru. Současně ve vstupní podmínce ošetříme již prefixovaný link na volání signálu komponenty z presenteru (n:href=„MyControl-ping!, MyControl-count: 2“)
b) Nette\Application\UI\Component::signalReceived()
public function signalReceived(string $signal): void
{
// začátek změn
// if (!$this->tryCall(static::formatSignalMethod($signal), $this->params)) {
$method = static::formatSignalMethod($signal);
$params = $this->params;
$presenter = $this->getPresenter();
if ($presenter) {
$signalParams = $presenter->popGlobalParameters($this->getParameterId('__signal'));
if ($signalParams && static::getReflection()->hasMethod($method)) {
foreach (static::getReflection()->getMethod($method)->getParameters() as $reflParam) {
$name = $reflParam->getName();
if (array_key_exists($name, $signalParams)) {
$params[$name] = $signalParams[$name];
}
}
}
}
if (!$this->tryCall($method, $params)) {
// konec změn
$class = static::class;
throw new BadSignalException("There is no handler for signal '$signal' in class $class.");
}
}
Upravujeme těsně před voláním metody handleru signálu, protože přímo v tryCall() to už nejde, je to metoda sdílená i pro jiné scénáře než signál. Protože jsou parametry signálu prefixované, nejsou v $this->params. Vyzvedneme si je pomocí Presenter::popGlobalParameters() a ověříme, že je handler signálu skutečně očekává. Parametry signálu se přidávají do lokální proměnné $params, nikoliv do $this->params, kde jsou parametry akce presenteru.
Zpětná kompatibilita
Navržené změny ovlivní pouze aplikace které (vědomě či nevědomě) spoléhají na automatické přepsání parametrů akce signálem. Odhaduji, že takových aplikací bude minimum.
Jsem přesvědčen, že signály Nette si navrženou opravu zaslouží i přes případné drobné problémy se zpětnou kompatibilitou.
Uvítám jakékoliv relevantní komentáře k navrženým úpravám.
Editoval m.brecher (4. 7. 4:06)
- David Grudl
- founder | 8320
Tady se dá možná udělat to, že při shodě názvu parametru v akcích a signálech se vyhodí notice, ale v žádném případě není možné měnit tohle chování a rozbít existující aplikace.
- Kamil Valenta
- Člen | 851
m.brecher napsal(a):
dump po kliknutí na signál ping:
'action__2' 'signal__2'
A třeba z mého pohledu je tohle naopak silně očekávané chování. GET
parametr je stále jen jeden,
pokud mám v actioně i handlu vždy „count“, tak asi vím proč a
rozhodně neočekávám, že když si v linku
explicitně řeknu o count=2, že mi před tím profrčí něco
s count=1…
latte:
<a data-ajax n:href="ping!, count: 2">ping</a>
Zkus napsat, jak by výsledná URL, kterou vrátí makro, vypadala. Z té URL poznáš, že není žádný důvod, aby actiona proběhla s jinou hodnotou count než handle.
Parametr signálu potichu přepíše parametr akce.
„Bližší košile než kabát“. Nehledal bych v tom žádnou záludnost.
Parametry akce a signálu představují dva nezávislé zdroje dat. Architektonicky žádoucí je proto jejich oddělený scope, aby se vzájemně nemohly ovlivňovat.
Tohle určitě neplatí takto obecně. A když budeš chtít handlem opravdu přepsat parametr v actioně, budeš po navrhované úpravě namydlený…

- m.brecher
- Generous Backer | 926
@KamilValenta
Tohle určitě neplatí takto obecně. A když budeš chtít handlem opravdu přepsat parametr v actioně, budeš po navrhované úpravě namydlený
Proto jsem také poslal RFC, abych věc prodiskutoval v komunitě. Chování které je v FW desítky let, se může stát součástí komunitního know how.
V dokumentaci by se mělo existující chování parametrů popsat. Mě naopak vůbec nenapadlo, že parametr signálu je totéž jako parametr akce. To je vlastně nejlepší řešení – chování je záměrně navržené a zjednodušuje řízení parametrů akce ze signálu, přidáme dvě věty do dokumentace a všechno je v pořádku.
A co ignorování POST parametrů signálu jak popisuje @SamuelThorn ? Asi ani zde nemá cenu měnit chování po desítkách let používání. I toto chování by se mělo zdokumentovat.
Editoval m.brecher (Dnes 13:03)