Last active
April 14, 2022 13:17
-
-
Save codedokode/65d43ca5ac95c762bc1a to your computer and use it in GitHub Desktop.
Revisions
-
codedokode revised this gist
May 20, 2016 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -186,7 +186,7 @@ catch (LoadUsersException $e) { } ``` Мы можем рассматривать исключения как часть интерфейса функции (часть правил работы с этой функцией). Есть аргументы, которые мы даем на вход, есть результат, который она возвращает, и есть исключения которые она может выкинуть при ошибке. И использование исключений позволяет пользователю функции (тому кто ее вызвал) решить что делать в случае ошибки. ## Поддержка исключений везде @@ -268,7 +268,7 @@ try { ## Страница ошибки в веб-приложениях По умолчанию при непойманном исключении PHP завершает скрипт. Если опция `display_errors` в php.ini равна 1, то PHP выводит подробности об исключении, а если она равна 0, то в браузере отображается просто белая страница. Также, PHP записывает информацию об исключении в лог ошибок сервера. Очевидно что оба варианта не годятся для использования на продакшен ("боевом") сервере: обычные пользователи не должны видеть непонятные надписи на английском о твоем приложении, и тем более не должны смотреть на пустую страницу и гадать в чем дело. А хакеры не должны видеть подробности об устройстве твоего приложения. Более того, PHP не выдает при ошибке HTTP код 500, который говорит роботам (вроде Гугла или Яндекса) что на странице ошибка и индексировать ее на надо. Разработчики PHP конечно выбрали неудачный способ поведения по умолчанию. -
codedokode revised this gist
Mar 17, 2016 . 1 changed file with 4 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,7 @@ Этот урок переехал в мой гитхаб: https://github.com/codedokode/pasta/blob/master/php/exceptions.md - ниже представлена старая версия, потому советую перейти и прочитать новую. -------------- # Как использовать исключения в PHP Если ты изучаешь ООП, ты наверняка натыкался на исключения. В [мануале PHP](http://php.net/manual/ru/language.exceptions.php) описаны команды `try`/`catch`/`throw` и `finally` (доступна начиная с PHP 5.5), но не объясняется толком как их использовать. Чтобы разобраться с этим, надо узнать почему они вообще были придуманы. -
codedokode revised this gist
Mar 17, 2016 . 1 changed file with 36 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -212,18 +212,26 @@ set_error_handler(function ($errno, $errstr, $errfile, $errline ) { }); ``` Этот код превращает любые ошибки и предупреждения PHP в исключения. Некоторые современные фреймворки (Slim) включают в себя такой код. ## Исключения и PDO Чтобы расширение для работы с базами данных PDO использовало исключения (например при попытке выполнить неправильно написанный SQL запрос), надо установить соответствующий параметр (рекомендуется). Без него ты будешь должен после вызова каждой функции проверять результат с помощью `if`: ```php $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); ``` Мануал: http://php.net/manual/ru/pdo.error-handling.php ## Исключения и mysqli Библиотека mysqli при ошибках не выбрасывает исключений и не генерирует предупреждений, а просто возвращает `false` (то есть молчит как партизан). Таким образом, после каждого действия ты должен проверять результат с помощью `if`, что видно в примерах кода в мануале: http://php.net/manual/ru/mysqli.query.php На мой взгляд это крайне неудачное решение, так как программист может забыть сделать проверку и ошибка останется полностью незамеченной, в итоге придется потратить больше времени на ее поиск. Чтобы не писать ифы во всей программе, ты можешь сделать класс-обертку над mysqli. Или просто использовать PDO. ## Так делать не надо Не стоит ловить все исключения без разбора: @@ -254,6 +262,32 @@ try { } ``` ## Страница ошибки в веб-приложениях По умолчанию при непойманном исключении PHP звершает скрипт. Если опция `display_errors` в php.ini равна 1, то PHP выводит поодробности об исключении, а если она равна 0, то в браузере отображается просто белая страница. Также, PHP записывает информацию об исключении в лог ошибок сервера. Очевидно что оба варианта не годятся для использования на продакшен ("боевом") сервере: обычные пользователи не должны видеть непонятные надписи на английском о твоем приложении, и тем более не должны смотреть на пустую страницу и гадать в чем дело. А хакеры не должны видеть подробности об устройстве твоего приложения. Более того, PHP не выдает при ошибке HTTP код 500, который говорит роботам (вроде Гугла или Яндекса) что на странице ошибка и индексировать ее на надо. Разработчики PHP конечно выбрали неудачный способ поведения по умолчанию. Потому на боевом сервере надо ставить `display_errors` в 0, а в приложении делать свою страницу ошибки. Что будет, если просто не ловить исключение: - информация пишется в лог (ок) - на компьютере разработчика выводятся подробности (ок) - пользователь видит белую страницу или подробности ошибки (плохо) - отдается HTTP код 200 (плохо) Как надо обрабатывать исключения: - записать информацию в лог - показать пользователю заглушку ("сайт временно недоступен, вот контакты администратора") - на заглушке выставить [HTTP код ответа 503](https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%BA%D0%BE%D0%B4%D0%BE%D0%B2_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F_HTTP#503) для роботов - на компьютере разработчика (при display_errors = 1) можно показать подробности и стектрейс Для реализации страницы ошибки можно либо сделать try/catch на уровне FrontController, либо установить свой обработчик исключений через `set_exception_handler`. Не забудь записать информацию в лог с помощью `error_log($e->__toString())` - иначе ты не узнаешь об ошибках которые происходят у пользователей твоего приложения. Если ты используешь фреймворк, возможно, в нем все это уже реализовано. Современные фрейморки, такие как Slim, Yii 2, Symfony 2, выводят заглушку при непойманном исключении. Старые - не выводят. ## Ссылки Механизм исключений существует и в других языках в таком же виде: Java, C++, Ruby, Javascript и многих других. Статья в вики: -
codedokode revised this gist
Mar 17, 2016 . 1 changed file with 4 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -186,7 +186,7 @@ catch (LoadUsersException $e) { ## Поддержка исключений везде Эта система работала бы идеально, если бы стандартные функции PHP выкидывали исключения при ошибках. Но увы, по историческим причинам эти функции просто генерируют сообщение об ошибке и возвращают `false`. Ну например, функция чтения файла в память `file_get_contents` поступает именно так, и ошибка чтения файла или его отсуствие не завершает программу. Потому ты обязан ставить `if` после каждого вызова: ```php // Если файла нет, функция просто вернет false, и программа продолжит выполняться @@ -196,9 +196,9 @@ if ($content === false) { } ``` Многие разработчики забывают или ленятся проверять результат выполнения функции, и пишут некорректные программы, которые даже в случае ошибки пытаются продолжать выполнение. Это опасно, так как программа, в которой возникла ошибка, скорее всего будет дальше работать неправильно и выведет неточную информацию. Например, программа берет из базы сумму денег на счету пользователя, что-то меняет в ней и сохраняет ее обратно в базу. Если мы опечатаемся в имени переменной, может получиться так, что в базу мы запишем ноль. Хотя PHP и выведет предупреждение о несуществующей переменной, он не прерывает выполнения программы в этом случае. Для этой проблемы есть решение. Можно установить общий обработчик ошибок (он вызывается при любой ошибке PHP, например обращении к несуществующей переменной или невозможности чтения файла), и в нем выкидывать исключение. Таким образом, любая ошибка или предупреждение приведут к выбросу исключения. Все это делается в несколько строчек с помощью встроенного в PHP класса `ErrorException` ([мануал](http://php.net/manual/ru/class.errorexception.php)): ```php set_error_handler(function ($errno, $errstr, $errfile, $errline ) { @@ -212,7 +212,7 @@ set_error_handler(function ($errno, $errstr, $errfile, $errline ) { }); ``` Этот код превращает любые ошибки и предупреждения PHP в исключения. ## Исключения и PDO -
codedokode revised this gist
Mar 17, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -125,7 +125,7 @@ a(); echo "Survived\n"; ``` В данном примере кода исключение мгновенно выйдет из всех вложенных вызовов функций и завершит программу. Это хорошо, так как если произошла какая-то неисправимая ошибка, выполнять программу дальше нельзя. Это называется [принципом fail fast (статья на Хабре)](http://habrahabr.ru/post/218325/). ## Ловим исключения -
codedokode revised this gist
Mar 17, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -105,7 +105,7 @@ $users = loadUsersFromFile($file); Вот простой пример: ```php function a() { b(); -
codedokode revised this gist
Mar 17, 2016 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -117,7 +117,8 @@ function b() } // Функция a() вызывает b() которая выбрасывает исключение. Исключение выходит // из функции b() наверх в функцию a(), выходит из нее и, оказавшись на верхнем // уровне, завершает программу a(); // Эта строчка никогда не будет выполнена -
codedokode revised this gist
Mar 17, 2016 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -87,9 +87,9 @@ if (!file_exists($file)) { Исключение — это объект встроенного в PHP класса `Exception` ([мануал по Exception](http://php.net/manual/ru/class.exception.php)) или его наследника. Объект исключения содержит подробности о причинах ошибки. Также, в PHP есть еще другие классы исключений, которые ты можешь использовать: http://php.net/manual/ru/spl.exceptions.php В PHP7 определение расширено: исключения не обязаны наследоваться от `Exception`, это может быть любой класс, реализующий интерфейс [Throwable](http://php.net/manual/en/class.throwable.php), а так как сам `Exception` его реализует, то его наследники по-прежнему будут работать. Также, ты можешь написать свой класс исключений, унаследовав его от `Exception`. В этом случае ты даже можешь добавить туда свои методы и свойства. Свой класс надо писать, если ты хочешь позже ловить выброшенное функцией исключение или добавить какие-то дополнительные подробности об ошибке (например, имя файла из которого не удалось загрузить пользователей). Исключение выбрасывается в случае возникновения нештатной ситуации, когда функция обнаруживает что не способна выполнить свою задачу. -
codedokode revised this gist
Mar 17, 2016 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -87,6 +87,8 @@ if (!file_exists($file)) { Исключение — это объект встроенного в PHP класса `Exception` ([мануал по Exception](http://php.net/manual/ru/class.exception.php)) или его наследника. Объект исключения содержит подробности о причинах ошибки. Также, в PHP есть еще другие классы исключений, которые ты можешь использовать: http://php.net/manual/ru/spl.exceptions.php В PHP7 определение расшширено: исключения не обязаны наследоваться от `Exception`, это может быть любой класс, реализующий интерфейс [Throwable](http://php.net/manual/en/class.throwable.php), а так как сам `Exception` его реализует, то его наследники по-прежнему будут работать. Также, ты можешь написать свой класс исключений, унаследовав его от `Exception`. В этом случае ты даже можешь добавить туда свои методы и свойства, например, для хранения информации об ошибке. Свой класс надо писать, если ты хочешь позже ловить выброшенное функцией исключение или добавить какие-то дополнительные подробности об ошибке (например, имя файла из которого не удалось загрузить пользователей). Исключение выбрасывается в случае возникновения нештатной ситуации, когда функция обнаруживает что не способна выполнить свою задачу. -
codedokode revised this gist
Mar 17, 2016 . 1 changed file with 55 additions and 20 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -41,18 +41,18 @@ function loadUsersFromFile($file) { function loadUsersFromFile($file) { // Файла не существует — ошибка if (!file_exists($file)) { return [ 'success' => false, 'error' => "файл $file не существует" ]; } .... загружаем информацию о пользователях .... return [ 'success' => true, 'result' => $users ]; } ``` @@ -61,21 +61,23 @@ function loadUsersFromFile($file) { ```php .... // Загружаем список пользователей в массив $loadResult = loadUsersFromFile($file); // можно еще писать if (!$loadResult['success']) if ($loadResult['success'] === false) { // Выводим текст ошибки die("Не удалось вывести список пользователей из-за ошибки: {$loadResult['error']}\n"); } $users = $loadResult['result']; ... ``` Теперь код гораздо лучше. Тот, кто вызывает функцию, может обрабатывать ошибки так, как ему хочется. Но за это нам пришлось заплатить усложнением кода: теперь мы должны после каждого вызова писать `if` и проверять, успешно ли выполнилась функция. Когда функций много, и каждая может вернуть ошибку, код начинает наполовину состоять из таких проверок. ## Выбрасываем исключение В качестве решения проблемы были придуманы исключения. Если в функции произошла какая-то ошибка и она не может выполнить свою работу, то она выбрасывает исключение командой `throw`: ```php if (!file_exists($file)) { @@ -85,9 +87,9 @@ if (!file_exists($file)) { Исключение — это объект встроенного в PHP класса `Exception` ([мануал по Exception](http://php.net/manual/ru/class.exception.php)) или его наследника. Объект исключения содержит подробности о причинах ошибки. Также, в PHP есть еще другие классы исключений, которые ты можешь использовать: http://php.net/manual/ru/spl.exceptions.php Также, ты можешь написать свой класс исключений, унаследовав его от `Exception`. В этом случае ты даже можешь добавить туда свои методы и свойства, например, для хранения информации об ошибке. Свой класс надо писать, если ты хочешь позже ловить выброшенное функцией исключение или добавить какие-то дополнительные подробности об ошибке (например, имя файла из которого не удалось загрузить пользователей). Исключение выбрасывается в случае возникновения нештатной ситуации, когда функция обнаруживает что не способна выполнить свою задачу. Исключение по умолчанию (если оно не перехватывается) выходит из всех вызовов функций до самого верха и завершает программу, выводя сообщение об ошибке. Таким образом, если ты не перехватываешь исключения, то все равно увидишь причину ошибки (а если у тебя установлено расширение `xdebug` то еще и стектрейс — цепочку вызовов функций, внутри которых оно произошло). И тебе больше не надо писать if: @@ -97,8 +99,37 @@ if (!file_exists($file)) { $users = loadUsersFromFile($file); ``` Выброс исключения чем-то похож на возврат значения через `return`, однако в отличие от `return`, выброс исключения выходит не только из функции где оно выброшено, но и всех вышестоящих функций, которые ее вызвали, если они не ловят это исключение. Поскольку исключение сигнализирует о внештатной ситуации, которую не исправить, исключения обычно не ловят и позволяют им завершить выполнение программы. Вот простой пример: ``` function a() { b(); } function b() { throw new Exception("Some error happened"); } // Функция a() вызывает b() которая выбрасывает исключение. Исключение выходит // из функции a(), из функции b() и оказавшись на верхнем уровне, завершает программу a(); // Эта строчка никогда не будет выполнена echo "Survived\n"; ``` В данном примере кода исключение мгновенно выйдет из всех вложенных вызовов функций и завершит программу. Это хорошо, так как если произошла какая-то неисправимая ошибка, выполнять программу дальше нельзя. Это назвается [принципом fail fast (статья на Хабре)](http://habrahabr.ru/post/218325/). ## Ловим исключения Исключения можно "ловить". Это полезно в нескольких случаях. Иногда мы можем как-то отреагировать на неудачу: например, при ошибке скачивании файла по сети можно сделать паузу и повторить попытку. Для этого нам надо перехватить выброшенное функцией исключение. Также, исключения обычно ловят на верхнем уровне программы в веб-приложениях для того, чтобы сделать свою страницу, информирующую об ошибке (так как PHP при непойманном исключении завершает программу, и пользователь видит пустую белую страницу в браузере, что плохо). В приложениях, запускаемых в командной строке, а не в браузере, обычно это не требуется, так как они не ориентированы на "обычных" пользователей. Перехватывать исключения в теории можно двумя способами: неструктурно и структурно. Неструктурно — это когда мы задаем обработчик исключений в начале программы: ```php @@ -107,17 +138,19 @@ set_exception_handler(function (Exception $exception) { }); ``` Этот способ ловит неперехваченные исключения любых видов во всей программе, его можно использовать для того, чтобы сделать свою страницу ошибки (об этом подробнее написано ниже). После срабатывания обработчика программа будет завершена, предотвратить это невозможно. Структурная обработка исключений - это когда мы ловим только исключения определенных типов в опредленном месте кода. Она реализуется с помощью `try`/`catch`: ```php try { // В try пишется код, в котором мы хотим перехватывать исключения $users = loadUsersFromFile(...); .... } catch (LoadUsersException $e) { // В catch мы указываем, исключения каких классов хотим ловить. // В данном случае мы ловим исключения класса LoadUsersException и его // наследников, то есть только те, которые выбрасывает наша функция // Блоков catch может быть несколько, для разных классов die("Ошибочка: {$e->getMessage()}\n"); @@ -126,26 +159,28 @@ try { В PHP5.5 и выше добавлен блок `finally`. Команды из этого блока будут выполнены после любого из блоков (`try` или `catch`) — в случае если исключения не произойдет и в случае если оно произойдет. Перехватывать абсолютно любые типы исключений — плохая идея, так как мы обычно хотим обрабатывать только определенные, "наши", типы ошибок и не знаем что делать с другими. Чтобы перехватывать только нужные нам исключения, надо сделать свой класс на основе встроенного в PHP `Exception`: ```php class LoadUsersException extends Exception { } ``` Выкидывать его в `throw` ```php throw new LoadUsersException("Файл $file не существует"); ``` И ловить в `catch` только наши исключения: ```php catch (LoadUsersException $e) { .... обрабатываем ошибку ... } ``` Мы можем рассматривать исключения как часть интерфейса функции (часть правил работы с этой функцией). Есть аргументы, которые мы даем на вход, есть результат, который она возвращает, и есть исключения которые она может выкинуть при ошибке. И использование исключений позводяет пользователю функции (тому кто ее вызвал) решить что делать в случае ошибки. ## Поддержка исключений везде Эта система работала бы идеально, если бы стандартные функции PHP выкидывали исключения при ошибках. Но увы, по историческим причинам эти функции просто генерируют сообщение об ошибке и возвращают `false`. Ну например, функция чтения файла в память `file_get_contents` поступает именно так, и ошибка чтения файла не завершает программу. Потому ты обязан ставить `if` после каждого вызова: -
codedokode revised this gist
Nov 23, 2014 . 1 changed file with 7 additions and 7 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -75,7 +75,7 @@ if ($users['success'] === false) { ## Выбрасываем исключение В качестве решения проблемы были придуманы исключения. В случае ошибки код выбрасывает исключение командой `throw`: ```php if (!file_exists($file)) { @@ -107,15 +107,15 @@ set_exception_handler(function (Exception $exception) { }); ``` Этот способ ловит неперехваченные исключения во всей программе, нам он не нужен. Перейдем к более продвинутой структурной обработке исключений, когда мы ловим только исключения определенных типов в опредленном месте кода. Она реализуется с помощью `try`/`catch`: ```php try { // В try пишется код, в котором мы хотим перехватывать исключения $users = loadUsersFromFile(...); .... } catch (Exception $e) { // В catch мы указываем, исключения каких классов хотим ловить. // В данном случае мы ловим исключения класса Exception и его // наследников, то есть все исключения (обычно это плохая идея) // Блоков catch может быть несколько, для разных классов @@ -135,10 +135,10 @@ class FileErrorException extends Exception { } Выкидывать его в `throw` ```php throw new FileErrorException("Файл $file не существует"); ``` И ловить в `catch` только наши исключения: ```php catch (FileErrorException $e) { @@ -148,7 +148,7 @@ catch (FileErrorException $e) { ## Поддержка исключений везде Эта система работала бы идеально, если бы стандартные функции PHP выкидывали исключения при ошибках. Но увы, по историческим причинам эти функции просто генерируют сообщение об ошибке и возвращают `false`. Ну например, функция чтения файла в память `file_get_contents` поступает именно так, и ошибка чтения файла не завершает программу. Потому ты обязан ставить `if` после каждого вызова: ```php // Если файла нет, функция просто вернет false, и программа продолжит выполняться @@ -158,7 +158,7 @@ if ($content === false) { } ``` Многие разработчики забывают или ленятся проверять результат выполнения функции, и пишут некорректные программы, которые даже в случае ошибки пытаются продолжать выполнение. Это опасно, так как программа, в которой возникла ошибка, скорее всего будет дальше работать неправильно и выведет неточную информацию. Для этой проблемы есть решение. Можно установить общий обработчик ошибок, и в нем выкидывать исключение. Все это делается в несколько строчек с помощью встроенного в PHP класса `ErrorException` ([мануал](http://php.net/manual/ru/class.errorexception.php)): -
codedokode revised this gist
Nov 23, 2014 . 1 changed file with 81 additions and 58 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -23,7 +23,6 @@ foreach ($users as $user) { Самый простой (но плохой) вариант — поместить код обработки и вывода ошибки прямо в `loadUsersFromFile()`: ```php function loadUsersFromFile($file) { // Файла не существует — ошибка if (!file_exists($file)) { @@ -60,7 +59,6 @@ function loadUsersFromFile($file) { Конечно, мы должны поменять и код, который вызывает функцию: ```php .... // Загружаем список пользователей в массив $users = loadUsersFromFile($file); @@ -70,7 +68,6 @@ if ($users['success'] === false) { // Выводим текст ошибки die("Не удалось вывести список пользователей из-за ошибки: {$users['error']}\n"); } ... ``` @@ -80,9 +77,11 @@ if ($users['success'] === false) { В качестве решения проблемы были придуманы искючения. В случае ошибки код выбрасывает исключение командой `throw`: ```php if (!file_exists($file)) { throw new Exception("Ошибка: файл $file не существует"); } ``` Исключение — это объект встроенного в PHP класса `Exception` ([мануал по Exception](http://php.net/manual/ru/class.exception.php)) или его наследника. Объект исключения содержит подробности о причинах ошибки. Также, в PHP есть еще другие классы исключений, которые ты можешь использовать: http://php.net/manual/ru/spl.exceptions.php @@ -92,106 +91,130 @@ if ($users['success'] === false) { Исключение по умолчанию (если оно не перехватывается) выходит из всех вызовов функций до самого верха и завершает программу, выводя сообщение об ошибке. Таким образом, если ты не перехватываешь исключения, то все равно увидишь причину ошибки (а если у тебя установлено расширение `xdebug` то еще и стектрейс — цепочку вызовов функций, внутри которых оно произошло). И тебе больше не надо писать if: ```php // Если тут произойдет исключение, оно само завершит программу // потому у нас нет необходимости проверять результат $users = loadUsersFromFile($file); ``` ## Ловим исключения Перехватывать исключения в теории можно двумя способами: неструктурно и структурно. Неструктурно — это когда мы задаем обработчик исключений в начале программы: ```php set_exception_handler(function (Exception $exception) { // Функция будет вызвана при возникновении исключения }); ``` Этот способ ловит неперехваченные исключения во всей программе, нам он не нужен, потому перейдем к более продвинутой структурной обработке исключений. Она реализуется с помощью `try`/`catch`: ```php try { // В try пишется код, в котором мы хотим перехватывать исключения $users = loadUsersFromFile(...); .... } catch (Exception $e) { // В catch мы указываем, искючения каких классов хотим ловить. // В данном случае мы ловим исключения класса Exception и его // наследников, то есть все исключения (обычно это плохая идея) // Блоков catch может быть несколько, для разных классов die("Ошибочка: {$e->getMessage()}\n"); } ``` В PHP5.5 и выше добавлен блок `finally`. Команды из этого блока будут выполнены после любого из блоков (`try` или `catch`) — в случае если исключения не произойдет и в случае если оно произойдет. Перехватывать абсолютно любые типы исключений — плохая идея. Чтобы перехватывать только нужные нам исключения, надо сделать свой класс: ```php class FileErrorException extends Exception { } ``` Выкидывать его в `throw` ```php throw new FileErrorException("Файл не существует"); ``` И ловить в `catch`: ```php catch (FileErrorException $e) { .... обрабатываем ошибку ... } ``` ## Поддержка исключений везде Эта система работала бы идеально, если бы стандартные функции PHP выкидывали исключения при ошибках. Но увы, по историческим причинам эти функции просто генерируют сообщение об ошибке и возвращают `false`. Ну например, функция чтения файла в память `file_get_contents` поступает именно так, и ошибка чтения файла не завершает программу. Потому ты обязан ставить if после каждого вызова: ```php // Если файла нет, функция просто вернет false, и программа продолжит выполняться $content = file_get_contents('file.txt'); if ($content === false) { ... не удалось прочесть файл ... } ``` Многие разработчики забывают проверять ошибки, и пишут некорректные программы,которые даже в случае ошибки пытаются продолжать выполнение. Это опасно, так как программа, в которой возникла ошибка, скорее всего будет дальше работать неправильно и выведет неточную информацию. Для этой проблемы есть решение. Можно установить общий обработчик ошибок, и в нем выкидывать исключение. Все это делается в несколько строчек с помощью встроенного в PHP класса `ErrorException` ([мануал](http://php.net/manual/ru/class.errorexception.php)): ```php set_error_handler(function ($errno, $errstr, $errfile, $errline ) { // Не выбрасываем исключение если ошибка подавлена с // помощью оператора @ if (!error_reporting()) { return; } throw new ErrorException($errstr, $errno, 0, $errfile, $errline); }); ``` Этот код превращает любые ошибки и предупреждения PHP в исключения. ## Исключения и PDO Чтобы расширение для работы с базами данных PDO использовало исключения, надо установить соответствующий параметр (рекомендуется): ```php $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); ``` Мануал: http://php.net/manual/ru/pdo.error-handling.php ## Так делать не надо Не стоит ловить все исключения без разбора: ```php catch (Exception $e) ``` Лучше создать свой класс исключений и ловить только его. Не надо скрывать информацию об ошибках. В 99% этот код, игнорирующий исключение, неправильный: ```php catch (Exception $e) { // ничего не делаем } ``` Не надо располагать `try`/`catch` и `throw` на одном уровне — в этом случае проще написать `if`: ```php try { ... throw new Exception(...); ... } catch (Exception $e) { ... } ``` ## Ссылки -
codedokode revised this gist
Nov 23, 2014 . 1 changed file with 29 additions and 25 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -7,7 +7,6 @@ Для примера представим, что мы пишем приложение для вывода списка пользователей из файла на экран. Допустим, код выглядит как-то так: ```php $file = './users.csv'; // Загружаем список пользователей из файла в массив @@ -39,36 +38,41 @@ function loadUsersFromFile($file) { Что же, давай улучшим код и переделаем функцию, чтобы она возвращала массив из 2 элементов: если все ок, то элемент `success` содержит `true`, а элемент `result` содержит массив пользователей. Если же произошла ошибка, то в `success` будет находиться `false`, а в элементе `error` текст ошибки. ```php function loadUsersFromFile($file) { // Файла не существует — ошибка if (!file_exists($file)) { return array( 'success' => false, 'error' => "файл $file не существует" ); } .... загружаем информацию о пользователях .... return array( 'success' => true, 'result' => $users ) } ``` Конечно, мы должны поменять и код, который вызывает функцию: ```php <?php .... // Загружаем список пользователей в массив $users = loadUsersFromFile($file); // можно еще писать if (!$users['success']) if ($users['success'] === false) { // Выводим текст ошибки die("Не удалось вывести список пользователей из-за ошибки: {$users['error']}\n"); } ... ``` Теперь код гораздо лучше. Тот, кто вызывает функцию, может обрабатывать ошибки так, как ему хочется. Но за это нам пришлось заплатить усложнением кода: теперь мы должны после каждого вызова писать `if` и проверять, успешно ли выполнилась функция. Когда функций много, и каждая может вернуть ошибку, код начинает наполовину состоять из таких проверок. -
codedokode revised this gist
Nov 23, 2014 . 1 changed file with 22 additions and 16 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -6,29 +6,35 @@ Для примера представим, что мы пишем приложение для вывода списка пользователей из файла на экран. Допустим, код выглядит как-то так: ```php <?php $file = './users.csv'; // Загружаем список пользователей из файла в массив $users = loadUsersFromFile($file); // Выводим foreach ($users as $user) { echo "{$user['name']} набрал {$user['score']} очков\n"; } ``` Все ли тут верно? Нет, не все. Мы забыли сделать обработку ошибок. Файла может не существовать, к нему может не быть доступа, данные в нем могут быть в неверном формате. Хорошая программа, разумеется должна обрабатывать такие ситуации и выводить соответствующее сообщение. Самый простой (но плохой) вариант — поместить код обработки и вывода ошибки прямо в `loadUsersFromFile()`: ```php <?php function loadUsersFromFile($file) { // Файла не существует — ошибка if (!file_exists($file)) { die("Ошибка: файл $file не существует\n"); } .... } ``` Этот вариант плохой, так как теперь тот, кто вызывает функцию `loadUsersFromFile` не может никак повлиять на обработку ошибок. Может он хочет при ошибке вывести другое сообщение или хочет попробовать поискать файл в другом месте. Но наша функция просто завершает программу, не спрашивая его мнения. Это плохо. Что же, давай улучшим код и переделаем функцию, чтобы она возвращала массив из 2 элементов: если все ок, то элемент `success` содержит `true`, а элемент `result` содержит массив пользователей. Если же произошла ошибка, то в `success` будет находиться `false`, а в элементе `error` текст ошибки. -
codedokode revised this gist
Nov 23, 2014 . 1 changed file with 38 additions and 22 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -8,7 +8,7 @@ $file = './users.csv'; // Загружаем список пользователей из файла в массив $users = loadUsersFromFile($file); // Выводим @@ -29,34 +29,42 @@ .... } Этот вариант плохой, так как теперь тот, кто вызывает функцию `loadUsersFromFile` не может никак повлиять на обработку ошибок. Может он хочет при ошибке вывести другое сообщение или хочет попробовать поискать файл в другом месте. Но наша функция просто завершает программу, не спрашивая его мнения. Это плохо. Что же, давай улучшим код и переделаем функцию, чтобы она возвращала массив из 2 элементов: если все ок, то элемент `success` содержит `true`, а элемент `result` содержит массив пользователей. Если же произошла ошибка, то в `success` будет находиться `false`, а в элементе `error` текст ошибки. function loadUsersFromFile($file) { // Файла не существует — ошибка if (!file_exists($file)) { return array( 'success' => false, 'error' => "файл $file не существует" ); } .... загружаем информацию о пользователях .... return array( 'success' => true, 'result' => $users ) } Конечно, мы должны поменять и код, который вызывает функцию: .... // Загружаем список пользователей в массив $users = loadUsersFromFile($file); // можно еще писать if (!$users['success']) if ($users['success'] === false) { // Выводим текст ошибки die("Не удалось вывести список пользователей из-за ошибки: {$users['error']}\n"); } ... Теперь код гораздо лучше. Тот, кто вызывает функцию, может обрабатывать ошибки так, как ему хочется. Но за это нам пришлось заплатить усложнением кода: теперь мы должны после каждого вызова писать `if` и проверять, успешно ли выполнилась функция. Когда функций много, и каждая может вернуть ошибку, код начинает наполовину состоять из таких проверок. ## Выбрасываем исключение @@ -66,13 +74,16 @@ throw new Exception("Ошибка: файл $file не существует"); } Исключение — это объект встроенного в PHP класса `Exception` ([мануал по Exception](http://php.net/manual/ru/class.exception.php)) или его наследника. Объект исключения содержит подробности о причинах ошибки. Также, в PHP есть еще другие классы исключений, которые ты можешь использовать: http://php.net/manual/ru/spl.exceptions.php Также, ты можешь написать свой класс исключений, унаследовав его от `Exception`. В этом случае ты даже можешь добавить туда свои методы и свойства, например, для хранения информации об ошибке. Исключение выбрасывается в случае возникновения нештатной ситуации. Исключение по умолчанию (если оно не перехватывается) выходит из всех вызовов функций до самого верха и завершает программу, выводя сообщение об ошибке. Таким образом, если ты не перехватываешь исключения, то все равно увидишь причину ошибки (а если у тебя установлено расширение `xdebug` то еще и стектрейс — цепочку вызовов функций, внутри которых оно произошло). И тебе больше не надо писать if: // Если тут произойдет исключение, оно само завершит программу // потому у нас нет необходимости проверять результат $users = loadUsersFromFile($file); ## Ловим исключения @@ -98,9 +109,9 @@ die("Ошибочка: {$e->getMessage()}\n"); } В PHP5.5 и выше добавлен блок `finally`. Команды из этого блока будут выполнены после любого из блоков (`try` или `catch`) — в случае если исключения не произойдет и в случае если оно произойдет. Перехватывать абсолютно любые типы исключений — плохая идея. Чтобы перехватывать только нужные нам исключения, надо сделать свой класс: class FileErrorException extends Exception { } @@ -111,7 +122,7 @@ И ловить в `catch`: catch (FileErrorException $e) { .... обрабатываем ошибку ... } ## Поддержка исключений везде @@ -121,7 +132,7 @@ // Если файла нет, функция просто вернет false, и программа продолжит выполняться $content = file_get_contents('file.txt'); if ($content === false) { ... не удалось прочесть файл ... } Многие разработчики забывают проверять ошибки, и пишут некорректные программы,которые даже в случае ошибки пытаются продолжать выполнение. Это опасно, так как программа, в которой возникла ошибка, скорее всего будет дальше работать неправильно и выведет неточную информацию. @@ -145,24 +156,24 @@ Чтобы расширение для работы с базами данных PDO использовало исключения, надо установить соответствующий параметр (рекомендуется): $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); Мануал: http://php.net/manual/ru/pdo.error-handling.php ## Так делать не надо Не стоит ловить все исключения без разбора: catch (Exception $e) Лучше создать свой класс исключений и ловить только его. Не надо скрывать информацию об ошибках. В 99% этот код, игнорирующий исключение, неправильный: catch (Exception $e) { // ничего не делаем } Не надо располагать `try`/`catch` и `throw` на одном уровне — в этом случае проще написать `if`: try { ... @@ -177,3 +188,8 @@ Механизм исключений существует и в других языках в таком же виде: Java, C++, Ruby, Javascript и многих других. Статья в вики: https://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0_%D0%B8%D1%81%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B9 (написано не очень понятно) Также, про исключения можно почитать в книгах: - Мэтт Зандстра «PHP: Объекты, шаблоны, методики программирования» - Джордж Шлосснейгл «Профессиональное программирование на PHP» -
codedokode revised this gist
Oct 31, 2014 . 1 changed file with 9 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,9 +2,9 @@ Если ты изучаешь ООП, ты наверняка натыкался на исключения. В [мануале PHP](http://php.net/manual/ru/language.exceptions.php) описаны команды `try`/`catch`/`throw` и `finally` (доступна начиная с PHP 5.5), но не объясняется толком как их использовать. Чтобы разобраться с этим, надо узнать почему они вообще были придуманы. А придуманы они были, чтобы сделать удобную обработку ошибок. Для примера представим, что мы пишем приложение для вывода списка пользователей из файла на экран. Допустим, код выглядит как-то так: $file = './users.csv'; @@ -56,7 +56,7 @@ ... Теперь наша функция стала лучше, она дает возможность вызывающему решать что делать в случае ошибки, но мы получили другую проблему: мы обязаны после каждого ее вызова писать `if`, и добавлять переменную для возврата текста ошибки. В реальных приложениях могут быть тысячи функций, почти любая может вернуть ошибку и код быстро обрастает кучей этих if. ## Выбрасываем исключение @@ -171,3 +171,9 @@ } catch (Exception $e) { ... } ## Ссылки Механизм исключений существует и в других языках в таком же виде: Java, C++, Ruby, Javascript и многих других. Статья в вики: https://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0_%D0%B8%D1%81%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B9 (написано не очень понятно) -
codedokode created this gist
Oct 31, 2014 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,173 @@ # Как использовать исключения в PHP Если ты изучаешь ООП, ты наверняка натыкался на исключения. В [мануале PHP](http://php.net/manual/ru/language.exceptions.php) описаны команды `try`/`catch`/`throw` и `finally` (доступна начиная с PHP 5.5), но не объясняется толком как их использовать. Чтобы разобраться с этим, надо узнать почему они вообще были придуманы. А придуманы они были чтобы сделать удобную обработку ошибок. Для примера, представим, что мы пишем приложение для вывода списка пользователей из файла на экран. Допустим, код выглядит как-то так: $file = './users.csv'; // Загружаем список пользователей в массив $users = loadUsersFromFile($file); // Выводим foreach ($users as $user) { echo "{$user['name']} набрал {$user['score']} очков\n"; } Все ли тут верно? Нет, не все. Мы забыли сделать обработку ошибок. Файла может не существовать, к нему может не быть доступа, данные в нем могут быть в неверном формате. Хорошая программа, разумеется должна обрабатывать такие ситуации и выводить соответствующее сообщение. Самый простой (но плохой) вариант — поместить код обработки и вывода ошибки прямо в `loadUsersFromFile()`: function loadUsersFromFile($file) { // Файла не существует — ошибка if (!file_exists($file)) { die("Ошибка: файл $file не существует\n"); } .... } Этот вариант плохой, так как теперь тот, кто вызывает функцию `loadUsersFromFile` не может никак повлиять на обработку ошибок. Может он хочет при ошибке вывести другое сообщение или хочет не завершать программу, а поискать файл в другом месте. Да и вообще, нехорошо что функция может завершить скрипт раньше времени, как-то страшно ее вызывать. Что же, давай улучшим код и переделаем функцию, заставив при ошибке ее возвращать значение `false` и выставлять текст ошибки в переменной (значок `&` обозначает что переменная передается не как копия, а по ссылке, и функция может изменить исходную переменную, подробнее: [передача по ссылке, мануал](http://php.net/manual/ru/language.references.pass.php)) function loadUsersFromFile($file, &$errorText) { // Файла не существует — ошибка if (!file_exists($file)) { $errorText = "Ошибка: файл $file не существует"; return false; } .... } Теперь мы должны поменять и код, который вызывает функцию: .... // Загружаем список пользователей в массив $error = ''; $users = loadUsersFromFile($file, $error); if ($users === false) { // Выводим текст ошибки die("Не удалось вывести список пользователей из-за ошибки: {$error}\n"); } ... Теперь наша функция стала лучше, она дает возможность вызывающему решать что делать в случае ошибки, но мы получили другую проблему: мы обязаны после каждого ее вызова писать `if`. В реальных приложениях могут быть тысячи функций, почти любая может вернуть ошибку и код быстро обрастает кучей этих if. ## Выбрасываем исключение В качестве решения проблемы были придуманы искючения. В случае ошибки код выбрасывает исключение командой `throw`: if (!file_exists($file)) { throw new Exception("Ошибка: файл $file не существует"); } Исключение — это объект встроенного в PHP класса `Exception` ([мануал по Exception](http://php.net/manual/ru/class.exception.php)) или его наследника (ты можешь делать свои классы исключений, например для разных видов ошибок). Объект исключения содержит подробности о причинах ошибки. Исключение выбрасывается в случае возникновения нештатной ситуации. Исключение по умолчанию (если оно не перехватывается) выходит из всех вызовов функций до самого верха и завершает программу, выводя сообщение об ошибке. Таким образом, если ты не перехватываешь исключения, то все равно увидишь причину ошибки (а если у тебя установлено расширение `xdebug` то еще и стектрейс — цепочку вызовов функций, внутри которых оно произошло). И тебе больше не надо писать if: // Если тут произойдет исключение, оно само завершит программу $users = loadUsersFromFile($file); ## Ловим исключения Перехватывать исключения в теории можно двумя способами: неструктурно и структурно. Неструктурно — это когда мы задаем обработчик исключений в начале программы: set_exception_handler(function (Exception $exception) { // Функция будет вызвана при возникновении исключения }); Этот способ ловит неперехваченные исключения во всей программе, нам он не нужен, потому перейдем к более продвинутой структурной обработке исключений. Она реализуется с помощью `try`/`catch`: try { // В try пишется код, в котором мы хотим перехватывать исключения $users = loadUsersFromFile(...); .... } catch (Exception $e) { // В catch мы указываем, искючения каких классов хотим ловить. // В данном случае мы ловим исключения класса Exception и его // наследников, то есть все исключения (обычно это плохая идея) // Блоков catch может быть несколько, для разных классов die("Ошибочка: {$e->getMessage()}\n"); } В PHP5.5 и выше добавлен блок `finally`. Команды из этого блока будут выполнены после любого из блоков — в случае если исключения не произойдет и в случае если оно произойдет. Перехватывать абсолютно юбые типы исключений — плохая ошибка. Чтобы перехватывать только нужные нам исключения, надо сделать свой класс: class FileErrorException extends Exception { } Выкидывать его в `throw` throw new FileErrorException("Файл не существует"); И ловить в `catch`: catch (FileErrorException $e) { .... } ## Поддержка исключений везде Эта система работала бы идеально, если бы стандартные функции PHP выкидывали исключения при ошибках. Но увы, по историческим причинам эти функции просто генерируют сообщение об ошибке и возвращают `false`. Ну например, функция чтения файла в память `file_get_contents` поступает именно так, и ошибка чтения файла не завершает программу. Потому ты обязан ставить if после каждого вызова: // Если файла нет, функция просто вернет false, и программа продолжит выполняться $content = file_get_contents('file.txt'); if ($content === false) { ... } Многие разработчики забывают проверять ошибки, и пишут некорректные программы,которые даже в случае ошибки пытаются продолжать выполнение. Это опасно, так как программа, в которой возникла ошибка, скорее всего будет дальше работать неправильно и выведет неточную информацию. Для этой проблемы есть решение. Можно установить общий обработчик ошибок, и в нем выкидывать исключение. Все это делается в несколько строчек с помощью встроенного в PHP класса `ErrorException` ([мануал](http://php.net/manual/ru/class.errorexception.php)): set_error_handler(function ($errno, $errstr, $errfile, $errline ) { // Не выбрасываем исключение если ошибка подавлена с // помощью оператора @ if (!error_reporting()) { return; } throw new ErrorException($errstr, $errno, 0, $errfile, $errline); }); Этот код превращает любые ошибки и предупреждения PHP в исключения. ## Исключения и PDO Чтобы расширение для работы с базами данных PDO использовало исключения, надо установить соответствующий параметр (рекомендуется): $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); Мануал: http://php.net/manual/ru/pdo.error-handling.php ## Плохие вещи, которые делать не стоит Не стоит ловить все исключения без разбора: catch (Exception $e) Лучше создать свой класс исключений и ловить только его. Не надо скрывать исключения. В 99% этот код, игнорирующий исключение, неправильный: catch (Exception $e) { // ничего не делаем } Не надо располагать try/catch и throw на одном уровне — в этом случае проще написать if: try { ... throw new Exception(...); ... } catch (Exception $e) { ... }