Nefunkční redirect v rámci bloku try

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Jake Cooney
Člen | 11
+
0
-

Zdravim,

nikdy mi nefunguje redirect v rámci bloku try. Například zde:

<?php
public function actionCreate()
{
	// ...
	try
	{
		$this->model->news->insertPost($values);
		$this->flashMessage('Článek byl vytvořen a publikován.');
		$this->redirect('default');
	}
	catch(Exception $exc)
	{
		$this->flashMessage('Článek nebyl vytvořen.','error');
	}
}
?>

Konkrétně zde se vykoná oboje flashMessage(), catch chytne Exception (vyvolanou někdy během redirectu) s prázdnou message a k přesměrování vůbec nedojde.

Obejít to lze takhle:

<?php
public function actionCreate()
{
	// ...
	try
	{
		$this->model->news->insertPost($values);
		$this->flashMessage('Článek byl vytvořen a publikován.');
		$this->redirect('default');
	}
	catch(Exception $exc)
	{
		if($exc->getMessage() == '') $this->redirect('default');

		$this->flashMessage('Článek nebyl vytvořen.','error');
	}
}
?>

Pozn: Používám Nette 0.9.4 (nefungovalo ani v předchozích 0.9.* verzích, 0.8 jsem nezkoušel).

Editoval Jake Cooney (2. 5. 2010 19:51)

Aurielle
Člen | 1281
+
+1
-

Redirect vždy vyvolá exception, takže nesmíš odchytávat všechny exceptiony… proto se přesměrování neprovede.

redhead
Člen | 1313
+
0
-

Podle toho, co ti model vyhazuje, odchytávej výjimky jako DibiException, atd..

Jake Cooney
Člen | 11
+
0
-

Jo tak, díky moc. To mi taky mohlo dojít.

Bernard Williams
Člen | 207
+
+1
-

Nazdárek,

dá se nějak nastavit, aby mi blok try/catch zachytával vše krom redirect výjimky? V bloku mám několik operací a každá může vyvolat jinou výjimku.

Děkuji
Bernard

Editoval Bernard Williams (5. 11. 2010 16:14)

redhead
Člen | 1313
+
0
-

Tak když to bude redirect výjimka, tak ji můžeš vyhodit znovu…

Tharos
Člen | 1030
+
0
-

Já to občas řeším následovně:

try {
	// nějaké modelové operace vyhazující různé výjimky
	$this->redirect(...);
} catch (Exception $e) {
	if ($e instanceof NAbortException) throw $e;
	// nějaké další operace, spíše presenterové, například $form->addError($e->getMessage()) a podobně
}

Edit: Na tomhle fóru je koukám reakční doba v řádu jednotek minut ;).

Editoval Tharos (5. 11. 2010 16:18)

kravčo
Člen | 721
+
0
-

Ak ti ide naozaj o zachytenie všetkého okrem redirectu, správnejšie bude:

$this instanceof Presenter === TRUE

try {
    $this->redirect(...);
} catch (\Exception $e) {
    if ($e instanceof AbortException) {
        if ($this->response instanceof RedirectingResponse) {
            throw $e;
        }
        // ...
    }
    // ...
}

Skôr ale predpokladám, že chceš znovuvyhodiť všetky AbortException bez výnimky, čiže niečo v štýle ako písal Tharos…


Presenter::$response je private, takže tento fígeľ sa napokon použiť nedá…

Editoval kravčo (5. 11. 2010 17:23)

Ondřej Mirtes
Člen | 1536
+
+1
-

Zachytávání obecné Exception by ses měl vyhnout. Zachytávat bys měl pouze výjimky, s kterými počítáš a které dává smysl předat uživateli do formuláře. Zbytek by měl probublat až nahoru a přistát ti v mailu, abys danou situaci ošetřil. Takhle ti to pohltí naprosto cokoli, od špatného připojení k databázi, přes nepovolený zápis do složky až třeba po špatnou definici formuláře.

V praxi tedy dává smysl vytvořit si nějakou svoji ModelException, od které budeš dědit konkrétní, např. ArticleException. A ve formuláři budeš zachytávat právě jen potomky té ModelException, aby ti tam nepřistávaly věci jako redirect anebo InvalidStateException, které si žádají opravu kódu.

Jako bonus pak můžeš plnit zprávu výjimky nějakým smysluplným textem, který předáš uživateli jako chybovou hlášku do formuláře/flash zprávičky. Pokud bys zachytával všechny výjimky, tak by tento postup byl velmi nebezpečný a zároveň uživatelsky nepřívětivý, protože by tam mohl přistát např. SQL dotaz.

Tharos
Člen | 1030
+
0
-

Ondro, díky za trefné doplnění. Máš samozřejmě pravdu. Já jsem ten svůj kus kódu psal v kontextu toho, že Bernadrd vysloveně říkal, že chce odchytávat vše. Tak jsem se podřídil dotazu :).

Bernard Williams
Člen | 207
+
+1
-

Všem moc díky za příspěvky.

Když teď nad tím tak přemýšlím, tak asi to zachytávání výjimek používám špatně.. :-/

Uvedu svůj příklad na mazání fotek:

if (!checkIfExist($id)) {
    $this->flashMessage('Vybraná fotka neexistuje.');
    $this->redirect(':Admin:smazatFotku');
}
else {
    dibi::begin();
    try {
        $fotka = dibi::fetch('SELECT nazev FROM [table] WHERE id=%i;', $id);
        dibi::query('DELETE FROM [table] WHERE id=%i;', $id);
        @unlink(WWW_DIR.'/fotky/'.$fotka->nazev);
        dibi::commit();
        $this->flashMessage('Fotka byla úspěšně smazána.', 'ok');
        $this->redirect(':Admin:smazatFotku');
    } catch (Exception $e) {
        dibi::rollback();
        $this->flashMessage('Fotku se nepodařilo smazat.');
        throw $e;
    }
}

Testuji existenci fotky. Pokud existuje, tak se ji pokusím smazat. Nejdřív zkouším smazat z DB – pokud by to neprošlo, tak se fotka díky bloku try nesmaže ani z disku. Kdybych první mazal fotku, tak by se mohlo stát, že fotku sice z disku smažu, ale už se mi ji nepodaří smazat z DB – záznam by v DB sice byl, ale fotka by už neexistovala..
Když v mém případě selže smazání fotky, tak díky rollback() vrátím zpět záznam do DB. Tak či tak informuji uživatele flash zprávou. V případě neúspěchu vyhodím danou výjimku, aby mi ji laděnka zalogovala. V případě neúspěchu je flash zpráva nejspíš zbytečná, protože aplikace díky throw $e; skončí s chybou 500 (přesměrování na ErrorPresenter). Když ji ale nevyhodím, tak se mi zase nezaloguje.

Podobným způsobem řeším i update operace.

if (checkIfExist($id)) {
   try {
       // nejake SELECT dotazy
       // pocetni operace
       // formatovaci operace
       // ...
       // UPDATE dotaz
       $this->flashMessage('Fotka byla úspěšně upravena.', 'ok');
       $this->redirect(':Admin:upravitFotku');
   } catch (Exception $e) {
       $form->addError('Fotku se nepodařilo upravit.');
       throw $e;
   }
}

Zase testuji existenci fotky. Pokud existuje, tak by žádná chyby již neměla nastat, protože uživatel vložil správné ID fotky. Pokud ale chyba nastane (např. nefunguje PgSQL), tak se v DB nic nezmění (UPDATE dotazu se díky try bloku vůbec neprovede), zobrazí se „stránka 500“ a zaloguje se chyba.

Dle mé filozofie, podle které se dosud řídím, by nikdy k vyhození výjimky nemělo dojít. Třeba ono tolik známé dělení nulou – nemělo by k tomu dojít, protože bych to měl mít ošetřené podmínkami. Stejně tak jiné operace.. test na existenci souborů, potřebných dat atd.. na to vše bych měl myslet předem a vše ošetřit podmínkami s patřičnými zprávami pro uživatele. Když už k výjimce dojde, tak by mělo jít o chybu, která neměla nastat nebo se kterou jsem nepočítal a zalogovat ji.

Co říkáte na moje řešení? Jak to řešíte vy?