Test výsledku volání metody ->table()->insert(): kontrola na chybové stavy

FilipCZ
Člen | 2
+
0
-

Dobrý den,

po vložení dat do databázové tabulky pomocí obdobné konstrukce:

$row = $explorer->table('users')->insert([
	'name' => $name,
	'year' => $year,
]);

bych prosím potřeboval rozlišit, zda vložení dat do tabulky proběhlo v pořádku (vypíše se zpráva o úspěšně vloženém záznamu) nebo zda nastala chyba (vypíše se informace o nastalé chybě). Nemohu však přijít na to, jakým způsobem mohu z proměnné $row vyčíst výsledek operace. Napoví mi prosím někdo, jakým způsobem je možné výsledek volání metody ->insert() otestovat? Děkuji.

Editoval FilipCZ (19. 3. 2023 21:10)

Kamil Valenta
Člen | 758
+
+3
-

Pokud selže insert, tak to vykopne exceptionu, takže ji stačí odchytit v try … catch.

m.brecher
Generous Backer | 736
+
-1
-

@FilipCZ

Operace, kdy se v databázi něco mění – INSERT, UPDATE, DELETE je vhodné vždy ošetřit proti případné chybě, kterou nikdy nelze zcela vyloučit.

Jakou výjimku zachytávat záleží na tom, jakou může databáze vyhodit. Tam pak záleží jak máš udělaný Model. Nette Database vyhazuje obvykle DriverException, ale jde zachytit i PDOException, pokud použiješ transakce tak je nejlepší to ošetřit rovnou v modelu vyhozením vlastní výjimky, vestavěná databázová knihovna PHP – PDO vyhazuje PDOException, atd…

V modelu se jednoduše o výjimky nestarám a zachytávám je až v handleru odeslání formuláře, kde se volá příslušná metoda modelové třídy. Výjimku v transakci ale je potřeba ošetřit rovnou v modelu.

Příklad práce s výjimkami při transakci:

// handler obsluhující odeslání formuláře
    public function handleUpdate(ArrayHash $values)
    {
        try{
            $this->pricelistModel->update($this->id, $values)
                ? $this->flashMessage('Změny ... byly úspěšně uloženy', 'success')
                : $this->flashMessage('Nebyly provedeny žádné změny');
            $this->redirect('this');

        }catch (TransactionException $exception){
            $this->flashMessage('.... se nepodařilo aktualizovat - '.$exception->getMessage());
        }
    }

PricelistModel je třída pracující s databází, metoda update() provádí aktualizaci ceníku a vrací podle výsledku operace v databázi zda byly modifikovány nějaké záznamy nebo ne. Protože jsem použil transakce, vyhazuji si svoji vlastní výjimku TransactionException.

PricelistModel:

    public function update(int $id, ArrayHash $values): bool
    {
        $database = $this->database;
        try{
            $database->beginTransaction();
                [$table, $rowArr] = $this->normalizeValues($values, update: true);
                $success1 = $this->getOneTable($id)->update($table);
                $success2 = $this->getTableHeadingRow($id)->update($rowArr);
            $database->commit();

        }catch (\PDOException $exception){
            $database->rollBack();
            throw new TransactionException($exception->getMessage());
        }
        return $success1 || $success2;
    }

A moje vlastní výjimka:

class TransactionException extends \Exception
{}

Můžeš samozřejmě vyhodit místo TransactionException obecnou výjimku PHP \Exception, ale takhle je to čitelnější.

Takd doufám, že jsem to srozumitelně vysvětlil.

FilipCZ
Člen | 2
+
0
-

@KamilValenta @mbrecher děkuji za tipy.

Aktuálně by mi to stačilo nějak takhle:

...
try {
	$this->database->table('users')->insert([
		'name' => $user['name'],
		'year' => $user['year']
	]);
	// Zde vytiskni, že insert proběhl v pořádku
} catch (PDOException | DriverException | \Exception $e) {
	// Zde vytiskni informaci o chybě
}
...

Je prosím ten try-catch takto validní?

Dle PHP Wiki ( https://wiki.php.net/…ltiple-catch ) by se asi ta general \Exception měla odchytávat v samostatném catchi, ale mám trochu obavy, zda se mi pak nebude v některých případech chybový výpis duplikovat (pokud i ve druhém catchi nechám vypisovat proměnnou $e).

Mohlo by to tedy takto vypadat? Děkuji za schválení, případně další připomínky.

Marek Bartoš
Nette Blogger | 1165
+
0
-

Každá exception dědí od \Exception, takže PDOException | DriverException. Jsou duplikáty. Obecnou exception bys ale neměl odchytávat pokud je jiná možnost.

Též bys neměl vypisovat uživateli text výjimky, nebývá pro něj určen a stejně z něj nic nevyčte. Buď nech aplikaci prostě spadnout, aby se ti chyba zalogovala a uživatel nemohl pokračovat, nebo ji po odchycení zaloguj a vypiš uživateli jen obecnou zprávu o tom, že se stala chyba.

Že se ti nic nebude duplikovat si můžeš ověřit jednoduchým testem https://3v4l.org/2RQK0

Ten odkaz na wiki mluví pouze o přidání funkce, která umožňuje místo více catch statements zapsat jen jeden, společný pro více typů výjimek

Editoval Marek Bartoš (21. 3. 2023 21:30)

m.brecher
Generous Backer | 736
+
0
-

@FilipCZ

Omlouvám se, měl jsem v prvním příkladu špatně třídu výjimky:

// handler obsluhující odeslání formuláře
    public function handleUpdate(ArrayHash $values)
    {
        try{
            $this->pricelistModel->update($this->id, $values)
                ? $this->flashMessage('Změny ... byly úspěšně uloženy', 'success')
                : $this->flashMessage('Nebyly provedeny žádné změny');
            $this->redirect('this');

        }catch (DriverException $exception){   // opraveno
            $this->flashMessage($exception->getMessage());  // po dobu vývoje
			$this->flashMessage('.... se nepodařilo aktualizovat - ');  // po nasazení na produkci
        }
    }

Máš to vcelku dobře – jak psal Marek, stačí tam jedna třída výjimky, já používám DriverException.

Mě během vývoje vyhovuje, když se zobrazují chybové zprávy, ale na produkčním serveru to není dobré. Takže buďto chybová hlášení po dokončení vývoje přepíšeš na produkční verzi, nebo já to řeším tak, že podle $debugMode vypisuji vždy příslušnou verzi chybového hlášení:

// handler obsluhující odeslání formuláře
    public function handleUpdate(ArrayHash $values)
    {
        try{
            $this->pricelistModel->update($this->id, $values)
                ? $this->flashMessage('Změny ... byly úspěšně uloženy', 'success')
                : $this->flashMessage('Nebyly provedeny žádné změny');
            $this->redirect('this');

        }catch (DriverException $exception){   // opraveno
			$this->debugMode
				? $this->flashMessage('.... se nepodařilo aktualizovat - ');  // Development režim
                : $this->flashMessage($exception->getMessage());  // Production režim
        }
    }

Property $debugMode není v presenteru automaticky, ale je jako default parametr v configu, odkud se dá do presenteru nějak přesunout – pomocí decorator, nebo pomocí nějaké speciální služby $config, kterou si musíš vytvořit sám.

David Matějka
Moderator | 6445
+
0
-

@mbrecher takovy zpusob handlovani chyb je pracny a ve vysledku horsi. Chybi ti tam zalogovani te vyjimky, takze jako developer se nedozvis, ze se neco pokazilo.

Vetsinou je nejlepsi nechat ji propadnout na generickou error500 stranku, kde dojde k zalogovani te chyby.

m.brecher
Generous Backer | 736
+
0
-

@DavidMatějka

takovy zpusob handlovani chyb je pracny a ve vysledku horsi. Chybi ti tam zalogovani te vyjimky, takze jako developer se nedozvis, ze se neco pokazilo.

kód v ukázce je zjednodušený, souhlasím – logování výjimky tam na produkci patří, když vyhodíš 500ku, tak jak potom řešíš chybovou stránku, aby to pro uživatele nebylo matoucí?

Máš ale pravdu, že když se to dobře odladí, tak potom by k výjimkám v produkci už docházet nemělo a pak by asi stačilo to zachytit na nevyšší úrovni jako 500. Uživatelé zmateni nebudou, protože by k tomu docházet prakticky nemělo. Budu nad tím přemýšlet.

Editoval m.brecher (24. 3. 2023 18:16)