{"id":229,"date":"2010-08-08T22:27:06","date_gmt":"2010-08-08T21:27:06","guid":{"rendered":"http:\/\/eriz.pcinside.pl\/weblog\/propozycja-niech-php-bedzie-wyjatkiem-229.html"},"modified":"2010-08-08T22:27:06","modified_gmt":"2010-08-08T21:27:06","slug":"propozycja-niech-php-bedzie-wyjatkiem","status":"publish","type":"post","link":"https:\/\/eriz.pcinside.pl\/weblog\/propozycja-niech-php-bedzie-wyjatkiem-229.html","title":{"rendered":"propozycja: niech PHP b\u0119dzie wyj\u0105tkiem"},"content":{"rendered":"<p>Od momentu wprowadzenia namiastki obiekt\u00f3w do naszego kochanego j\u0119zyka, coraz bardziej zacz\u0119\u0142y nasila\u0107 si\u0119 narzekania, \u017ce PHP jest sto lat za konkurencj\u0105 pod wieloma wzgl\u0119dami. Pewnie lista narzeka\u0144 ci\u0105gn\u0119\u0142aby si\u0119 a\u017c do wyczerpania zapas\u00f3w papieru toaletowego w WC, ale na pewne rzeczy progamista nie ma po prostu wp\u0142ywu.<\/p>\n<p>Je\u015bli nie da si\u0119 czego\u015b rozwi\u0105za\u0107 wprost, zawsze mo\u017cna spr\u00f3bowa\u0107 to&#8230; obej\u015b\u0107. Wbrew pozorom, w programowaniu zdarza si\u0119 to nierzadko. <img src=\"https:\/\/eriz.pcinside.pl\/weblog\/wp-includes\/images\/smilies\/e_wink.png\" alt=\";)\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\" \/><\/p>\n<p>A o czym chc\u0119 napisa\u0107? O wyrzucaniu b\u0142\u0119d\u00f3w przez PHP zamiast wyj\u0105tk\u00f3w.<\/p>\n<p><!--more--><\/p>\n<h3>chore pomys\u0142y?<\/h3>\n<p>Na <a href=\"http:\/\/blip.pl\">Blipie<\/a>, na tagu <a href=\"http:\/\/blip.pl\/tags\/php\">#php<\/a> zawrza\u0142a dyskusja: Python vs. PHP. Przytaczanie ca\u0142ej dyskusji by\u0142oby raczej bezsensowne i bym si\u0119 zaje\u017adzi\u0142 z kopiowaniem wypowiedzi wszystkich u\u017cytkownik\u00f3w. W pewnym momencie na tapecie znalaz\u0142 si\u0119 temat totalnego non-sensu PHP w postaci wyrzucania b\u0142\u0119d\u00f3w zamiast wyj\u0105tk\u00f3w. I tu si\u0119 w zupe\u0142no\u015bci zgodz\u0119 &#8211; przecie\u017c funkcje np. otwierania pliku musz\u0105 najpierw sprawdzi\u0107, czy ten plik istnieje, a i tak wyrzuci b\u0142\u0105d.<\/p>\n<p>Aby unikn\u0105\u0107 b\u0142\u0119d\u00f3w, kt\u00f3re nieraz ci\u0119\u017cko wy\u0142apa\u0107 (bo przecie\u017c nie wp\u0142ywaj\u0105 na dalsze dzia\u0142anie kodu), trzeba albo &#8222;wyciszy\u0107&#8221; wypluwanie warning\u00f3w korzystaj\u0105c z ma\u0142piszona przed funkcj\u0105, albo sprawdzi\u0107 poprzez <em>file_exists<\/em> istnienie obiektu \u017ar\u00f3d\u0142owego oraz jego uprawnienia przez <em>is_readable<\/em>. <em>Po kiego<\/em>, skoro jest to ponowne wykonanie czego\u015b, co zosta\u0142o ju\u017c sprawdzone.<\/p>\n<p>Ok, skoro s\u0105 b\u0142\u0119dy, to PHP pozwala te\u017c co\u015b z nimi zrobi\u0107 &#8211; albo schowa\u0107, albo zignorowa\u0107, albo wy\u0142apa\u0107. Zajmiemy si\u0119 tym ostatnim przypadkiem. <a href=\"http:\/\/php.net\/set_error_handler\"><em>set_error_handler<\/em><\/a> chyba wi\u0119kszo\u015b\u0107 Czytelnik\u00f3w ju\u017c zna (albo w\u0142a\u015bnie poznaje). Pozwala na przechwycenie wypluwanego przez wszystkie funkcje b\u0142\u0119du i ew. zalogowanie, etc. Oczywi\u015bcie, z np. <em>PARSE_ERROR<\/em> ju\u017c nie jest tak r\u00f3\u017cowo (bo b\u0142\u0119du parsowania kodu si\u0119 nie da przechwyci\u0107 &#8211; co by by\u0142o w sytuacji, gdyby \u00f3w znajdowa\u0142 si\u0119 w algorytmie przechwytywania? ;)).<\/p>\n<h3>PHP daje popali\u0107<\/h3>\n<p>&#8222;Najprostsze rozwi\u0105zania s\u0105 zwykle najlepsze&#8221;. Przeanalizujmy poni\u017cszy kawa\u0142ek kodu.<\/p>\n<p><code lang=\"php\">$f = fopen('asdasdasdasd', 'r');<\/code><\/p>\n<p>Otrzymamy <em>warning: &#8230; failed to open stream<\/em>. No ok, ale to przy aktywnym wy\u015bwietlaniu b\u0142\u0119d\u00f3w. Zwykle podobny kod jest wykorzystywany z innymi przydatnymi funkcjami (celowo tutaj nie uwzgl\u0119dniam <em>file_get_contents<\/em>, etc):<\/p>\n<p><code lang=\"php\">$f = fopen('asdasdasd', 'r');<br \/>\nflock($f, LOCK_SH);<\/p>\n<p>while(!feof($f)){<br \/>\n    echo fread($f, 512);<br \/>\n}<\/code><\/p>\n<p>Otrzymamy ju\u017c wi\u0119cej ni\u017c jeden b\u0142\u0105d. Na serwerze produkcyjnym nie wy\u015bwietli si\u0119 \u017caden (przynajmniej nie powinien), je\u015bli administrator nie pilnuje serwisu, nikt mo\u017ce nawet nie zauwa\u017cy\u0107, \u017ce co\u015b jest nie tak.<\/p>\n<p>Ale nasz admin, czy programista jest jednak troch\u0119 bardziej \u0142ebski i zastosowa\u0142 dodatkowo poni\u017cszy kawa\u0142ek kodu:<\/p>\n<p><code lang=\"php\">function catchError($no, $str, $file, $line){<br \/>\n    logger::instance()->write($str.' -> '.$file.':'.$line);<br \/>\n    return true;<br \/>\n}<\/p>\n<p>set_error_handler('catchError');<br \/>\n<\/code><\/p>\n<p>Fajnie, cacy, logujemy sobie to do pliku, czasem admin zajrzy i znajdzie b\u0142\u0119dy. Ok, tylko \u017ce idealny kod wygl\u0105da\u0142by tak:<\/p>\n<p><code lang=\"php\">if(file_exists($fname) && is_readable($fname) && $f = fopen($fname, 'r')){<br \/>\n    if(flock($f, LOCK_SH)){<br \/>\n        while(!feof($f)){<br \/>\n            echo fread($f, 512);<br \/>\n        }<br \/>\n    }else{<br \/>\n        echo 'co\u015b nie tak';<br \/>\n    }<br \/>\n}else{<br \/>\n    echo 'co\u015b nie tak';<br \/>\n}<br \/>\n<\/code><\/p>\n<p>A\u017c si\u0119 prosi o wyj\u0105tki. Zamiast tego mamy w chorob\u0119 sprawdzania i tak naprawd\u0119 &#8211; jest to spora cz\u0119\u015b\u0107 tego, co chcemy zrobi\u0107. Oczywi\u015bcie, je\u015bli s\u0105 to b\u0142\u0119dy, to mo\u017cna p\u00f3j\u015b\u0107 na lenia i zrezygnowa\u0107 ze sprawdzania oraz to wyciszy\u0107.<\/p>\n<p>Idealnie by by\u0142o jednak to wpakowa\u0107 w blok <em>try..catch<\/em> i obs\u0142u\u017cy\u0107 wyj\u0105tkiem, ale standardowe funkcje PHP ich nie wyrzucaj\u0105. Dopiero biblioteki, kt\u00f3re powsta\u0142y w p\u00f3\u017anych latach programowania PHP5 zacz\u0119\u0142y ich u\u017cywa\u0107. W innych j\u0119zykach, to standard, co\u015b w stylu <em>IOException<\/em>, czy pochodne. Dlaczego PHP tego nie ma?!<\/p>\n<h3>prawa Murphy&#8217;ego: &#8222;prowizorka jest zawsze najlepsza&#8221;<\/h3>\n<p>Mia\u0142em okazj\u0119 rozmawia\u0107 z go\u015bciem na roku, kt\u00f3ry ju\u017c nieco programowa\u0142, ale w\u0105tpi\u0142 w filozofi\u0119 wyj\u0105tk\u00f3w. No ok, jego sprawa, ale rozwa\u017cmy prost\u0105 sytuacj\u0119:<\/p>\n<p><code lang=\"php\">$f = @fopen('zuo', 'r');<\/code><\/p>\n<p>No i nasza zmienna <em>$f<\/em> zawiera albo zas\u00f3b, albo <em>false<\/em>. A sk\u0105d mamy wiedzie\u0107, czy to by\u0142 b\u0142\u0105d timeoutu, czy brak uprawnie\u0144, czy po prostu nie istniej\u0105cego pliku? U\u017cyjmy bardzo prostego przyk\u0142adu (fakt, troch\u0119 naginany, ale zobrazuje moj\u0105 my\u015bl bez zb\u0119dnego owijania):<\/p>\n<p><code lang=\"php\"><br \/>\ntry{<br \/>\n    $f = fopen('asd', 'a');<br \/>\n    flock($f, LOCK_EX);<br \/>\n    \/\/...<br \/>\n}catch (IOException $ex){<br \/>\n    switch ($ex->type){<br \/>\n        case 'noExists':<br \/>\n            \/\/<br \/>\n        break;<br \/>\n        case 'noPermissions':<br \/>\n            \/\/<br \/>\n        break;<br \/>\n    }<br \/>\n}<\/code><\/p>\n<p>Aby zrealizowa\u0107 ten hipotetyczny kawa\u0142ek kodu, nale\u017ca\u0142oby u\u017cy\u0107 mniej wi\u0119cej:<\/p>\n<p><code lang=\"php\">class IOException extends Exception {}<\/p>\n<p>try {<br \/>\n    if (!file_exists('asd')) {<br \/>\n        throw new IOException('noExists');<br \/>\n    }<br \/>\n    if (!is_writable('asd')) {<br \/>\n        throw new IOException('noPermissions');<br \/>\n    }<br \/>\n    $f = fopen(...);<\/p>\n<p>    \/\/...<br \/>\n}<br \/>\ncatch (IOException $ex) {<br \/>\n    \/\/ reszta jak wy\u017cej<br \/>\n}<br \/>\n<\/code><\/p>\n<p><q>No\u017c w mord\u0119!<\/q> Wymy\u015blanie ko\u0142a na nowo. A je\u015bli jest, do tego, takich miejsc w kodzie kilkana\u015bcie albo i wi\u0119cej? Koszmar. Niekt\u00f3rzy pewnie napisali ju\u017c swojego obiektowego wrappera, kt\u00f3ry za\u0142atwia takie rzeczy. Jednak nadal kusz\u0105 te wyj\u0105tki. Ciekawe, ile ticket\u00f3w w tej sprawie wisi na bugtracku developer\u00f3w PHP&#8230; <img src=\"https:\/\/eriz.pcinside.pl\/weblog\/wp-includes\/images\/smilies\/e_wink.png\" alt=\";)\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\" \/><\/p>\n<p>Wpad\u0142 mi jednak do g\u0142owy pewien pomys\u0142.<\/p>\n<h3>jak nie do przodu, to ty\u0142em naprz\u00f3d<\/h3>\n<p>Najlepiej niech kod przem\u00f3wi:<\/p>\n<p><code lang=\"php\">function catchError(...) {<br \/>\n    throw new Exception();<br \/>\n}<\/p>\n<p>set_error_handler('catchError');<\/code><\/p>\n<p>My\u015bl\u0119 sobie, <em>za choler\u0119 nie b\u0119dzie dzia\u0142a\u0107<\/em>. Jednak przyzwyczajony do niekt\u00f3rych dziwactw tego j\u0119zyka postanowi\u0142em odpali\u0107 podobny do powy\u017cszego kawa\u0142ek kodu:<\/p>\n<p><code lang=\"php\">function catchError($no, $str, $file, $line) {<br \/>\n    throw new Exception($str);<br \/>\n}<\/p>\n<p>set_error_handler('catchError');<\/p>\n<p>try {<br \/>\n    echo 'raz';<br \/>\n    fopen('asdasd', 'r');<\/p>\n<p>    echo 'dwa';<br \/>\n    $p = file_get_contents('asfjhsdfkd');<\/p>\n<p>    echo 'trzy';<br \/>\n}catch (exception $ex) {<br \/>\n    echo $ex;<br \/>\n}<\/code><\/p>\n<p>Jakie\u017c by\u0142o moje zdziwienie, gdy przegl\u0105darka wy\u015bwietli\u0142a:<\/p>\n<p><code lang=\"text\">razexception 'Exception' with message 'fopen(asdasd) [function.fopen]: failed to open stream: No such file or directory' in E:\\www\\poligon\\exception\\test.php:6<br \/>\nStack trace:<br \/>\n#0 [internal function]: catchError(2, 'fopen(asdasd) [...', 'E:\\www\\poligon\\...', 13, Array)<br \/>\n#1 E:\\www\\poligon\\exception\\test.php(13): fopen('asdasd', 'r')<br \/>\n#2 {main}<\/code><\/p>\n<p>Wy\u015bwietli\u0142o si\u0119 tylko <em>raz<\/em> i zrzutowany na stringa wyj\u0105tek. Przecie\u017c to ustrojstwo&#8230; Dzia\u0142a?! A st\u0105d ju\u017c kr\u00f3tka droga do rozr\u00f3\u017cnienia, kt\u00f3ry b\u0142\u0105d wyst\u0105pi\u0142.<\/p>\n<h3>\u017ce co?!<\/h3>\n<p>Tak, do tej pory jako\u015b do mnie nie trafia, \u017ce:<\/p>\n<ul>\n<li>w og\u00f3le co\u015b takiego zadzia\u0142a\u0142o<\/li>\n<li>\u017ce tak prostego obej\u015bcia jeszcze si\u0119 nie wykorzystuje na szersz\u0105 skal\u0119<\/li>\n<\/ul>\n<p>Owszem, na pewno jest to rozwi\u0105zanie wolniejsze ni\u017c prawdopodobna implementacja w kodzie interpretera. Jednak zyskujemy co\u015b, co pozwala nam na lepsz\u0105 i wygodniejsz\u0105 kontrol\u0119 kodu. Ju\u017c sam fakt, \u017ce zosta\u0142 wypluty wyj\u0105tek &#8211; tak naprawd\u0119 &#8211; ma do\u015b\u0107 du\u017ce znaczenie.<\/p>\n<p>Pozostaje jeszcze dostosowanie funkcji wy\u0142apuj\u0105cej b\u0142\u0119dy tak, aby wypluwa\u0107 poszczeg\u00f3lne rodzaje wyj\u0105tk\u00f3w.<\/p>\n<p><code lang=\"php\">class IOException extends Exception {<br \/>\n    public $type = null;<\/p>\n<p>    function __construct($message = null, $code = 0) {<br \/>\n        parent::__construct($message, $code);<\/p>\n<p>        if(strpos($message, 'no such') !== null){<br \/>\n            $this->type = 'noExists';<br \/>\n        }else if (strpos($message, 'permission') !== null) {<br \/>\n            $this->type = 'noPermissions';<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>function catchError($no, $str, $file, $line) {<br \/>\n    if ($no == E_WARNING) {<br \/>\n        $func = substr($str, 0, strpos($str, '('));<br \/>\n        switch ($func) {<br \/>\n            case 'fopen':<br \/>\n            case 'fwrite':<br \/>\n            case 'file_get_contents':<br \/>\n            case 'file_put_contents':<br \/>\n            case 'flock':<br \/>\n                \/\/...<br \/>\n                throw new IOException($str);<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>set_error_handler('catchError');<\/p>\n<p>try {<br \/>\n    echo 'raz';<br \/>\n    fopen('asdasd', 'r');<\/p>\n<p>    echo 'dwa';<br \/>\n    $p = file_get_contents('asfjhsdfkd');<\/p>\n<p>    echo 'trzy';<br \/>\n}<br \/>\ncatch (IOException $ex) {<br \/>\n    echo $ex->type;<br \/>\n}<\/code><\/p>\n<p>Nie jest to mo\u017ce idealne sprawdzanie w wyj\u0105tku, ale&#8230; dzia\u0142a.<\/p>\n<h3>koniec?<\/h3>\n<p>Przyznam szczerze, \u017ce do tej pory nie bardzo mie\u015bci si\u0119 to wszystko w moim ma\u0142ym rozumku, \u017ce do\u015b\u0107 prowizoryczne rozwi\u0105zanie po prostu dzia\u0142a. Pewnie znalaz\u0142yby si\u0119 lepsze metody wykrywania rodzaju b\u0142\u0119du w wyj\u0105tku, ale chcia\u0142em zaprezentowa\u0107 tylko og\u00f3ln\u0105 ide\u0119.<\/p>\n<p>Zapraszam do dyskusji. <img src=\"https:\/\/eriz.pcinside.pl\/weblog\/wp-includes\/images\/smilies\/e_smile.png\" alt=\":)\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\" \/><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Od momentu wprowadzenia namiastki obiekt\u00f3w do naszego kochanego j\u0119zyka, coraz bardziej zacz\u0119\u0142y nasila\u0107 si\u0119 narzekania, \u017ce PHP jest sto lat za konkurencj\u0105 pod wieloma wzgl\u0119dami. Pewnie lista narzeka\u0144 ci\u0105gn\u0119\u0142aby si\u0119 a\u017c do wyczerpania zapas\u00f3w papieru toaletowego w WC, ale na pewne rzeczy progamista nie ma po prostu wp\u0142ywu. Je\u015bli nie da si\u0119 czego\u015b rozwi\u0105za\u0107 wprost, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11],"tags":[48,43,30],"_links":{"self":[{"href":"https:\/\/eriz.pcinside.pl\/weblog\/wp-json\/wp\/v2\/posts\/229"}],"collection":[{"href":"https:\/\/eriz.pcinside.pl\/weblog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/eriz.pcinside.pl\/weblog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/eriz.pcinside.pl\/weblog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/eriz.pcinside.pl\/weblog\/wp-json\/wp\/v2\/comments?post=229"}],"version-history":[{"count":0,"href":"https:\/\/eriz.pcinside.pl\/weblog\/wp-json\/wp\/v2\/posts\/229\/revisions"}],"wp:attachment":[{"href":"https:\/\/eriz.pcinside.pl\/weblog\/wp-json\/wp\/v2\/media?parent=229"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/eriz.pcinside.pl\/weblog\/wp-json\/wp\/v2\/categories?post=229"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/eriz.pcinside.pl\/weblog\/wp-json\/wp\/v2\/tags?post=229"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}