Модераторы: skyboy, MoLeX, Aliance, ksnk

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Работа с исключениями, Как правильно подходить к ним... 
:(
    Опции темы
Dima 2015
Дата 28.8.2008, 17:07 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 287
Регистрация: 16.3.2008
Где: SPb

Репутация: 2
Всего: 2



Добрый, теперь уже вечер, уважаемые коллеги!

Хотел посоветоваться с опытными людьми - как правильно подходить к выбрасыванию и обработке исключений в проекте, скажем так среднего размера (порядка 100 исполняемых скриптов).

Сейчас в нем никаких исключений нет и клас Exception не трогается вообще нигде. 

У нас сейчас как все сделано. Есть классы, в них ф-ции которые чтото делают, и если сделалось - возвращают ТРУ, иначе - ФАЛСЕ. На этом построена вся логика проекта.

Так вот как я понял, есть другой вариант - ф-ции могут возвращать чего им надо, или вообще ничего, а в случае неудачи просто выбрасывать соответствующие исключения, ведь код то после выброса исключения не выполняется, а ловится catch и оттуда уже пляшет...

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

Что я сделла на данный момент? Есть класс наследуемый от базового класса Exception, в его конструктор передается пачка параметров, с которыми работают методы-обработчики исключений.

Код

/****************************
     * Конструктор - вызывает конструктор родителя и заполняет объект исключения
     *
     * @param string    $message        сообщение пользователю
     * @param integer    $errorLevel        код ошибки
     * @param string    $errorFile        файл с ошибкой
     * @param integer    $errorLine        строка с ошибкой
     * @param string    $admin_message    сообщение администратору
     * @param string    $redirect_url    ссылка для переадресации
     */
    public function __construct($message, $errorLevel = 0, $errorFile = '', $errorLine = 0, $admin_message = '', $redirect_url = '') {
        
        // Вызываем конструктор родителя
        parent::__construct($message, $errorLevel);
        
        // Заполняем объект полученными данными
     $this->file                = $errorFile;
        $this->line                = $errorLine;
    $this->user_message        = $message;
    $this->admin_message    = $admin_message;
     $this->redirect_url        = $redirect_url;

// ...


Основным звеном у меня сдесь является переменная $errorLevel. По ее значению обработчик исключения (метод render() в классе Exceptions) тупым switch-case - ом перебирает ошибки и решает что делать - выводить ли юзеру ошибку (текст которой был передан в конструктор), писать ли лог админу (текст тоже был передан в конструктор) и т.д. и т.п.

И эта схема на все случаи жизни - неверное заполнение форм, попытка взлома, неверный логин-пароль, ошибка БД и прочее прочее прочее...

В исполняемых скриптах я делаю следующее:

Код

// ** Смотрим есть ли в базе логин/пароль
            if ($oUser->find_id($email, $password) === false) {
    
                // Бросаем соответствующее исключение
                $user_message    = 'Неверный логин или пароль';
                $admin_message    = 'Неудачный вход под логином ' . $email;
                throw new Exceptions($user_message, Exceptions::USER_NOTICE , __FILE__, __LINE__, $admin_message);
            }

// ... код выполняемый если не было выброшено исключений

catch (Exceptions $e) {
            
            $e->render();
}


Вот в общем то так. Но чтото у меня есть подозрение что кривоватый подход. Куча параметров передается в конструктор, 1 ф-ция с кучей case-ов при обработке. Я с большими проектами работаю совсем недавно и вот интересно, может есть уже общий "правильный" подход, используемый в данной задаче.

Например была мысль что на каждый логический блок исключений свой класс-наследник - ошибки ввода данных формы, ошибки доступа и тд. Правда так получается что просто куча case-ов заменяется кучей классов и все ))))

В общем хочется послушать что скажут те, кто уже собаку съел на этом деле, можно даже послать меня читать ченить умное, желательно по-русски. Хотя я поиск прошерстил уже нехило, в основном встречаются простейшие примеры - аля кидать исключение вот так, ловить вот так...
PM MAIL ICQ   Вверх
Mal Hack
Дата 28.8.2008, 17:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Мудрый...
****


Профиль
Группа: Участник Клуба
Сообщений: 9926
Регистрация: 15.2.2004

Репутация: 122
Всего: 261



Лично я всегда исходил из такой позиции: 
1. Надо отличать исключения и проверку работы функции на ошибку.
2. Исключения надо ловить там, где они есть.
3. Проверку выполнения надо делать там, где может быть ошибка (возвращена функцией)
4. Реализация своих исключений должна быть тогда, когда это явно проще с точки зрения обработки (большое кол-во последовательных операций)
PM ICQ   Вверх
Dima 2015
Дата 28.8.2008, 18:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 287
Регистрация: 16.3.2008
Где: SPb

Репутация: 2
Всего: 2



Mal Hack

1. А что ты вообще подразумеваешь проверкой ф-ции на ошибку?
2. Мне бы понять где они и с чем их есть вообще )))

У меня с идеологией проблемы видимо. Синтаксис и конструкции я худо-бедно разобрал. А вот где и как они должны применяться не понимаю. 

3. Ну и соответственно что ты подразумеваешь под проверкой выполнения...
4. Надо так понимать когда логика сожная - куча вложенных ифов? Тогда проще исключения делать? Чтобы сразу предусмотреть все "плохое", что может случиться...

В общем хочется както более развернуто чтоли, а то я даже синтаксис фраз твоих не очень понимаю ))) Хотя может просто голова уже не варит...
PM MAIL ICQ   Вверх
Mal Hack
Дата 28.8.2008, 23:06 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Мудрый...
****


Профиль
Группа: Участник Клуба
Сообщений: 9926
Регистрация: 15.2.2004

Репутация: 122
Всего: 261



Цитата(Dima 2015 @  28.8.2008,  19:11 Найти цитируемый пост)
1. А что ты вообще подразумеваешь проверкой ф-ции на ошибку?

 fp = fopen()
if(!fp) {error}

Цитата(Dima 2015 @  28.8.2008,  19:11 Найти цитируемый пост)
2. Мне бы понять где они и с чем их есть вообще )))

http://www.php.net/manual/en/language.exceptions.php

Цитата(Dima 2015 @  28.8.2008,  19:11 Найти цитируемый пост)
3. Ну и соответственно что ты подразумеваешь под проверкой выполнения...

см. п.1.

Цитата(Dima 2015 @  28.8.2008,  19:11 Найти цитируемый пост)
4. Надо так понимать когда логика сожная - куча вложенных ифов? Тогда проще исключения делать? Чтобы сразу предусмотреть все "плохое", что может случиться...

Это уже зависит от задачи, точнее от способа ее реализации smile
PM ICQ   Вверх
Dima 2015
Дата 29.8.2008, 10:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 287
Регистрация: 16.3.2008
Где: SPb

Репутация: 2
Всего: 2



Mal Hack, понятно... : )))

Видать это токо с опытом приходит. По сслыке что ты дал я ходил уже раз 10 : ))
PM MAIL ICQ   Вверх
solenko
Дата 29.8.2008, 11:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1473
Регистрация: 15.1.2006
Где: Украина

Репутация: 34
Всего: 67



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

У вас на последнем этапе выясняется, что корабль забыли заправить. Вы генерите exception, который отлавливается в самой верхней функции. Итог -- отсутствие кучи проверок + общественность оповещена, что в этот раз выяснить ничего не удалось + причина ушла в лог.

Ну и, естественно, exception оно на то и исключение, чтобы генерится при внештатной работе скрипта. Лично я против их использования, вместо обычных проверок, например, прав доступа. Но это уже дело вкуса и религии )





--------------------
Ла-ла-ла-ла
Заметьте, нет официального подтверждения, что это не просто четыре слога.
PM MAIL WWW ICQ Skype   Вверх
Dima 2015
Дата 29.8.2008, 11:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 287
Регистрация: 16.3.2008
Где: SPb

Репутация: 2
Всего: 2



solenko, пример порадовал ))))

Вообще тема интересная конечно, мне начинает казаться что грамотное использование исключений сродни искусству. Жаль народ вяло отвечает...
PM MAIL ICQ   Вверх
solenko
Дата 29.8.2008, 11:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1473
Регистрация: 15.1.2006
Где: Украина

Репутация: 34
Всего: 67



Забыл упомянуть, что все функции в иерархии должны генерить exception, кастомного класса и, соответственно, в верхней функции нужно ловить только его.


--------------------
Ла-ла-ла-ла
Заметьте, нет официального подтверждения, что это не просто четыре слога.
PM MAIL WWW ICQ Skype   Вверх
Dima 2015
Дата 29.8.2008, 11:31 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 287
Регистрация: 16.3.2008
Где: SPb

Репутация: 2
Всего: 2



solenko, прости, а что ты называешь верхней ф-цией?
PM MAIL ICQ   Вверх
solenko
Дата 29.8.2008, 12:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1473
Регистрация: 15.1.2006
Где: Украина

Репутация: 34
Всего: 67



Dima 2015, ну в моем примере это "Проверить есть ли жизнь на марсе". Суть в  том, что генерация MarsException будет означать, что скрипт, в принципе, отработал корректно, но по какой-то причине не смог выполнить все. А если у тебя случился просто Exception, то речь уже идет действительно о ошибке.


--------------------
Ла-ла-ла-ла
Заметьте, нет официального подтверждения, что это не просто четыре слога.
PM MAIL WWW ICQ Skype   Вверх
sTa1kEr
Дата 29.8.2008, 12:15 (ссылка) |    (голосов:3) Загрузка ... Загрузка ... Быстрая цитата Цитата


9/10 программиста
***


Профиль
Группа: Завсегдатай
Сообщений: 1553
Регистрация: 21.2.2007

Репутация: 56
Всего: 146



Dima 2015, когда следует использовать екзепшены, советую прочитать этот пост.

Цитата(Dima 2015 @  28.8.2008,  18:07 Найти цитируемый пост)
throw new Exceptions($user_message, Exceptions::USER_NOTICE , __FILE__, __LINE__, $admin_message);

1. Бессмысленно тут передавать  __FILE__ и __LINE__, т.к. эти данные и так содержатся в экзепшене. 
2. Использовать экзепшены только для логирования ошибок - все равно что забивать гвозди микроскопом, т.к. это значительно более гибкий и мощьный инструмент.
3. В данном куске кода экзепшены не нужны вовсе, т.к. не правильный логин/пароль - это вовсе не исключительная ситуация. Исключительная ситуация была бы, к примеру, если бы логин и пароль был верный, но запись о пользователе отсутствовала.

Попробую объяснить на примере.
Код

function authenticate($login, $password)
{
    // Оба аргумента обязательны, по этому если хоть один из них
    // пустой, то мы бросаем исключение наверх
    if (empty($login))
    {
        throw new EmptyArgumentException('login');
    }
    
    if (empty($password))
    {
        throw new EmptyArgumentException('password');
    }
    
    // Здесь мы уверены что $login и $password у нас не пустые

    try 
    {
        // Для примера тут проверка логина и пароля из файла
        
        return $result;
    }
    catch (FileNotFoundException $ex) 
    {
        // Т.к. мы работаем с файлами, то мы ожидаем соответсвующие экзепшены 
        // Коду выше нечего знать, что у нас тут проблемы с файлами,
        // по этому перехватываем тут и возвращаем, что юзер не авторизован
        
        return false;
    }
    catch (Exception $ex)
    {
        // А остальные исключения бросаем дальше
        throw $ex; // Ничего не возвращаем!
    }
}

try 
{
    $result = authenticate($login, $password);
    
    var_dump($result);
}
catch (Exception $ex)
{
    // Internal Error! Т.е. именно ошибка, исключительная ситуация. 
    // Это вовсе не значит что юзер не авторизован.
}


И еще, применимо к PHP:
Код

function exception_error_handler($errno, $errstr, $errfile, $errline )
{
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler("exception_error_handler");

В идеале, любой скрипт должен работать именно в таком режиме.
PM MAIL   Вверх
Dima 2015
Дата 29.8.2008, 13:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 287
Регистрация: 16.3.2008
Где: SPb

Репутация: 2
Всего: 2



sTa1kEr, спасибо большое за пост. Собираю информацию об эксепшенах по крупицам, твоя крупица была одной из самых больших )))

Теперь по пунктам.

Цитата

1. Бессмысленно тут передавать  __FILE__ и __LINE__, т.к. эти данные и так содержатся в экзепшене. 


Это как раз и сделано для того, что ты написал в конце. Все дело в том что у меня всего 1 класс - наследник от Exception. Как я понимаю для нормальной работы с ними этих классов должно быть много - на каждый вид исключения свой. Даже у тебя в примере их 2 + еще ErrorException, итого уже 3.

Так вот __FILE__ и __LINE__ передаются с учетом того, что может быть установлен обработчик ошибок ПХП set_error_handler, у него то эти параметры обязательными являются и они должны пойти в конструктор соответствующего эксепшена.

Правда немного пугает количество возможных исключений. Пустое поле, попытка взлома, ошибка ПХП скрипта, ошибка БД, не найдена запись... так количество классов исключений перевалит за количество классов самого проекта : ))))

2. Ну я вот и пытаюсь разобраться с его гибкостью... пока все что придумалось.

Теперь по твоему примеру...

Код

    catch (Exception $ex)
    {
        // А остальные исключения бросаем дальше
        throw $ex; // Ничего не возвращаем!
    }


Вот это вообще повергло в тихий шок... Я уже уяснил что catch ловит все исключения указанного класса и всех его потомков, т.е.   catch (Exception $ex) поймает вообще все исключения, что были. Т.е. тут ты их ловишь и снова выбрасываешь получается... жесть, как она есть. Честно говоря сложно следить за такой логикой.

И еще, не очень понял фразу 
Цитата


// Оба аргумента обязательны, по этому если хоть один из них
    // пустой, то мы бросаем исключение наверх

PM MAIL ICQ   Вверх
sTa1kEr
Дата 29.8.2008, 14:42 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


9/10 программиста
***


Профиль
Группа: Завсегдатай
Сообщений: 1553
Регистрация: 21.2.2007

Репутация: 56
Всего: 146



Цитата(Dima 2015 @  29.8.2008,  14:02 Найти цитируемый пост)
Так вот __FILE__ и __LINE__ передаются с учетом того, что может быть установлен обработчик ошибок ПХП set_error_handler, у него то эти параметры обязательными являются и они должны пойти в конструктор соответствующего эксепшена.

Если собрался пользоватся исключениями, то забудь про set_error_handler() раз и на всегда. Используй его только для http://php.net/errorexception .
Код

try 
{
    throw new Exception('Some message', 0);
}
catch (Exception $ex)
{
    echo 'Line: '.$ex->getLine()."\n";
    echo 'File: '.$ex->getFile()."\n";
    echo 'Code: '.$ex->getCode()."\n";
    echo 'Trace: '.$ex->getTraceAsString()."\n";
}

Все. И не надо никаких __FILE__, __LINE__.

Цитата(Dima 2015 @  29.8.2008,  14:02 Найти цитируемый пост)
Правда немного пугает количество возможных исключений. Пустое поле, попытка взлома, ошибка ПХП скрипта, ошибка БД, не найдена запись... так количество классов исключений перевалит за количество классов самого проекта : ))))

Не надо создавать их на каждый чих. Обычно в среднем требуется по одному исключению на компонент.

Если необходимо перехватить и обработать конкретное исключение, то тогда создай его. Иначе достаточно общего исключения. 
Например, у тебя логика приложения не зависит от того какая именно ошибка в БД произошла, то для всех исключений связанных с БД достаточно одного класса DbException например.
А если хочешь отдельно обрабатывать ошибки вставки и отдельно ошибки удаления записей из БД, то наследуешь от DbException -а DbInsertException и DbDeleteException. Тогда перехватывая DbException, попрежнему будешь ловить все ошибки БД, но приэтом если понадобится, то сможешь так же ловить и конкретно ошибки вставки или удаления.

А часто может быть достаточно внутренних исключений или же базового Exception. http://www.php.net/~helly/php/ext/spl/classes.html - тут можешь посмотреть какие есть в PHP встроенные исключения.

Цитата(Dima 2015 @  29.8.2008,  14:02 Найти цитируемый пост)
Вот это вообще повергло в тихий шок...

На самом деле в данном примере последний catch не обязателен (хоть и не мешает), я просто хотел показать как с ними можно работать, но немного не тот пример привел.

А происходит примерно следующее:
Открывая блок try, мы говорим, что ожидаем исключительную ситуацию в этом блоке. 

После блока идет выборка какое исключение и как мы будем обрабатывать (примерно тоже самое что и со switch case -ами)
В первом catch-е мы ловим конкретные исключения FileNotFoundException и обрабатываем его полностью, в результате чего функция 
корректно отрабатывается и возращает false.

А во втором блоке уже ловим все остальные, что-то делаем с ними и явно бросаем дальше. Т.е. в моем примере это равносильно тому, если бы вообще не писать второй catch, тогда бы все остальные исключения просто проходили дальше в выше стоящий блок try или если больше блоков try нету то выдавался бы fatal error.

Еще пример, если нам надо обработать все исключения, кроме какого-то конкретного
Код

try 
{
    // Ловим исключения
}
catch (SomeException $ex)
{
   throw $ex; // Именно это исключение мы бросаем дальше, что бы оно обработалось в каком-либо другом блоке
}
catch (Exception $ex)
{
   someDo(); // А все остальные обрабатываем и работаем дальше
}

Т.о. из этого блока дальше пробрасыватся будут только SomeException.
PM MAIL   Вверх
Dima 2015
Дата 29.8.2008, 15:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 287
Регистрация: 16.3.2008
Где: SPb

Репутация: 2
Всего: 2



sTa1kEr, жжесть. Как она есть. Я так и подозревал что не так это все просто. Пока видимо мне придется оставить так как есть, буду по-тихоньку разбираться и учиться использовать всю гибкость исключений. А тебе спасибо еще раз, буду переодически возвращаться в эту тему подглядывать. Вот именно таких примеров я и искал, чтоб показали как работает, а не просто синтаксис. И почему-то сложно найти, ни в книгах, ни в инете...
PM MAIL ICQ   Вверх
yurik_l
Дата 29.8.2008, 21:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


тарантиноман



Профиль
Группа: Участник
Сообщений: 49
Регистрация: 17.9.2006

Репутация: 1
Всего: 3



PM MAIL   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "PHP"
Aliance
IZ@TOP
skyboy
SamDark
MoLeX

Новичкам:

  • PHP редакторы собираются и обсуждаются здесь
  • Электронные книги по PHP, документацию можно найти здесь
  • Интерпретатор PHP, полную документацию можно скачать на PHP.NET

Важно:

  • Не брезгуйте пользоваться тегами [code=php]КОД[/code] для повышения читабельности текста/кода.
  • Перед созданием новой темы воспользуйтесь поиском и загляните в FAQ
  • Действия модераторов можно обсудить здесь

Внимание:

  • Темы "ищу скрипт", "подскажите скрипт" и т.п. будут переноситься в форум "Web-технологии"
  • Темы с именами: "Срочно", "помогите", "не знаю как делать" будут УДАЛЯТЬСЯ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, IZ@TOP, skyboy, SamDark, MoLeX, awers.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | PHP: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0993 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.