Finally
Školení, která pořádám
V diskusi o připravovaném PHP 5.4 se opět vynořila otázka, zda PHP bude podporovat blok finally. Jde o blok, který souvisí s ošetřováním výjimek – zavolá se na konci try-catch, ať už k výjimce dojde nebo ne. Důležité je, že se blok finally musí zavolat i v případě, kdy kód výjimku nedokáže zpracovat a řízení předá výš (v tom případě se zavolá ještě před tímto předáním výš). Blok finally se tedy hodí především pro úklid.
Řešení v JavaScriptu
JavaScript blok finally přímo podporuje, takže si na něm ukážeme chování:
<script type="text/javascript">
try {
try {
alert('try');
throw 'error';
} finally {
alert('finally');
}
} catch (e) {
alert('outer catch');
}
</script>
Postupně se zobrazí try
, finally
, outer catch
.
Řešení v PHP
V PHP jsem nikdy neměl příliš velkou potřebu tento blok používat. Je to především proto, že PHP po sobě na konci skriptu uklízí automaticky – uvolňuje zabranou paměť, zavírá otevřené soubory a tak dále. A vzhledem k tomu, že PHP skripty mají obvykle jepičí život, tak není moc velký důvod to dělat dřív.
Někdy by se tento obrat ale přeci jen mohl hodit, třeba pro smazání dočasných souborů. Přemýšlel jsem proto, jak by se dalo chování finally napodobit přímo v PHP kódu. Nejprve mě napadla funkce set_exception_handler, ta přijde ke slovu ale až na nejvyšší úrovni, když výjimku nezachytil nikdo. Nakonec jsem proto vytvořil jednoduchou funkci:
<?php
/** Emulace bloku finally
* @param callable hlavní kód
* @param callable blok zavolaný po hlavním kódu
* @return mixed návratová hodnota funkce $try
* @throws Exception v případě, že funkce vyvolá výjimku
* @copyright Jakub Vrána, https://php.vrana.cz/
*/
function tryFinally($try, $finally) {
try {
$return = $try();
$finally();
return $return;
} catch (Exception $e) {
$finally();
throw $e;
}
}
?>
Použití je nejlepší s anonymními funkcemi (od PHP 5.3). S pojmenovanými funkcemi by bylo moc práce, navíc bychom jim klauzulí use nemohli předat proměnné, které mohou používat.
<?php
$filename = tempnam("", "tmp");
tryFinally(function () use ($filename) {
try {
processData($filename);
return true;
} catch (FileException $e) {
return false;
}
}, function () use ($filename) { // finally
unlink($filename);
});
?>
Kód je nepřehledný a krkolomný, ale v nouzi se použít dá.
Diskuse
cooler:
Proč je to vlastně takový problém takovou praktickou, jednoduchou a standardní věc jako je "finally" do toho PHP přidat, ví to někdo?
kukulich:
Jak už jsem říkal v diskuzi na zdrojáku, když se zavádělo try-catch, tak to zřejmě nikdo z PHP core vývojářů nepotřeboval nebo nechtěl. Časy se ale změnily a také se změnili vývojáři. Stačí napsat RFC na https://wiki.php.net/ nebo napsat a rozvířit diskuzi na php.internals a třeba se finally zavede. Ale přijde mi, že všichni ti, kteří to tolik v PHP chtějí, si myslí, že se to zavede nějak samo od sebe. Nikde jsem v poslední době od nikoho neviděl na php.internals návrh nebo nějakou diskuzi.
Miloslav Ponkrác:
Protože posledních 10 let to jde s programovacími jazyky z kopce. Namísto praktičnosti a potřebnosti se řeší ideologie a politika. Je to prostě moderní dívat se spíše, které věci jsou módně in, i když jsou na nic a nepraktické a ignorovat požadavky praxe.
Je to jen výstřelek desetiletí, který časem přejde.
Tento módní trend řídit vývoj jazyků politicky postihl více jazyků a některé srazil ke dnu – například Perl. Python tím také hodně ztratil. PHP tím také ztrácí, protože svět dal PHP jasně najevo, že přechod PHP4 -> PHP5 trval přes dva roky. A teď bude zase problém přechod PHP5.2->PHP5.3, který bude také trvat.
Nicméně v PHP není problém finally emulovat. Protože všechny výjimky v PHP musí být potmky třídy Exception, pak catch filtr, který zachytí Exception výjimky funguje.
Hloupé je, že u takto emulovaného finally se ztrácí kontext výjimky, který bude novým throw zresetován a tak se případný příjemnce výjimky, či log aplikace nedozví skutečné místo kde vznikla výjimka, pokud si k tomu nepřidá složitější ošetření.
Stejně tak jsem třeba nepochopil, proč je možné testovat parametry na typ třídy, nebo na array, ale na třeba na int. Proč není možné
function (int $a, bool $b)
{
}
Také jsem nepochopil, proč PHP tak dlouho otálelo s Unicode jako základním typem. Proč PHP dodnes nemá dobrou dokumentaci a řadu věcí se člověk dozví jenom ze zdrojáků PHP.
Jakub Vrána :
Reaguji na „u takto emulovaného finally se ztrácí kontext výjimky“:
Objekt výjimky si s sebou nese místo, kde byl vytvořen, nikoliv kdy byla tato výjimka vyhozena:
<?php
$e = new Exception;
try {
throw $e;
} catch (Exception $e) {
echo $e->getLine(); // vypíše 2, nikoliv 4
}
?>
Kontext by se posunul jen v případě, že bych výjimku vyhodil znovu: <?php throw new Exception("", 0, $e); ?>. Pak by původní výjimka byla dostupná přes $e->getPrevious().
Z tohoto chování těží i kód v tomto článku.
Miloslav Ponkrác:
Děkuji za upřesnění.
To je ale také v PHP špatně.
Když tedy udělám
$predpripravena vyjimka = new NejakaVyjimka();
...
if (...)
throw $predpripravena_vyjimka
Tak zase dostávám od PHP jádra špatné informace.
Jakub Vrána :
Ohledně napovídání skalárních typů: U dynamicky typovaného jazyka nepanovala shoda na tom, jestli by (int $a) mělo přijímat výhradně int nebo by mělo přijmout třeba i string a převést ho na int. Nebo dokonce i objekt s metodou __toString(). Oba způsoby se někdy hodí a mají své opodstatnění, takže vznikly i návrhy, že (int $a) bude přijímat výhradně int a ((int) $a) přijme cokoliv a převede to na int.
Ohledně typu Unicode se tak dlouho otálelo, až se zrušil úplně… Poptávka po něm jednoduše nebyla tak velká, aby se někomu chtělo dořešit všechny problémy s tím spojené. Ono to není zase tak jednoduché, jak to na první pohled vypadá. A nepodpora Unicode zároveň není žádný zásadní problém.
Ohledně dokumentace to je asi relativní. Já když jsem s PHP začínal, tak mi dokumentace přišla jako jedna z nejlepších. Kdykoliv jsem něco potřeboval, tak jsem to tam našel. To se mi třeba u C nikdy tak dobře nedařilo a podobně později třeba u Javy nebo z jiného soudku třeba u Oracle.
Později jsem se do zdrojáků taky začal koukat, to je pravda. Některé věci ale podle mě nestojí za to dokumentovat. Nicméně jako spoluautor oficiální dokumentace si rád poslechnu konkrétní výtky. Tedy tu „řadu věcí“, které se člověk dozví jen ze zdrojáků.
Miloslav Ponkrác:
Přesně z takových důvodů existují psané standardy programovacích jazyků. Aby se to promyslelo a pak implementovalo.
„U dynamicky typovaného jazyka nepanovala shoda na tom, jestli by (int $a) mělo přijímat výhradně int nebo by mělo přijmout třeba i string a převést ho na int. Nebo dokonce i objekt s metodou __toString(). Oba způsoby se někdy hodí a mají své opodstatnění, takže vznikly i návrhy, že (int $a) bude přijímat výhradně int a ((int) $a) přijme cokoliv a převede to na int.“
Pokud kontroluji typ, tak prostě kontroluji typ. Tedy int má přijímat jenom int. Pokud chci přijímat i něco jiného, pak to zkontroluji svým vlastním kódem uvnitř.
Samozřejmě hodí se někdy úplně všechno – tím opodstatníte cokoli, kdekoli a kdykoli. Nicméně jazyk by měl mít jednotnou logiku a ne každý pes jiná ves.
---
„Poptávka po něm jednoduše nebyla tak velká…“
Tak to je vtip. Já sám si vzpomínám na dávnou komunikaci s týmem PHP a bylo to velmi velmi frustrující. Několikrát po sobě jsem jim na jejich argumenty dokázal proč něco nejde, že tým PHP lže jako když tiskne. Teprve když jsem jim v diskusi jejich argumentaci dokazoval na každou reakci, že je to proud lží, konečně jsme se dohrabali k řešení a začali konečně jednat normálně.
---
„A nepodpora Unicode zároveň není žádný zásadní problém.“
To je velmi zásadní problém.
PHP je v současnosti jazyk, ve kterém jako ve strojáku uchováváte stringy v binární podobě, ručně řešíte jeho kódování a všechny binární drobnosti. PHP je snad jediný jazyk, který v práci s řetězci je zhruba na úrovni programování v assembleru.
Nemůžu si momentálně vzpomenou na žádný jiný programovací jazyk (kromě velmi starodávného C), který by neměl dva oddělené typy „binární kus dat“ a typ „řetězec“. Jen PHP.
---
„Později jsem se do zdrojáků taky začal koukat, to je pravda. Někte
ré věci ale podle mě nestojí za to dokumentovat.“
Doufám, že celá dokumentace v PHP nevisí na jednom člověku.
---
„Nicméně jako spoluautor oficiální dokumentace si rád poslechnu konkrétní výtky. Tedy tu „řadu věcí“, které se člověk dozví jen ze zdrojáků.“
Celá dokumentace je špatně a nepřesně.
Kam se podívám.
Například náhodně – funkce strlen. Prý vrací „string length“, což není pravda. Vrací počet bajtů daného binárního kusu. Pokud je třeba string kódovaný v UTF-8, tak to co vrací funkce strlen se jenom stěží dá prohlásit za „string length“.
O tom popis funkce strlen ani nenaznačuje.
A tak bych mohl vzít polovinu stránek kam se v manuálu podívám.
Jakub Vrána :
„Tedy int má přijímat jenom int.“ – To je ovšem v zásadním rozporu s chováním většiny funkcí v PHP. Ty sice mají v deklaraci uvedeno, že přijímají int, ale spokojí se i s číselným řetězcem. Konzistence jazyka by tedy významně utrpěla.
„Poptávka po něm jednoduše nebyla tak velká…“ – Důležitá je ta druhá část věty :-), tedy: „aby se někomu chtělo dořešit všechny problémy s tím spojené“.
Mě osobně nepodpora Unicode skutečně žádný vážný problém nezpůsobuje. Když potřebuji v PHP pracovat s řetězci na úrovni znaků (což samo o sobě není moc často), použiji iconv nebo MBstring. A jsem toho názoru, že to nezpůsobuje velký problém ani drtivé většině ostatních programátorů.
Celá dokumentace samozřejmě na jednom člověku nevisí. Nechápu, jak jsi k tomu dospěl.
Ohledně výtky ke strlen() – přečti si hned první větu v dokumentaci datového typu string: “A string is series of characters, where a character is the same as a byte.” http://www.php.net/manual/en/language.types.string.php. Na tuto stránku mimochodem vede z dokumentace funkce strlen() nápadný odkaz.
qwe:
Taky nechápu, v čem vám "nepodpora" Unicode způsobuje prakticky při vývoji webových aplikací problém.
kukulich:
"Pokud kontroluji typ, tak prostě kontroluji typ. Tedy int má přijímat jenom int. Pokud chci přijímat i něco jiného, pak to zkontroluji svým vlastním kódem uvnitř."
Typická ukázka jako fungují třeba různé knihovny a frameworky:
<?php
/**
* @param int $foo
*/
public function setFoo($foo)
{
$this->foo = (int) $foo;
}
?>
Takže v docblocku se píše, že přijímá int, ale pak dojde k automatickému přetypování. Proto se řešilo, co by mělo setFoo(int $foo) vlastně dělat.
Jiří Knesl:
"Tedy int má přijímat jenom int." - to je podle mě špatně. Měl bych mít možnost předat cokoliv, co "implementuje rozhraní Numeric" (kdyby nějaké existovalo) a PHP by si to při první vynuceném přetypování zavolalo s nějakou metodou toInt() (nebo toInt16(), toInt32()..., nebo obecně do toNumeric() ?)
kukulich:
Osobně mi asi nejvíc vadí špatně zdokumentované rozšíření Reflection. U spousty funkcí se toho člověk moc nedozví a musí buď procházet zdrojáky nebo si dělat různé debugy. Např. http://cz.php.net/manual/en/reflectionclass.getconstants.php. Těžko se člověk z dokumentace dozví, jak vracené pole vypadá. A takových funkcí je u Reflection spousta.
Jakub Vrána :
To je hezká ukázka, dokumentace reflexe je skutečně slabá. Ty o této extenzi máš perfektní přehled, nechceš se na její dokumentaci podílet? Ať už oficiálně (není to až tak složité: http://doc.php.net/php/dochowto/) nebo polooficiálně (pošli mi patche http://svn.php.net/viewvc/phpdoc/en/trunk…/reflection/ a já je commitnu s uvedením tvého jména) nebo neoficiálně (pošli mi co kam doplnit a já to opět commitnu s uvedením tvého jména).
Popis návratové hodnoty ReflectionClass::getConstants() jsem doplnil, funkci jsem ale nadále nechal jako nedokumentovanou (např. proto, že chybí příklad).
kukulich:
To je dobrý nápad! Požádám si o svn účet. A zkusím přesvědčit i svého parťáka Andrewa. Je lepší v angličtině než já, tak to můžeme dát dohromady společně.
Andrew:
Jo a až tu dokumentaci dostaneme do stavu, že bude odpovídat skutečnosti, budou mít PHP-hateři další zbraň, protože uvidí, jak strašně je reflexe v PHP nekonzistentní :)
Jakub Vrána :
A tomu logicky následuje další krok – poslání pár patchů do zdrojáků PHP a následné rozšíření karmy na možnost tam rovnou commitovat…
optik:
před nedávnem byla oficiálně oznámena podpora online editoru
https://edit.php.net/, takže můžeš zkoušet i tam. Což je proti svn, instalování phd a buildování dokumentace sám asi nejlepší možnost, je ale otázka, jak to bude ve skutečnosti fungovat
Jakub Vrána :
Online editor jsem před časem zkoušel a normálně fungoval. Ale pro mně osobně je nadále pohodlnější to SVN, PhD a buildování. Tento offline proces se za poslední rok taky hodně zjednodušil a zrychlil – configure.php zjišťující validitu celého manuálu je rovnou v repozitáři dokumentace a běží velmi rychle (alespoň ve srovnání s dřívějším stavem a vzhledem k rozsahu dokumentace). A PhD je taky rychlý a pohodlný na používání (dá se zkompilovat třeba jen jedna kapitola).
kukulich:
O online editoru vím, ale nechtěl jsem v něm pracovat jako "anonymous". Jak jsem ale koukal, tak přidali přihlašování přes FB, takže to to zatím řeší i bez oficiálního svn účtu.
optik:
Ale to není vůbec pravda, přečtěte si něco v internals od dneška tak měsíc zpět a uvidíte, že se tam řeší hlavně ty praktické věci. PHP je a vždycky bylo hlavně o praktickém vývoji serverové části webu a skoro vůbec ne o akademických věcech. To jak PHP vypadá je dáno především vedením projektu - čistě komunitní styl - jeden z vůbec nejotevřenějších, což zejména v minulosti vedlo trochu k bordelu.
Karel:
> V PHP jsem nikdy neměl příliš velkou potřebu
> tento blok používat. Je to především proto,
> že PHP po sobě na konci skriptu uklízí automaticky ...
Osobně finally používám při auditu - tedy když potřebuji za každou cenu zapsat akci do auditního logu.
Diskuse je zrušena z důvodu spamu.