Lze to řešit lépe? (routování)
- Tomik
- Nette Evangelist | 485
Zdravím diskutující!
Mám několik rout, např.:
<?php
$router[] = new Route('user/<username>', array(
'presenter' => 'User',
'action' => 'profile',
));
$router[] = new Route('<page>', array(
'presenter' => 'Page',
'action' => 'default',
));
?>
A nyní mám presenter Admin, který se stará o přihlašování a chci, aby vždy odkaz na přihlášení (odhlášení), byl ve tvaru /normalni-adresa/login (resp. logout), tzn. pro profil uživatele tedy např. /user/tomik/login, přidal jsem si tedy ke kadžé routě routu odpovídající, ale s přidanou „koncovkou“:
<?php
$router[] = new Route('user/<fromPage>/<action login|logout>', array(
'presenter' => 'Admin',
'page' => NULL,
'fromView' => 'profile',
'fromPres' => 'User',
));
$router[] = new Route('<fromPage>/<action login|logout>', array(
'presenter' => 'Admin',
'fromView' => 'default',
'fromPres' => 'Page',
));
?>
Toto řešení má sice kýžený výsledek – tedy adresy se generují tak, jak chci, ale musím mít dvakrát tolik rout – což spomaluje aplikaci a samozřejmě je to 2× tolik práce.
Ptám se tedy, lze toto nějak skloubit vždy do jedné routy, tak aby pokud tam „dodatek“ /login na konci není, směřovala routa např. na presenter User, pokud tam je, tak na presenter Admin, nebo je nutné opravdu vytvořit dvě routy (a je to tedy daň za to, že chci takhle pěkné adresy)?
Díky!
- pmg
- Člen | 372
Zdravím do Roztok!
Jedním z řešení by mohlo být poslat si ten „dodatek“ jako volitelný parametr, nějak takto:
$router[] = new Route('user/<username>/<extra login|logout>', array(
'presenter' => 'User',
'action' => 'profile',
'extra' => NULL,
));
BasePresenter
potom může předat řízení jinam nebo
změnit view.
public function startup($extra)
{
if (isset($extra)) {
$this->forward('Admin:' . $extra);
}
}
- David Grudl
- Nette Core | 8218
Pokud se rozhodování bude odehrávat až na úrovni presenteru a poté bude
následovat přesměrování, dojde k zohlednění (ztrátě) např.
některých persistentních parametrů. Rozhodovací logika by proto měla být
součástí routování. Řešením je například vytvořit potomka třídy
Route a přepsat metodu match()
:
/**
* Maps HTTP request to a PresenterRequest object.
* @param Nette\Web\IHttpRequest
* @return PresenterRequest|NULL
*/
public function match(IHttpRequest $context)
{
$request = parent::match($context);
if ($request === NULL) {
return NULL;
} elseif ($request->params['id'] === ....) { // mel by tohle ID resit jiny presenter
return new PresenterRequest(
'JinyPresenter',
$request->getMethod(), // *)
$request->getParams(),
$request->getPost(),
$request->getFiles(),
$request->getFlags() // *)
);
} elseif (...) { // nebo jeste jiny?
return new PresenterRequest(...);
} else {
return $request;
}
}
*) všiml jsem si, že tyto metody ve třídě PresenterRequest chybí. Doplním.
- Tomik
- Nette Evangelist | 485
Tak jsem se do toho pustil, vytvořil jsem si
<?php
class MujRouter extends MultiRouter implements IRouter
{
/**
* Maps HTTP request to a PresenterRequest object.
* @param Nette\Web\IHttpRequest
* @return PresenterRequest|NULL
*/
public function match(IHttpRequest $context)
{
$request = parent::match($context);
if ($request === NULL) {
return NULL;
} elseif ($request->params['admin'] === 'login') {
return new PresenterRequest(
'Admin',
$request->getMethod(),
$request->getParams(),
$request->getPost(),
$request->getFiles(),
$request->getFlags()
);
} else {
return $request;
}
}
}
?>
v config.ini mám potom
service.Nette-Application-IRouter = MujRouter
a když pustím
aplikaci, hází mi to
MemberAccessException
Cannot read an undeclared property Application::$getRouter.
Neví někdo co s tím? :)
Děkuju!
Edit: Mám poslední revizi.
Editoval Tomik (4. 12. 2008 23:21)
- romansklenar
- Člen | 655
Podle mě by jsi měl rozšiřovat spíše třídu Route
.
MultiRouter bys rozšiřoval/upravoval, kdyby ti nevyhovovalo například to
(teď mě nic lepšího nenapadá), že se routy nedají pojmenovávat.
Pokud chceš jen upravit chování
výběru požadovaného presenteru při zpracování
PresenterRequestu
, upravíš Route
.
Z úvodního postu soudím, že tobě jde o tu druhou variantu – definovat si pravidla pro výběr presenteru, am I right?
- Tomik
- Nette Evangelist | 485
romansklenar napsal(a):
Podle mě by jsi měl rozšiřovat spíše třídu
Route
. MultiRouter bys rozšiřoval/upravoval, kdyby ti nevyhovovalo například to (teď mě nic lepšího nenapadá), že se routy nedají pojmenovávat.Pokud chceš jen upravit chování výběru požadovaného presenteru při zpracování
PresenterRequestu
, upravíšRoute
.Z úvodního postu soudím, že tobě jde o tu druhou variantu – definovat si pravidla pro výběr presenteru, am I right?
No jo, už bylo včera večer pozdě… :) Kaju se.. ;)
- Tomik
- Nette Evangelist | 485
David Grudl napsal(a):
Pokud se rozhodování bude odehrávat až na úrovni presenteru a poté bude následovat přesměrování, dojde k zohlednění (ztrátě) např. některých persistentních parametrů. Rozhodovací logika by proto měla být součástí routování. Řešením je například vytvořit potomka třídy Route a přepsat metodu
match()
:/** * Maps HTTP request to a PresenterRequest object. * @param Nette\Web\IHttpRequest * @return PresenterRequest|NULL */ public function match(IHttpRequest $context) { $request = parent::match($context); if ($request === NULL) { return NULL; } elseif ($request->params['id'] === ....) { // mel by tohle ID resit jiny presenter return new PresenterRequest( 'JinyPresenter', $request->getMethod(), // *) $request->getParams(), $request->getPost(), $request->getFiles(), $request->getFlags() // *) ); } elseif (...) { // nebo jeste jiny? return new PresenterRequest(...); } else { return $request; } }
*) všiml jsem si, že tyto metody ve třídě PresenterRequest chybí. Doplním.
Ještě drobný dotaz: jak pak mám generovat odkazy pomocí link? Díky!
Edit: Myslím odkaz na ten admin presenter…
Editoval Tomik (5. 12. 2008 20:29)
- Tomik
- Nette Evangelist | 485
phx napsal(a):
Rekl bych ze uplne stejne jako driv. Jen si to prechroupe jiny Router.
Znamená to tedy, že si musím vytvořit i svůj router? Zatím mám v bootstrapu něco jako toto:
$router[] = new MyRoute('<page>/<admin login|logout>', array(
'presenter' => 'Page',
'action' => 'default',
'page' => NULL,
'admin' => NULL,
));
...
A další routy podobného ražení.
MyRoute definuju takto:
class MyRoute extends Route
{
/**
* Maps HTTP request to a PresenterRequest object.
* @param Nette\Web\IHttpRequest
* @return PresenterRequest|NULL
*/
public function match(IHttpRequest $context)
{
$request = parent::match($context);
if ($request === NULL) {
return NULL;
} elseif (isset($request->params['admin'])) if ($request->params['admin'] === 'login' || $request->params['admin'] === 'logout') {
if (isset($request->params['username']))
$request->params['page'] = $request->params['username'];
return new PresenterRequest(
'Admin',
$context->getMethod(),
array(
'fromPage' => $request->params['page'],
'fromView' => $request->params['action'],
'fromPres' => $request->getPresenterName(),
'action' => $request->params['admin'],
),
$context->getPost(),
$context->getFiles(),
array('secured' => $context->isSecured())
);
//return $request;
} else {
return $request;
}
}
}
A Admin presenter vypadá potom takto:
class AdminPresenter extends BasePresenter
{
public function actionLogin($fromPage = NULL, $fromView = NULL, $fromPres = 'Page:')
{
....
Kde parametry $from… jsou kvůli tomu, abych věděl, kam po přihlášení přesměrovat (tedy zpět na stránku, ze které přišel požadavek).
Pokud totiž uživatel vstoupí na stránku, kde má být přihlášen, je ihned přesměrován na admin presenter, kde se přihlásí a pak je přesměrován zpět.
Chci se tedy zeptat, jak to mám vyřešit, protože za dané situace mi to hází
InvalidLinkException
No route for Admin:login(fromPage=homepage, fromView=default, fromPres=Page)
Ta routa sice vytvořena opravdu není, ale myslím, že by to vadit nemělo, když jsem si vytvořil tu MyRoute na to, aby v případě, že je potřeba request předělat jej předělala.
Takže otázka zní takto:
Jak na to? :)
Chtěl bych, aby výsledek byl ten, že pokud uživatel klidne na odkaz „login“ ve stránce např. s url www.web.cz/clanek, tak jej to odkáže na www.web.cz/clanek/login, tam se přihlásí a ono ho to přesměrovalo zpět na www.web.cz/clanek, zároveň pokud uživatel přistoupí na www.web.cz/prisne-tajne a nemá tam co dělat (není v adekvátní roli), aby jej to přesměrovalo automaticky (bez klikání na cokoli) na adresu www.web.cz/…-tajne/login – tedy na Admin presenter a ten pak mohl opět po přihlášení vesele redirektovat zpět na www.web.cz/prisne-tajne – kdy už by uživatel práva měl.
Snad je to pochopitelné.
Dřív jsem to řešil pomocí dvou rout pro každou situaci (jedna s dovětke /login, jedna bez něho – viz výše), ale to mi přijde zbytečné, určitě to jde udělat jinak – a když si upraví Route třídu, tak jak poradil David, hlásí mi to, že neexistuje pro daný request routa (viz výše). Poradí někdo?
Díky a pěkné svátky! :)
- David Grudl
- Nette Core | 8218
Zkus se podívat na příklad akrabat.forms. V DashboardPresenter v metodě startup() se ověří uživatelská oprávnění a pokud nevyhovují, dojde k přesměrování na AuthPresenter. Přitom jako parameter $backlink se mu předá klíč k requestu, na který se po přihlášení uživatele vrátí.
- Tomik
- Nette Evangelist | 485
David Grudl napsal(a):
Zkus se podívat na příklad akrabat.forms. V DashboardPresenter v metodě startup() se ověří uživatelská oprávnění a pokud nevyhovují, dojde k přesměrování na AuthPresenter. Přitom jako parameter $backlink se mu předá klíč k requestu, na který se po přihlášení uživatele vrátí.
Díky to je přesně to, co potřebuji – ale… :) (vždycky je nějaké ale, že?) ale chtěl bych, aby místo adresy „www.web.cz/admin/login/3c86“ pro přihlášení na stránku „www.web.cz/stranka“ byla ta adresa „www.web.cz/stranka/login“. Předpokládám, že by asi šlo požadovaného dosáhnout pomocí přepsání jak Route, tak MultiRouter. Ptám se ale: šlo by to jinak a jednodušeji? (tak abych nemusel vytvářet ty duální routy – jednou s a jednou bez těch koncovek „/login“ a „/logout“). A pokud by to jednodušeji nešlo, nakopnul bys mě prosím, jak přepsat i ten MultiRouter (Route už mám přepsanou podle Tvého návrhu – to funguje – resp. pokud to požadavky splňuje ten request, tak vytvoří nový na Admin presenter, ale pak mi to zařve, že neexistuje routa pro admin presenter – kvůli tomu předpokládám, že bude potřeba přepsat i MultiRouter.
Ale možná, že se snažím to asž moc „zkrášlit“ a je to pitomost (resp. to to určitě je), ale prostě mi přijde poměrně velký rozdíl mezi adresou „www.web.cz/admin/login/3c86“ a „www.web.cz/stranka/login“ a rád bych tu aplikaci, co teď v Nette píšu měl dokonalou. :)
Díky za čas!
- David Grudl
- Nette Core | 8218
Už to asi začínám chápat.
Tomik napsal(a):
Pokud totiž uživatel vstoupí na stránku, kde má být přihlášen, je ihned přesměrován na admin presenter, kde se přihlásí a pak je přesměrován zpět.
…
Ta routa sice vytvořena opravdu není, ale myslím, že by to vadit nemělo, když jsem si vytvořil tu MyRoute na to, aby v případě, že je potřeba request předělat jej předělala.
Nojo, ty sice umíš akceptovat URL a předat správnému routeru, ale už ne vygenerovat URL. Což je potřeba, protože jinak na ně nemůžeš uživatele přesměrovat. Takže kromě metody match() je potřeba vytvořit ještě metodu constructUrl().
(Zde by se ještě dalo narazit kvůli kešovacímu mechanismu uvnitř
třídy Route, kdyby to zlobilo, přidej ještě metodu
function getTargetPresenter() { return NULL; }
.)
Dřív jsem to řešil pomocí dvou rout pro každou situaci (jedna s dovětke /login, jedna bez něho – viz výše)
To mi připadá docela rozumné, navíc stačila by pouze jedna
přihlašovací routa s maskou jako
<url>/<admin login|logout>
, ne?
- Tomik
- Nette Evangelist | 485
David Grudl napsal(a):
Už to asi začínám chápat.
To jsem rád! :)
Nojo, ty sice umíš akceptovat URL a předat správnému routeru, ale už ne vygenerovat URL. Což je potřeba, protože jinak na ně nemůžeš uživatele přesměrovat. Takže kromě metody match() je potřeba vytvořit ještě metodu constructUrl().
A to znamená rozšířít metodu constructUrl třídy Route, nebo jiné (protože constructUrl je ve více třídách :).
(Zde by se ještě dalo narazit kvůli kešovacímu mechanismu uvnitř třídy Route, kdyby to zlobilo, přidej ještě metodu
function getTargetPresenter() { return NULL; }
.)
Díky!
To mi připadá docela rozumné, navíc stačila by pouze jedna přihlašovací routa s maskou jako
<url>/<admin login|logout>
, ne?
A pak tedy jen na straně AdminPresenteru provedu přesměrování na
<url>
? Není to trochu proti koncencím Nette? To že budu
přesměrovávat přímo na nějakou adresu, nikoli však preš link na
presenter:view
?
- David Grudl
- Nette Core | 8218
Tomik napsal(a):
A to znamená rozšířít metodu constructUrl třídy Route, nebo jiné (protože constructUrl je ve více třídách :).
Třídy MyRoute.
A pak tedy jen na straně AdminPresenteru provedu přesměrování na
<url>
? Není to trochu proti koncencím Nette? To že budu přesměrovávat přímo na nějakou adresu, nikoli však preš link napresenter:view
?
Koncepcí se musím držet já, ty můžeš být pragmatický :-)
- Tomik
- Nette Evangelist | 485
Tak nakonec jsem se rozhodl pro řešení pomocí jedné routy, která vypadá nějak takto:
<?php
$router[] = new Route('<url .*>/<action login|logout>', array(
'presenter' => 'Admin',
));
?>
Presenter Admin
(metoda actionLogin
– resp.
actionLogout
– má jako jediný parametr $url
) pak
provede přihlášení a pokud je vše ok, přesměruje na url
<url>
.
Ovšem pokud v nějakém jiném presenteru zjistím, že uživatel není
přihlášen, pak chci přesměrovat na presenter Admin
a view
login
, to dělám takto:
<?php
if (!$user->isAuthenticated()) {
$httpRequest = $this->getHttpRequest();
$this->redirect('Admin:login', $httpRequest->getUri()->relativeUri);
}
?>
Ale místo očekávaného přesměrování na (pokud
$httpRequest->getUri()->relativeUri
je např.
edit/homepage
) edit/homepage/login
dojde
k přesměrování na edit%2Fhomepage/login
, tzv. lomítko
v parametru <url>
(do kterého je předán obsah
$httpRequest->getUri()->relativeUri
) je nahrazeno za
%2F
.
Chtěl bych se tedy zeptat, zda jsem něco nepřehlédl, zda je to moje
chyba, či chybí někde ve vytváření adres urldecode
?
Díky a šťastnej novej!
- Jan Tvrdík
- Nette guru | 2595
Route::addStyle('url', NULL);
Route::setStyleProperty('url', Route::PATTERN, '.*');
Editoval Jan Tvrdík (1. 1. 2009 21:03)
- Tomik
- Nette Evangelist | 485
Jan Tvrdík napsal(a):
Route::addStyle('url', NULL); Route::setStyleProperty('url', Route::PATTERN, '.*');
Díky, a není to je to samé, co jsem napsal pomocí
<?php
$router[] = new Route('<url .*>/<action login|logout>', array(
'presenter' => 'Admin',
));
?>
?
Protože podle 915-routovanie-rekurzivnych-stranok Davida, by si to mělo odpovídat…
Ale jinak díky! Jdu to vyzkoušet…
- Tomik
- Nette Evangelist | 485
Jan Tvrdík napsal(a):
Route::addStyle('url', NULL); Route::setStyleProperty('url', Route::PATTERN, '.*');
Díky! Tak toto pomohlo. Můžu se tedy zeptat, jakej je rozdíl oproti té verzi, co jsem psal já? Protože právě podle 915-routovanie-rekurzivnych-stranok se mi zdálo, že by v tom rozdíl být neměl. Každopádně díky!
- Jan Tvrdík
- Nette guru | 2595
Tomik napsal(a):
Díky! Tak toto pomohlo. Můžu se tedy zeptat, jakej je rozdíl oproti té verzi, co jsem psal já? Protože právě podle 915-routovanie-rekurzivnych-stranok se mi zdálo, že by v tom rozdíl být neměl. Každopádně díky!
Rozdíl je v tom, že David to tam má špatně.
Route::setStyleProperty
dělá to samé jako
<url .*>
, ale klíčový rozdíl je právě v tom
Route::addStyle
, jehož druhý parametr říká, že nechceme
zdědit standartní filtrování pomocí rawurlencode
.
- Tomik
- Nette Evangelist | 485
Jan Tvrdík napsal(a):
Tomik napsal(a):
Díky! Tak toto pomohlo. Můžu se tedy zeptat, jakej je rozdíl oproti té verzi, co jsem psal já? Protože právě podle 915-routovanie-rekurzivnych-stranok se mi zdálo, že by v tom rozdíl být neměl. Každopádně díky!Rozdíl je v tom, že David to tam má špatně.
Route::setStyleProperty
dělá to samé jako<url .*>
, ale klíčový rozdíl je právě v tomRoute::addStyle
, jehož druhý parametr říká, že nechceme zdědit standartní filtrování pomocírawurlencode
.
Díky, už je mi to jasné! :)