Sloučení rout pro slug i null

Infanticide0
Člen | 64
+
0
-

Ahoj,

mám routy:

$router->addRoute("all[/<page=1 \d+>][/<perPage \d+>]", "List:default");
$router->addRoute("<user>[/<page=1 \d+>][/<perPage \d+>]", ...);

Routa <user> předává entitu Usera do Presenteru a ta je pak použita pro filtrování v DB a výpis dat se stránkováním.
Když je $user null, nic se nefiltruje a zobrazí se komplet data.

Routa /all je pro zobrazení stránky, kde je zobrazeno všechno.

  1. Jde tyhle routy nějak sloučit? Abych nemusel duplikovat parametry rout.
  2. <page> je tam pro Paginator, $page a $perPage bere z action/default parametrů. Neni lepší mít tyhle hodnoty jako Persistent? Co je tady best practice?

Díky za rady.

uestla
Backer | 796
+
0
-

Nešly by na to použít filtry a překlady? Něco ve stylu

$router->addRoute("<user>[/<page=1 \d+>][/<perPage \d+>]", [
	'presenter' => 'List',
	'action' => 'default',
	'user' => [
		Route::FilterTable => [
			'all' => null,
		],
	],
]);
m.brecher
Generous Backer | 765
+
0
-

@Infanticide0

Když je $user null, nic se nefiltruje a zobrazí se komplet data.
Routa /all je pro zobrazení stránky, kde je zobrazeno všechno.

Takhle bez detailních informací mě tyto dvě stránky přijdou duplicitní – obě zobrazí všechno (komplet data). Otázkou je, pokud duplicitní nejsou čím se liší a pokud duplicitní jsou, proč routu /all jednoduše neodstranit, nebo ji ponechat a pro $user = null vyhodit 404.

Infanticide0
Člen | 64
+
0
-

uestla napsal(a):

Nešly by na to použít filtry a překlady? Něco ve stylu

$router->addRoute("<user>[/<page=1 \d+>][/<perPage \d+>]", [
	'presenter' => 'List',
	'action' => 'default',
	'user' => [
		Route::FilterTable => [
			'all' => null,
		],
	],
]);

Dík za tip, zkusil jsem a asi by to fungovalo kdybych nepředával entity přímo do Presenteru přes FilterIn/Out, takhle to vyhodí chybu.

$router->addRoute("<user>[/<page=1 \d+>][/<perPage \d+>]", [
				'presenter' => 'List',
				'action' => 'default',
				'user' => [
					Route::FilterTable => [
						'all' => null,
					],
					Route::FilterIn => function (string $user) {
						return $this->orm->users->getById($user]);
					},
					Route::FilterOut => function (User|null $user) {
						return $user->id;
					},
				],
			]);
Infanticide0
Člen | 64
+
0
-

m.brecher napsal(a):

@Infanticide0

Když je $user null, nic se nefiltruje a zobrazí se komplet data.
Routa /all je pro zobrazení stránky, kde je zobrazeno všechno.

Takhle bez detailních informací mě tyto dvě stránky přijdou duplicitní – obě zobrazí všechno (komplet data). Otázkou je, pokud duplicitní nejsou čím se liší a pokud duplicitní jsou, proč routu /all jednoduše neodstranit, nebo ji ponechat a pro $user = null vyhodit 404.

Obě routy míří na jeden Presenter, ale neni to Homepage, takže routu potřebuje.
Presenter s $user=null zobrazuje třeba Datagrid všech dat ->findAll(), když $user je entita, Datagrid podle jeho id filtruje data ->findBy(..$user->id..).

zjednodušeně
.cz/123456 ⇒ data uživatele ID=123456 (ListPresenter)
.cz/all ⇒ data všech uživatelů (ListPresenter)
.cz ⇒ HomePresenter

A já nechci duplikovat parametry. Hledám něco jako <user ?? ‚all‘>

m.brecher
Generous Backer | 765
+
0
-

@Infanticide0

takže jestli jsem to správně pochopil, tak chceš aby url vypadaly takto:

.cz/123456 ⇒ data uživatele ID=123456 (ListPresenter)
.cz/all ⇒ data všech uživatelů (ListPresenter)
.cz ⇒ HomePresenter

Jde to samozřejmě udělat, třeba nějak takhle:

$router->addRoute('', 'Homepage:default');
$router->addRoute('<user all|\d+>[/<page>][/<perPage>]', 'List:default');

A v presenteru:

public function actionDefault(string $user, ?int $page = 1, ?int $perPage = 20)
{
    if($user === 'all'){
        //.....
	}
}

Proč jsem odstranil regulární výrazy pro $page a $perPage? Protože je lepší typovat int v signatuře metody akce než v routě.
Naopak v parametru <user> se regulární výrazy musí použít, protože jedině tak lze mít jednu routu pro all i pro single user. UserId ale dostaneš jako string.

Editoval m.brecher (28. 2. 2:58)

uestla
Backer | 796
+
0
-

@Infanticide0 Aha, nedošlo mi, že už v té routě budeš mít Route::FilterIn a Route::FilterOut – v tom případě si tu „prázdnou“ hodnotu all můžeš pořešit už tam:

$router->addRoute("<user>[/<page=1 \d+>][/<perPage \d+>]", [
	'presenter' => 'List',
	'action' => 'default',
	'user' => [
		Route::FilterIn => function (string $user) {
			return $user === 'all' ? null : $this->orm->users->getById($user);
		},
		Route::FilterOut => function (User|null $user) {
			return $user === null ? 'all' : $user->id;
		},
	],
]);
Kamil Valenta
Člen | 762
+
+2
-

m.brecher napsal(a):

Proč jsem odstranil regulární výrazy pro $page a $perPage? Protože je lepší typovat int v signatuře metody akce než v routě.

Proč je to lepší? S původní routou url „/all/ahoj“ skončí na 404 (nebo to matchne jiná routa, pro kterou to validní bude), s upravenou routou to spadne na 500 (a žádná jiná routa nedostane příležitost, ačkoliv by pro to třeba měla validní response).
V routě nejde o typování. Tazatel možná v metodě typováno má, routa má být každopádně co nejstriktnější.

m.brecher
Generous Backer | 765
+
-3
-

@KamilValenta

routa má být každopádně co nejstriktnější

Hodně záleží na celkové situaci, takže nejde generalizovat, ale s typováním int již v routě nemám dobré zkušenosti. Při neplatném parametru router vyhodí 404 a chybí informace, ve kterém modulu a presenetru se tak stalo. Obvykle chceme 404 stránky vykreslovat do layoutu aktuálního modulu a to je v tomto případě problém. Naopak když se bude int parametru typovat až v metodě akce tak 404 vyhodí presenter a error presenter bude vědět ve kterém modulu se to odehrálo a vykreslí 404 stránku do layoutu správného modulu. Typování int parametru v routě samo o sobě žádné výhody nemá.

Infanticide0
Člen | 64
+
0
-
 Route::FilterIn => function (string $user) {
 	return $user === 'all' ? null : $this->orm->users->getById($user);
 },
 Route::FilterOut => function (User|null $user) {
 	return $user === null ? 'all' : $user->id;
 },

Když FilterIn vrátí null, neznamená to, že router nenašel svůj „cíl“ a má jít hledat další routu?

Kamil Valenta
Člen | 762
+
+2
-

m.brecher napsal(a):

Hodně záleží na celkové situaci, takže nejde generalizovat

Celkem nezáleží, nedává smysl, aby router propustil pro něj nevalidní request a pak jej zařízl presenter.
Když pro zjednodušení přehlédnu filtrIn funkce, které do toho vnesou ještě větší přísnost, uvažujme dvě routy, jedna matchne čísla, druhá stringy:

$router->addRoute('<personalnumber>', ':Personal:List:default');
$router->addRoute('<slug>', ':Articles:Detail:default');

Pokud bude router moc benevolentní, url „/novinky-v-roce-2024“ se matchne na modul Personal a vyhodil by 404, sice krásně omalovanou layoutem z modulu Personal, ale druhá routa by ráda vrátila článek, který má…

ale s typováním int již v routě

Routy skutečně netypují.

Při neplatném parametru router vyhodí 404 a chybí informace, ve kterém modulu a presenetru se tak stalo. Obvykle chceme 404 stránky vykreslovat do layoutu aktuálního modulu a to je v tomto případě problém.

„Obvykle“ není „vždy“, ba dokonce možná ani ne „většinou“.
URL „/dvacetsedm“ je dle výše uvedených rout adept na 404 jakého modulu? Je to neexistující slug, nebo špatně zadané číslo?
Každopádně to není práce pro router. Ten má říct, zda je URL validní, nebo 404.
ErrorPresenter může o layoutu rozhodnout z debug_backtrace() a nebo query stringu.

Naopak když se bude int parametru typovat až v metodě akce tak 404 vyhodí presenter a error presenter bude vědět ve kterém modulu se to odehrálo.

Nikoliv. ErrorPresenter maximálně bude vědět, která routa byla „nejhladovější“ a matchnula to třeba i neprávem.

Editoval Kamil Valenta (28. 2. 19:40)

m.brecher
Generous Backer | 765
+
0
-

@KamilValenta

s upravenou routou to spadne na 500

500 vyhazuje presenter při nesouladu typů parametru v metodě akce ve starších verzích Nette. Je to cca rok/dva, co se toto matoucí chování opravilo a místo 500 v novějších verzích vyhazuje presenter 404.

m.brecher
Generous Backer | 765
+
0
-

@Infanticide0

aby kód pro sloučené routy byl skutečně funkční, je potřeba zajistit, aby router mohl rozlišit nepovinné parametry $page a $perPage, které jsou za sebou, stačí dodat prefixy:

$router->addRoute('', 'Homepage:default');
$router->addRoute('<user all|\d+>[/page-<page>][/perPage-<perPage>]', 'List:default');
Infanticide0
Člen | 64
+
0
-

m.brecher napsal(a):

@Infanticide0

aby kód pro sloučené routy byl skutečně funkční, je potřeba zajistit, aby router mohl rozlišit nepovinné parametry $page a $perPage, které jsou za sebou, stačí dodat prefixy:

$router->addRoute('', 'Homepage:default');
$router->addRoute('<user all|\d+>[/page-<page>][/perPage-<perPage>]', 'List:default');

Prefixy tam používám, jen jsem je do ukázky nedával pro zjednodušení, nejsou předmětem problému.
Vyházel jsem všechno mimo <user>, výraz .+ je špatně kvůli lomítkům, ale to teď ignoruju. Typy v routeru jsem taky odstranil, kdyby se na pozadí něco přetypovávalo.

S null to prostě nefunguje. Proč?

{link UserList: $adminUser} => /1-admin
{UserList:default, user: users} => /users
{UserList:default, user: null} => #error: No route for Front:UserList:default()
public function actionDefault(User|null|string $user): void
{
	if($user instanceof User)
		$this->user = $user;
}

$router->addRoute("<user users|.+>", [
	'presenter' => 'UserList',
	'action' => 'default',
	'user' => [
		Route::FilterIn => function ($user) {
			bdump($user, "in");
			if($user === "users" || $user === null)
				return false;

			$up = explode('-', $user);
			return $this->orm->users->getBy(["id" => $up[0]]);
		},
		Route::FilterOut => function ($user) {
			bdump($user, "out");

			if($user instanceof User)
				return "$user->id-admin";

			return "users";
		},
	],
]);
m.brecher
Generous Backer | 765
+
0
-

@KamilValenta

nedává smysl, aby router propustil pro něj nevalidní request a pak jej zařízl presenter.

Já to takto používám všude. V akci parametry requestu vždycky typuji. Potom je filtr regulárním výrazem \d+ v routě nadbytečný.

Pokud ovšem v jednom parametru chceme typem rozlišit dvě různé akce, pak je filtr v routě samozřejmě nutný. Také jsem to takto použil v parametru user:

$router->addRoute('<user all|\d+>.......', .....);

Parametry $page a $perPage žádnou paralelní routu nemají, filtry \d+ lze tedy vynechat a funkce zůstane stejná – proto jsem je tam nedal.

m.brecher
Generous Backer | 765
+
0
-

@Infanticide0

S null to prostě nefunguje. Proč?

Protože to routa neumožňuje. Tak jak jsou routy navrženy tak když je $user = null tak je to url homepage. Homepage ale má jiný presenter. Proto do odkazů nesmíš dávat $user = null a odkazovat přitom na UserList:default.

Pokud jsou routy takto:

$router->addRoute('', 'Homepage:default');
$router->addRoute('<user all|\d+>[/page-<page>][/perPage-<perPage>]', 'List:default');

Platné odkazy jsou tyto:

{link 'List:default', $user}
{link 'List:default', 'all'}
{link 'Homepage:default'}

Všimni si tohoto:

'<user all|\d+>'

Tento regulární výraz pro parametr $user povolí jenom ‚all‘ nebo integer, null nikoliv. A tak Jsi to přece úplně na začátku chtěl ne?

Editoval m.brecher (29. 2. 20:45)

m.brecher
Generous Backer | 765
+
0
-

@Infanticide0

S null to prostě nefunguje. Proč?

Až teď jsem si všiml, že používáš jinou routu:

$router->addRoute("<user users|.+>", [
	'presenter' => 'UserList',
	'action' => 'default',
	'user' => [
		Route::FilterIn => function ($user) {
			bdump($user, "in");
			if($user === "users" || $user === null)
				return false;

			$up = explode('-', $user);
			return $this->orm->users->getBy(["id" => $up[0]]);
		},
		Route::FilterOut => function ($user) {
			bdump($user, "out");

			if($user instanceof User)
				return "$user->id-admin";

			return "users";
		},
	],
]);

Zde je pointa v primárním filtru parametru user v routě:

'<user users|.+>'

Použitý regulární výraz null nezachytí.

Tento regulární výraz by se dal zjednodušit:

'<user .+>'	// jednodušší
'<user>'	// ještě jednodušší

všechny varianty pro <user> matchnou jakýkoliv řetězec s výjimkou prázdného

ale routa by měla zachycovat jenom users nebo userId které je int, pak je lépe ji napsat takto:

$router->addRoute("<user users|\d+>", ...);

Routa kterou Ti navrhuji null nematchne, což je dobře, protože to by nedávalo smysl. Výpis pro všechny je pro $user === ‚users‘, co by se mělo vypisovat pro null ?? Tam není co vypsat.

Editoval m.brecher (29. 2. 21:14)