Lze to řešit lépe? (routování)

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Tomik
Nette Evangelist | 485
+
0
-

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!

Tomik
Nette Evangelist | 485
+
0
-

Teď mě tak napadá, když si to po sobě čtu, že by to šlo udělat tak, že by všechny routy šli na universální presenter a ten by pak v rámci aplikace přesměrovával, ale to asi není úplně nejlepší řešení, to stávající se mi zdá (zatím) lepší. :)

pmg
Člen | 372
+
0
-

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

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

pmg: Díky, to mě nenapadlo, je to pěkné řešení, škoda jen, že člověk přijde o ty persistentní parametry, takže asi použiju to Davidovo řešení. :)

David: Díky!

Tomik
Nette Evangelist | 485
+
0
-

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

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

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

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

Ví někdo?

phx
Člen | 651
+
0
-

Rekl bych ze uplne stejne jako driv. Jen si to prechroupe jiny Router.

Tomik
Nette Evangelist | 485
+
0
-

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

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

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

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

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

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 na presenter:view?

Koncepcí se musím držet já, ty můžeš být pragmatický :-)

Tomik
Nette Evangelist | 485
+
0
-

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
+
0
-
Route::addStyle('url', NULL);
Route::setStyleProperty('url', Route::PATTERN, '.*');

Editoval Jan Tvrdík (1. 1. 2009 21:03)

Tomik
Nette Evangelist | 485
+
0
-

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

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

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

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 tom Route::addStyle, jehož druhý parametr říká, že nechceme zdědit standartní filtrování pomocí rawurlencode.

Díky, už je mi to jasné! :)