За последние 24 часа нас посетили 22440 программистов и 1150 роботов. Сейчас ищут 645 программистов ...

"красивый" вывод исключений и ошибок.

Тема в разделе "Прочие вопросы по PHP", создана пользователем Koc, 18 апр 2009.

  1. alexey_baranov

    alexey_baranov Активный пользователь

    С нами с:
    3 фев 2009
    Сообщения:
    647
    Симпатии:
    0
    Адрес:
    Сургут
    вовсе нет. смотри третий скрин и пояснение к нему.
     
  2. alexey_baranov

    alexey_baranov Активный пользователь

    С нами с:
    3 фев 2009
    Сообщения:
    647
    Симпатии:
    0
    Адрес:
    Сургут
    Хочу поделиться своими результатами обработки ошибок в графическом интерфейсе. Раньше у меня была проблема, что ошибка в любой модели обрывала все и отображалась в виде красного X-Debuga. Круто да?

    Следующим этапом был перехват ошибок в set_error_handler() если я не ошибаюсь и отображение в балее меннее красивом окне сообщения об ошибке. Но у этого метода есть недостатоки. Во- первых он не видет форму, чтобы смотреть то что он ввел и одновременно читать в чем не прав, а во- вторых, когда он нажмет кнопку "Назад" все данные, которые он ввел теряются. Маты были жуткие.

    На третьем этапе борьбы появились конструкции try catch вокруг всех пользовательских действий. Вывод стал красивым вверху страницы и данные сохранялись. Это уже была победа. Но неудобно, что на кождом действии приходится писать этот try - catch и обрабатывать эти исключения одним и тем же кодом. Поэтому появилась идея вывести этот код за пределы экшена.

    И только сейчас можно сказать, что я победил эту проблему раз и навсегда. Данные теперь сохраняются, страница отображается с сообщением для пользователя, я для этого ничего не делаю. Мечта программиста. Доволен как слон.

    у меня у контроллера (класс Cont) контрол есть такой метод, который связывает пользовательское действие с обработчиком. выгладит это так:

    PHP:
    1.  
    2. <?php
    3. class Cont {
    4.  
    5.     /**
    6.      * связывает событие с обработчиком. срабатывает на EVENT | EVENT_x | EVENT[ ]
    7.      *
    8.      * @param string $event ключ массива $_REQUEST input_key => key
    9.      * @param string $handler название функции- обработчика
    10.      * @param bool $continue продолжать после обработчика дальнейшую обработку? может быть насильственно проделжен возвратом return 'continue'
    11.      * @return void
    12.      *
    13.      */
    14.     public function bindAction($event, $handler, $continue= false){
    15.         if (isset($_REQUEST[$this->input().'_'.$event]) || isset($_REQUEST[$this->input().'_'.$event.'_x'])){
    16.             $result= $this->$handler();
    17.             if (!$continue && $result!='continue')
    18.                 die;
    19.         }
    20.         else{
    21.             foreach ($_REQUEST as $PARAM=>$value)
    22.                 if (eregi("^{$event}\\[[.*]\\]$", $PARAM)){
    23.                     $result= $this->$handler();
    24.                     if (!$continue && $result!='continue')
    25.                         die;
    26.                     break;
    27.                 }
    28.         }
    29.     }
    30.  
    31. }
    А для того чтобы автоматизировать передачу исключений пользователю, я переопределил этот метод у контралера страницы вот так

    PHP:
    1.  
    2. <?php
    3. class PageCont{
    4.     /**
    5.      * необработанная ошибка приводит к отображению формы на момент ошибки с соответствующим сообщением
    6.      *
    7.      * @param string $event событие
    8.      * @param string $handler обработчик
    9.      * @param bool $continue продолжить после обработки события может быть насильственно проделжен возвратом return 'continue'
    10.      */
    11.     function bindAction($event, $handler, $continue= false){
    12.         $page= $this->m;
    13.  
    14. //        ob_start();
    15.         try{
    16.             parent::bindAction($event, $handler, $continue);
    17.         }
    18.         //после PDOException на повтор отправлять нельзя, т.к. транзакция все равно оборвалясь
    19.         catch(PDOException $e){
    20.             throw $e;
    21.         }
    22.         //ошибка во время акшэна -> на повтор
    23.         catch(Exception $e){
    24.             unset ($_REQUEST[$this->input().'_'.$event]);
    25.             unset ($_REQUEST[$this->input().'_'.$event.'_x']);
    26.             unset ($_REQUEST[$this->input().'_'.$event.'_y']);
    27.             $page->error= $e;
    28.             $page->show($this->input());
    29.             die;
    30.         }
    31. //        //варнинги во время сохранения - показать, но не обрывать
    32. //        if (ob_get_length()){
    33. //            $page->error= new WarningException(ob_get_clean());
    34. //        }
    35.     }
    36. }
    При этом, что очень важно, все данные, которые ввел пользователь, включая выбранные фaйлы, сами собой сначала пихаются в модель, а потом повторно отображаются на странице. Ну и естественно, поскольку контроллеры всех страниц пронаследованы от PageCont, на всем сайте сам собой получился однообразный вывод ошибок. очень стильно, очень предсказуемо для пользователя. И конечно радует, что я пишу только код, а если возникает ошибки, то они сами обрабатываются и отображаются пользователю.

    Вот несколько примеров, когда действия пользователя дергают модели и натыкаются там на эксепшины. И вот как это отображаются (в принципе вьюшку эксепшена можно нафантазировать как угодно):

    [​IMG]
    [​IMG]
    [​IMG]
    [​IMG]
    [​IMG]
    [​IMG]

    Понимаете в чем фокус такого трюка? сколько эксепшенов наберется во всех моделях? наверное сотни. Они могут добавляться и удаляться. Но я ни разу не беспокоюсь чтобы их обрабатывать и отображать пользователю. Сюда же входят эксепшены чужих библиотек на сотом уровне вложенности, про которые я ничего не знаю и знать не хочу. меня вообще больше эта проблема не волнует. Если, к примеру даже добавлю новый эксепшен внутри какого- то метода модели, и пользователь на него нарвется, то он и увидет этот эксепшен в верхней части формы, и его данные сохранятся на форме, все это произойдет автоматически, для этого ни строчки не надо писать в форме.
     
  3. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    есть файл

    ob_start();

    какой-то код
    var_dump(smth);
    бросаем исключение
    еще какой-то код

    есть обработчик исключения, установленный через set_exception_handler, который выводит бектрейс, код ошибки и тд. В нем 1 строкой прописано ob_end_flush(); (что б увидеть что var_dump нам скажет). Но выводится только бектрейс. Что я делаю не так?

    если в конструктор класса с исключениями запихнуть ob_end_flush(); то вывод работает. Это так задумано шоле? Фича такая?
     
  4. Костян

    Костян Активный пользователь

    С нами с:
    12 ноя 2009
    Сообщения:
    1.724
    Симпатии:
    1
    Адрес:
    адуктО
    Koc
    да, точно не скажу, но вроде исключения убивают буфер...
     
  5. Костян

    Костян Активный пользователь

    С нами с:
    12 ноя 2009
    Сообщения:
    1.724
    Симпатии:
    1
    Адрес:
    адуктО
    хотя не должны, а ты уверен что буферизация стартовала?
     
  6. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    окей, если буферизация не стартовала, то куда делся мой вар_дамп? Кто его съел?
     
  7. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    А ты уверен что до него дошло дело?
     
  8. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    гарантирую это. Кроме того половина страницы должна отрендериться была. Если я отказываюсь от этого обработчика исключений (удаляю или комментирую строку с его назначением) то var_dump показывает то, что нужно, и пол-страницы видно.

    Кроме того:
    function debug_exception_handler($ex) {
    echo "<b>Error :</b>", $ex->getMessage()."<br />\n";
    }
    сообщение пустое. Убираю этот ссаный обработчик - сообщение выводится.

    Пошел я спать. Не заладилось как-то программирование сегодня.
     
  9. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Ммм, а как ты его повесил?
    Если через error_handler, то там не 1 параметр надо передавать.
     
  10. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    set_exception_handler('debug_exception_handler');
     
  11. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Все отлично работает.
    Можешь раскомментировать echo $r и убедиться.
    PHP:
    1. <?php
    2. function exception_handler($exception) {
    3.     $r = ob_get_clean();
    4.     //echo $r;
    5.     echo '<hr>';
    6.     echo "Uncaught exception: " , $exception->getMessage(), "\n";
    7. }
    8. set_exception_handler('exception_handler');
    9.  
    10. echo 'гарантирую это. Кроме того половина страницы должна отрендериться была. Если я отказываюсь от этого обработчика исключений (удаляю или комментирую строку с его назначением) то var_dump показывает то, что нужно, и пол-страницы видно.
    11.  
    12. Кроме того:
    13. function debug_exception_handler($ex) {
    14. echo "<b>Error :</b>", $ex->getMessage()."<br />\n";
    15. }
    16. сообщение пустое. Убираю этот ссаный обработчик - сообщение выводится.
    17.  
    18. Пошел я спать. Не заладилось как-то программирование сегодня.';
    19. $a = array('nothing', 'to', 'output');
    20. echo '<hr>';
    21. throw new Exception('Uncaught Exception');
    22. echo "Not Executed\n";
     
  12. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    Забавно.
    http://pastebin.mozilla-russia.org/102696

    вывод
    то есть после срабатывания исключения вывод произошел даже без ob_end_flush
     
  13. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    гага!! Вот разгадка почему так происходило
    PHP:
    1. <?
    2.   public function render(array $context)
    3.   {
    4.     ob_start();
    5.     try
    6.     {
    7.       $this->display($context);
    8.     }
    9.     catch (Exception $e)
    10.     {
    11.       ob_end_clean();
    12.  
    13.       throw $e;
    14.     }
    15.  
    16.     return ob_get_clean();
    17.   }
    18.  
    это шаблонизатор буйствовал.
     
  14. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    через register_shutdown_function отлавливаю fatal error'ы. Хочу сделать backtrace - но он пуст. Вернее не то что бы полностью пуст, но в нем только мои обработчики ошибок находятся.

    Как быть?
     
  15. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    По всей видимости это действительно невозможно. Что ж, придется довольствоваться get_included_files
     
  16. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Koc
    код выложи.
    Обработчик + вызов Fatal error
     
  17. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    PHP:
    1. <?php
    2.  
    3. function eh()
    4. {
    5. }
    6.  
    7.  
    8. class D
    9. {
    10.     public function p()
    11.     {
    12.         return $this->c();
    13.     }
    14.    
    15.     public static function c()
    16.     {
    17.         print_r(debug_backtrace());
    18.         return 'c';
    19.     }
    20. }
    21.  
    22. function a()
    23. {
    24.     D::p();
    25. }
    26.  
    27. function b()
    28. {
    29.     echo a();
    30. }
    31.  
    32. D::c(); // тут кой-че будет
    33. b(); // а тут только eh
    34.  

    хотя я наверно некорректно понимаю что такое backtrace. Это ж не просто какие-то последние действия, а именно действия по вызову этой функции? Тогда логично, что ничего оно мне не покажет.
     
  18. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    PHP:
    1. <?php
    2. function eh() {
    3.     var_dump(xdebug_get_function_stack());
    4. }
    5.  
    6. error_reporting(E_ALL | E_STRICT);
    7.  
    8. class D {
    9.  
    10.     public function p() {
    11.         return $this->c();
    12.     }
    13.    
    14.     public static function c() {
    15.         var_dump(debug_backtrace());
    16.         return 'c';
    17.     }
    18. }
    19.  
    20. function a() {
    21.     return D::p();
    22. }
    23.  
    24. function b() {
    25.     echo a();
    26. }
    27.  
    28. D::c(); // тут кой-че будет
    29. b(); // а тут только eh
    30.  
    31.  
     
  19. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Код (Text):
    1. array
    2.   'type' => int 1
    3.   'message' => string 'Using $this when not in object context' (length=38)
    4.   'file' => string '/home/simpliest/work/test/host1/eh.php' (length=35)
    5.   'line' => int 13
    6. array
    7.   0 =>
    8.     array
    9.       'function' => string '{main}' (length=6)
    10.       'file' => string '/home/simpliest/work/test/host1/eh.php' (length=35)
    11.       'line' => int 0
    12.       'params' =>
    13.         array
    14.           empty
    15.   1 =>
    16.     array
    17.       'function' => string 'b' (length=1)
    18.       'file' => string '/home/simpliest/work/test/host1/eh.php' (length=35)
    19.       'line' => int 31
    20.       'params' =>
    21.         array
    22.           empty
    23.   2 =>
    24.     array
    25.       'function' => string 'a' (length=1)
    26.       'file' => string '/home/simpliest/work/test/host1/eh.php' (length=35)
    27.       'line' => int 27
    28.       'params' =>
    29.         array
    30.           empty
    31.   3 =>
    32.     array
    33.       'function' => string 'p' (length=1)
    34.       'class' => string 'D' (length=1)
    35.       'file' => string '/home/simpliest/work/test/host1/eh.php' (length=35)
    36.       'line' => int 23
    37.       'params' =>
    38.         array
    39.           empty
    40.   4 =>
    41.     array
    42.       'function' => string 'eh' (length=2)
    43.       'file' => string '/home/simpliest/work/test/host1/eh.php' (length=35)
    44.       'line' => int 0
    45.       'params' =>
    46.         array
    47.           empty
     
  20. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    ну, круто конечно, но мне б более универсальное решение, без xDebug'а. Про error_get_last я знаю.

    В общем это баг backtrace или все нормально?
     
  21. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Понятия не имею.
    Поищи в баглисте.

    Хотя вряд ли баг. Можешь посмотреть еще APD, как альтернативу xdebug, возможно там тоже есть более глубокий трейс.
     
  22. Костян

    Костян Активный пользователь

    С нами с:
    12 ноя 2009
    Сообщения:
    1.724
    Симпатии:
    1
    Адрес:
    адуктО
    PHP:
    1. <?php
    2.  while(ob_get_level()) {
    3.       $r .= ob_get_clean();
    4.  }
     
  23. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Костян
    Нахрена?

    PHP:
    1. <?php
    2. for ($i = 1; $i < 5; $i++) {
    3.     ob_start();
    4.     echo 'started ' . $i . '<br>';
    5. }
    6. $r =  ob_get_clean();
    7. echo '<hr>';
    8. echo $r;
    9. echo '<hr>';
    10. echo 'nesting ' . ob_get_level();
     
  24. Костян

    Костян Активный пользователь

    С нами с:
    12 ноя 2009
    Сообщения:
    1.724
    Симпатии:
    1
    Адрес:
    адуктО
    PHP:
    1. <?php
    2.      for ($i = 1; $i < 5; $i++) {
    3.          ob_start();
    4.          echo 'started ' . $i . '<br>';
    5.          if ($i == 2) {
    6.             echo 'oops';
    7.             throw new Exception('OOOOOpss');
    8.          }
    9.      }
     
  25. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    И? :) что изменилось?