Homepage prezentér neumí vytvořit odkaz na vlastní signál v akci default

DefenestrationPraha
Člen | 127
+
0
-

Balíček nette/routing mám ve verzi 3.0.4, což je asi poslední verze.

Mám následující jednoduché routování:

final class RouterFactory
{
    use Nette\StaticClass;

    private const DEFAULT_ROUTE = 'Homepage:default';

    public static function createRouter(): RouteList
    {
        $router = new RouteList;
        $router->addRoute('login', 'Login:default');
        $router->addRoute('logout', 'Logout:default');
        $router->addRoute('submit', 'Submit:default');
        $router->addRoute('<presenter>/<action>[/<id \d+>]',  self::DEFAULT_ROUTE);
        /* Subdomains */
        return $router;
    }
}

a v rámci HomepagePresenter.php tento kód:

final class HomepagePresenter extends ForwarderProtectedPresenter
{
    /**
     * @inject
     */
    public ForwarderTelemetryFactory $telemetryFactory;

    public function renderDefault() : void
    {
    }

	public function handleTelemetry(string $class, string $id) : void {
        if ($this->isAjax()) {
            $snippetsToRedraw = array('page-body-snippet-area');
			... blahblah
            if (count($snippetsToRedraw) > 1) {
                foreach ($snippetsToRedraw as $snippetName) {
                    $this->redrawControl($snippetName);
                }
            }
        }
    }


    public function createComponentSmsGatewayTelemetry() : Control {
		... blahblah ...
		$this->link('telemetry!', array('class' => '', 'id' => ''));
    }
}

Tohle volání mi spolehlivě selže s hláškou

**User Warning

Invalid link: No route for Homepage:default(class=smsgatewaytelemetry, id=sms-gateway-telemetry, do=telemetry) search► skip error►

**

A problém je reálný, příslušný asynchronní indikátor se neaktualizuje, odkaz na signál nevznikl. Problém dokážu spravit přidáním jedné řádky do RouterFactory:

final class RouterFactory
{
    use Nette\StaticClass;

    private const DEFAULT_ROUTE = 'Homepage:default';

    public static function createRouter(): RouteList
    {
        $router = new RouteList;
        $router->addRoute('login', 'Login:default');
        $router->addRoute('logout', 'Logout:default');
        $router->addRoute('submit', 'Submit:default');
		$router->addRoute('', 'Homepage:default'); /** Toto jsem musel pridat **/
        $router->addRoute('<presenter>/<action>[/<id \d+>]',  self::DEFAULT_ROUTE);
        /* Subdomains */
        return $router;
    }
}

ale přijde mi to jako chybné, nebo aspoň nečekané chování od routeru. Proč bych měl nastavovat routu na prázdný řetězec, aby mi začaly fungovat signály?

Běžný přístup na výchozí routu funguje (http://muj-vlastni-server-s-tajemnou-adresou.net/). Selhávají jenom signály.

F.Vesely
Člen | 369
+
0
-

Podle mě tam bude problém v tom, že v routě máš parametr id a v signálu ho máš taky.

m.brecher
Generous Backer | 872
+
0
-

@DefenestrationPraha

Přidání routy '' je správný postup řešící problém – viz Tvůj kód:

$router->addRoute('', 'Homepage:default');
$router->addRoute('<presenter>/<action>[/<id \d+>]',  'Homepage:default');

V obecné routě máš na třetí pozici volitelný parametr id. Pokud je parametr id v signatuře akce/signálu, potom není volitelný ale povinný a router bere jako platnou takovou url, kde je id na 3. pozici. Protože jinak by nešlo správně namapovat jednotlivé části requestu na url. Na tom nic nemění uvedení parametru pro defaultní akci/presenter ‚Homepage:default‘ mapovanou na '' – toto pravidlo se nepoužije. Pokud parametr id není v signatuře akce/signálu, Router použije pravidlo mapování defaultní akce/presenteru ‚Homepage:default‘ na ''.

Proto Ti routa bez signálu fungovala a se signálem ne. Signál přidal do signatury akce parametr id.

Ano, není to ideální chování – jeden konkrétní kód routy produkuje různá url v závislosti na tom, zda je parametr v signatuře akce nebo ne. A dokonce signál přidává parametr do signatury akce také, což je pro mě novinka. Ale není to chyba, je to standardní chování Nette Routeru.

Poučení z Tvého postu je, že je potřeba si dát pozor na nepovinné parametry kombinované s defaultní akcí/presenterem. Fungovat to může bez problémů, ale je potřeba to řešit specifickou routou, která předchází obecnou, třeba takto:

$router->addRoute('[<id \d+>]',  'Homepage:default');
$router->addRoute('<presenter>/<action>[/<id \d+>]',  'Homepage:default');  // 'Homepage:default' bude vždy zachycen výše

Existuje fígl, jak předejít problému s povinnou pozicí parametru id. Použiješ u routy parametru prefix, ten umožní routeru, aby mapoval parametr bez ohledu na pozici v routě:

$router->addRoute('<presenter>/<action>[/id-<id \d+>]',  'Homepage:default');

Parametry, které používají signály bych nicméně do routy vůbec nedával. Takovéto url:

/presenter/akce/33?do=signal

, kde parametr id (33) používá signál je matoucí. Očekával bych, že id je parametr použitý v akci.

Naproti tomu:

/presenter/akce?do=signal&id=33

Je čitelné a srozumitelné, hned vidím, že akce parametr nemá a signál má parametr id.

Editoval m.brecher (26. 5. 2023 0:26)

DefenestrationPraha
Člen | 127
+
0
-

m.brecher napsal(a):

@DefenestrationPraha

Přidání routy '' je správný postup řešící problém – viz Tvůj kód:

$router->addRoute('', 'Homepage:default');
$router->addRoute('<presenter>/<action>[/<id \d+>]',  'Homepage:default');

V obecné routě máš na třetí pozici volitelný parametr id. Pokud je parametr id v signatuře akce/signálu, potom není volitelný ale povinný a router bere jako platnou takovou url, kde je id na 3. pozici. Protože jinak by nešlo správně namapovat jednotlivé části requestu na url. Na tom nic nemění uvedení parametru pro defaultní akci/presenter ‚Homepage:default‘ mapovanou na '' – toto pravidlo se nepoužije. Pokud parametr id není v signatuře akce/signálu, Router použije pravidlo mapování defaultní akce/presenteru ‚Homepage:default‘ na ''.

Proto Ti routa bez signálu fungovala a se signálem ne. Signál přidal do signatury akce parametr id.

Ano, není to ideální chování – jeden konkrétní kód routy produkuje různá url v závislosti na tom, zda je parametr v signatuře akce nebo ne. A dokonce signál přidává parametr do signatury akce také, což je pro mě novinka. Ale není to chyba, je to standardní chování Nette Routeru.

Poučení z Tvého postu je, že je potřeba si dát pozor na nepovinné parametry kombinované s defaultní akcí/presenterem. Fungovat to může bez problémů, ale je potřeba to řešit specifickou routou, která předchází obecnou, třeba takto:

$router->addRoute('[<id \d+>]',  'Homepage:default');
$router->addRoute('<presenter>/<action>[/<id \d+>]',  'Homepage:default');  // 'Homepage:default' bude vždy zachycen výše

Existuje fígl, jak předejít problému s povinnou pozicí parametru id. Použiješ u routy parametru prefix, ten umožní routeru, aby mapoval parametr bez ohledu na pozici v routě:

$router->addRoute('<presenter>/<action>[/id-<id \d+>]',  'Homepage:default');

Parametry, které používají signály bych nicméně do routy vůbec nedával. Takovéto url:

/presenter/akce/33?do=signal

, kde parametr id (33) používá signál je matoucí. Očekával bych, že id je parametr použitý v akci.

Naproti tomu:

/presenter/akce?do=signal&id=33

Je čitelné a srozumitelné, hned vidím, že akce parametr nemá a signál má parametr id.

To je tedy pěkně hluboký problém. Zároveň se mi tím vysvětlilo, proč druhá, sousední aplikace ten problém neměla. Tam totiž ten signál existuje jen v Admin:Homepage:default a User:Homepage:default, ale ne v Basic:Homepage:default.


	public const DEFAULT_ROUTE = 'Basic:Homepage:default';

	public static function createRouter(): RouteList
	{
		$router = new RouteList;
		$router->addRoute('login', 'Basic:Login:default');
		$router->addRoute('logout', 'Basic:Logout:default');
        $router->addRoute('submit', 'Basic:Submit:default');
        $router->addRoute('ping', 'Basic:Ping:default');
        $router->addRoute('query', 'Basic:Query:default');
        $router->addRoute('admin', 'Admin:Homepage:default');
        $router->addRoute('user', 'User:Homepage:default');
		$router->addRoute('[<module>[/<presenter>[/<action>[/<id \d+>]]]]',  self::DEFAULT_ROUTE);
        /* Subdomains */
        return $router;
	}
m.brecher
Generous Backer | 872
+
0
-

To je tedy pěkně hluboký problém.

Na ty dvě věci by asi bylo dobré upozornit v dokumentaci k Routeru:

a) pravidlo mapování defaultní akce/presenteru se nepoužije, pokud je v url mapován parametr za presenterem/akcí, který je použit v signatuře akce

b) totéž platí pro parametry signálu presenteru

Zkontroluji, zda to v dokumentaci nechybí a popř. pošlu PR na doplnění.

Kamil Valenta
Člen | 820
+
0
-

m.brecher napsal(a):

pravidlo mapování defaultní akce/presenteru se nepoužije, pokud je v url mapován parametr

To si nemyslím, že tak je. Problém byl jen v tom, že v routě bylo id jako volitelné a v signálu/akci povinné. Když se hledala routa směrem ven, žádná s povinným id se nenašla a router na to prostě jen správně upozornil.
Pokud by to bylo jednotné (povinné v routeru i akci nebo volitelné v routeru i akci), žádný problém by nebyl.

m.brecher
Generous Backer | 872
+
0
-

@KamilValenta

@KamilValenta Problém byl jen v tom, že v routě bylo id jako volitelné a v signálu/akci povinné.

Ta souvislost tam je, ale není to příčina problému. Naopak, parametr v routě volitelný, v akci povinný je běžně používané řešení. Já příčinu vidím v kombinaci dvou funkcí routeru ( a) + b) ) v jedné routě:

a) volitelný parametr v routě

$router->addRoute('admin/<presenter>/<action>[/<id>]');  // dle potřeby parametr v akcích uvedeme/neuvedeme

b) defaultní presenter/akce

$router->addRoute('admin/<presenter>/<action>', 'AdminHome:default');

c) kombinace a) + b) – potenciálně problematická

$router->addRoute('admin/<presenter>/<action>[/<id>]', 'AdminHome:default');

Routa c) je jasná, jednoduchá ALE funguje pod podmínkou, že v defaultní akci/signálu NEPOUŽÍVÁME parametr z routy (skrytá závislost). Pro nováčka to je magie – jedna a ta samá routa někdy funguje, někdy ne.

Řešení jak tomu předejít je nepoužívat routu kombinující a) + b):

$router->addRoute('admin/<presenter>/<action>[/<id>]', 'AdminHome:default');

místo toho používat dvě jednodušší, nezávislé routy:

$router->addRoute('admin', 'AdminHome:default');
$router->addRoute('admin/<presenter>/<action>[/<id>]');

Což je řešení, ke kterému nakonec dojde každý.

Editoval m.brecher (26. 5. 2023 16:17)

Marek Bartoš
Nette Blogger | 1275
+
+2
-

Úplně nejjednodušší je napsat si routu na míru pro každý presenter a akci zvlášť.

Editoval Marek Bartoš (26. 5. 2023 16:44)