php вернуть код ошибки

Php вернуть код ошибки

I was confused as to what the @ symbol actually does, and after a few experiments have concluded the following:

* the error handler that is set gets called regardless of what level the error reporting is set on, or whether the statement is preceeded with @

* it is up to the error handler to impart some meaning on the different error levels. You could make your custom error handler echo all errors, even if error reporting is set to NONE.

* so what does the @ operator do? It temporarily sets the error reporting level to 0 for that line. If that line triggers an error, the error handler will still be called, but it will be called with an error level of 0

Hope this helps someone

Be aware of using error control operator in statements before include() like this:

(@include( «file.php» ))
OR die( «Could not find file.php!» );

?>

This cause, that error reporting level is set to zero also for the included file. So if there are some errors in the included file, they will be not displayed.

It’s still possible to detect when the @ operator is being used in the error handler in PHP8. Calling error_reporting() will no longer return 0 as documented, but using the @ operator does still change the return value when you call error_reporting().

My PHP error settings are set to use E_ALL, and when I call error_reporting() from the error handler of a non-suppressed error, it returns E_ALL as expected.

But when an error occurs on an expression where I tried to suppress the error with the @ operator, it returns: E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR (or the number 4437).

I didn’t want to use 4437 in my code in case it changes with different settings or future versions of PHP, so I now use:

And, of course, if your error_reporting settings in PHP is something other than E_ALL, you’ll have to change that to whatever setting you do use.

If you’re wondering what the performance impact of using the @ operator is, consider this example. Here, the second script (using the @ operator) takes 1.75x as long to execute. almost double the time of the first script.

Error suppression should be avoided if possible as it doesn’t just suppress the error that you are trying to stop, but will also suppress errors that you didn’t predict would ever occur. This will make debugging a nightmare.

It is far better to test for the condition that you know will cause an error before preceding to run the code. This way only the error that you know about will be suppressed and not all future errors associated with that piece of code.

There may be a good reason for using outright error suppression in favor of the method I have suggested, however in the many years I’ve spent programming web apps I’ve yet to come across a situation where it was a good solution. The examples given on this manual page are certainly not situations where the error control operator should be used.

Please be aware that the behaviour of this operator changed from php5 to php7.

The following code will raise a Fatal error no matter what, and you wont be able to suppress it

Quick debugging methods :

There is no reason to NOT use something just because «it can be misused». You could as well say «unlink is evil, you can delete files with it so don’t ever use unlink».

?>
There are only 2 possible problems here: a missing variable or a missing index. If you’re sure you’re fine with both cases, you’re good to go. And again: suppressing errors is not a crime. Not knowing when it’s safe to suppress them is definitely worse.

After some time investigating as to why I was still getting errors that were supposed to be suppressed with @ I found the following.

1. If you have set your own default error handler then the error still gets sent to the error handler regardless of the @ sign.

Источник

http_response_code

(PHP 5 >= 5.4.0, PHP 7, PHP 8)

http_response_code — Получает или устанавливает код ответа HTTP

Описание

Получает или задаёт коды ответов HTTP.

Список параметров

Возвращаемые значения

Примеры

Пример #1 Использование http_response_code() в окружении веб-сервера

// Берём текущий код и устанавливаем новый
var_dump ( http_response_code ( 404 ));

// Берём новый код
var_dump ( http_response_code ());
?>

Результат выполнения данного примера:

Пример #2 Использование http_response_code() в CLI

// Берём текущий код по умолчанию
var_dump ( http_response_code ());

// Устанавливаем код
var_dump ( http_response_code ( 201 ));

// Берём новый код
var_dump ( http_response_code ());
?>

Результат выполнения данного примера:

Смотрите также

User Contributed Notes 18 notes

If your version of PHP does not include this function:

For reference the error codes I got from PHP’s source code:

And how the current http header is sent, with the variables it uses:

Note that you can NOT set arbitrary response codes with this function, only those that are known to PHP (or the SAPI PHP is running on).

The following codes currently work as expected (with PHP running as Apache module):
200 – 208, 226
300 – 305, 307, 308
400 – 417, 422 – 424, 426, 428 – 429, 431
500 – 508, 510 – 511

Codes 0, 100, 101, and 102 will be sent as «200 OK».

Everything else will result in «500 Internal Server Error».

If you want to send responses with a freestyle status line, you need to use the `header()` function:

When setting the response code to non-standard ones like 420, Apache outputs 500 Internal Server Error.

This happens when using header(0,0,420) and http_response_code(420).
Use header(‘HTTP/1.1 420 Enhance Your Calm’) instead.

Note that the response code in the string IS interpreted and used in the access log and output via http_response_code().

Status codes as an array:

You can also create a enum by extending the SplEnum class.
/** HTTP status codes */
class HttpStatusCode extends SplEnum <
const __default = self :: OK ;

const SWITCHING_PROTOCOLS = 101 ;
const OK = 200 ;
const CREATED = 201 ;
const ACCEPTED = 202 ;
const NONAUTHORITATIVE_INFORMATION = 203 ;
const NO_CONTENT = 204 ;
const RESET_CONTENT = 205 ;
const PARTIAL_CONTENT = 206 ;
const MULTIPLE_CHOICES = 300 ;
const MOVED_PERMANENTLY = 301 ;
const MOVED_TEMPORARILY = 302 ;
const SEE_OTHER = 303 ;
const NOT_MODIFIED = 304 ;
const USE_PROXY = 305 ;
const BAD_REQUEST = 400 ;
const UNAUTHORIZED = 401 ;
const PAYMENT_REQUIRED = 402 ;
const FORBIDDEN = 403 ;
const NOT_FOUND = 404 ;
const METHOD_NOT_ALLOWED = 405 ;
const NOT_ACCEPTABLE = 406 ;
const PROXY_AUTHENTICATION_REQUIRED = 407 ;
const REQUEST_TIMEOUT = 408 ;
const CONFLICT = 408 ;
const GONE = 410 ;
const LENGTH_REQUIRED = 411 ;
const PRECONDITION_FAILED = 412 ;
const REQUEST_ENTITY_TOO_LARGE = 413 ;
const REQUESTURI_TOO_LARGE = 414 ;
const UNSUPPORTED_MEDIA_TYPE = 415 ;
const REQUESTED_RANGE_NOT_SATISFIABLE = 416 ;
const EXPECTATION_FAILED = 417 ;
const IM_A_TEAPOT = 418 ;
const INTERNAL_SERVER_ERROR = 500 ;
const NOT_IMPLEMENTED = 501 ;
const BAD_GATEWAY = 502 ;
const SERVICE_UNAVAILABLE = 503 ;
const GATEWAY_TIMEOUT = 504 ;
const HTTP_VERSION_NOT_SUPPORTED = 505 ;
>

Do not mix the use of http_response_code() and manually setting the response code header because the actual HTTP status code being returned by the web server may not end up as expected. http_response_code() does not work if the response code has previously been set using the header() function. Example:

( ‘HTTP/1.1 401 Unauthorized’ );
http_response_code ( 403 );
print( http_response_code ());
?>

The raw HTTP response will be (notice the actual status code on the first line does not match the printed http_response_code in the body):

HTTP/1.1 401 Unauthorized
Date: Tue, 24 Nov 2020 13:49:08 GMT
Server: Apache
Connection: Upgrade, Keep-Alive
Keep-Alive: timeout=5, max=100
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

I only tested it on Apache. I am not sure if this behavior is specific to Apache or common to all PHP distributions.

The note above from «Anonymous» is wrong. I’m running this behind the AWS Elastic Loadbalancer and trying the header(‘:’.$error_code. ) method mentioned above is treated as invalid HTTP.

The documentation for the header() function has the right way to implement this if you’re still on ( «HTTP/1.0 404 Not Found» );
?>

At least on my side with php-fpm and nginx this method does not change the text in the response, only the code.

// HTTP/1.1 404 Not Found
http_response_code ( 404 );

?>

The resulting response is HTTP/1.1 404 OK

http_response_code() does not actually send HTTP headers, it only prepares the header list to be sent later on.
So you can call http_reponse_code() to set, get and reset the HTTP response code before it gets sent.

http_response_code(500); // set the code
var_dump(headers_sent()); // check if headers are sent
http_response_code(200); // avoid a default browser page

http_response_code is basically a shorthand way of writing a http status header, with the added bonus that PHP will work out a suitable Reason Phrase to provide by matching your response code to one of the values in an enumeration it maintains within php-src/main/http_status_codes.h. Note that this means your response code must match a response code that PHP knows about. You can’t create your own response codes using this method, however you can using the header method.

1. Using http_response_code will cause PHP to match and apply a Reason Phrase from a list of Reason Phrases that are hard-coded into the PHP source code.

2. Because of point 1 above, if you use http_response_code you must set a code that PHP knows about. You can’t set your own custom code, however you can set a custom code (and Reason Phrase) if you use the header method.

It’s not mentioned explicitly, but the return value when SETTING, is the OLD status code.
e.g.
= http_response_code ();
$b = http_response_code ( 202 );
$c = http_response_code ();

Источник

Правильное использование Exception’ов в PHP

Я рад бы написать что “эта статья предназначена для новичков”, но это не так. Большинство php-разработчиков, имея опыт 3, 5 и даже 7 лет, абсолютно не понимают как правильно использовать эксепшены. Нет, они прекрасно знают о их существовании, о том что их можно создавать, обрабатывать, и т.п., но они не осознают их удобность, логичность, и не воспринимают их как абсолютно нормальный элемент разработки.

Почему мы не умеем пользоваться эксепшенами:

PHP — это чрезмерно любящая мать. И при отсутствии строгого отца (например, Java ) или самодисциплины, разработчик вырастет эгоистом, которому плевать на все правила, стандарты и лучшие практики. И вроде бы E_NOTICE пора включать, а он все на мать надеется. Которая, между прочим, стареет — ей уже E_STRICT c E_DEPRICATED нужны, а сынуля все на шее висит.

И пока наш начинающий разработчик пытается написать свою первую быдло-cms, он ни разу не встретиться с механизмом эксепшенов. Вместо этого, он придумает несколько способов обработки ошибок. Я думаю, все понимают о чем я — эти методы, возвращающие разные типы (например, объект при успешном выполнении, а при неудаче — строка с ошибкой), или запись ошибки в какую-либо переменную/свойство, и всегда — куча проверок чтоб передать ошибку вверх по стеку вызовов.

Затем он научиться отлавливать и обрабатывать их. И на этом его знакомство c эксепшенами закончиться. Ведь надо работать, а не учиться: знаний ему и так хватает (сарказм)!

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

Преимущества эксепшенов

На самом деле использование эксепшенов — крайне лаконичное и удобное решение создания и обработки ошибок. Приведу наиболее значимые преимущества:

Контекстная логика

Прежде всего, хотелось бы показать что эксепшн — это не всегда только ошибка (как обычно ее воспринимают разработчики). Иногда он может быть частью логики.

Например, есть у нас функция чтения JSON объекта из файла:

Допустим мы пытаемся прочитать какие-то ранее загруженные данные. При такой операции эксепшн FileNotFoundException не является ошибкой и вполне допустим: возможно, мы ни разу не загружали данные, поэтому файла и нет. А вот JsonParseException — это уже признак ошибки, ибо данные были заргужены, обработаны, сохранены в файл, но почему-то сохранились не правильно.

Совсем другое дело, когда мы пытаемся прочитать файл, который должен быть всегда: при такой операции FileNotFoundException так же является сигналом ошибки.

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

Упрощение логики и архитектуры приложения

Попробуйте использовать эксепшены, и вы увидите как ваш код станет более лаконичным и понятным. Исчезнут все костыльные механизмы, возможно, уберутся куча вложенных if’ов, различные механизмы передачи ошибки вверх по стеку вызова, логика станет более простой и прямолинейной.

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

И, если рассматривать какой-либо самодостаточный кусок кода, например, компонента, то список бросаемых им эксепшенов выполняет еще одну важную вещь: дополняет интерфейс этого компонента.

Пользуясь сторонними компонентами мы привыкли обращать внимание только на положительную сторону — на то, что он умеет. При этом обычно не думаем о исключениях, которые он может создать в процессе работы. Список эксепшенов сразу предупреждает где, когда, и какие проблемы могут возникнуть. А предупрежден — значит вооружен.

Вот пример информативного интерфейса, который дополнен знаниями об эксепшенах:

Использование объектов

Использование определенного класса в качестве ошибки — очень удобное решение. Во первых, класс невозможно перепутать: взгляните на 2 способа обработки ошибки:

В первом варианте элементарная опечатке сломает вам всю логику, во втором же ошибку сделать просто невозможно.

Второе преимущество — класс эксепшена инкапсулирует все необходимые данные для его обработки. Например, AddressNotFoundException мог бы выглядеть следующим образом:

Как видим — эксепшн содержит адрес, который не удалось найти. Обработчик может его получить и выполнить на основании его какую-то свою логику.

Третье преимущество — это, собственно, все преимущества ООП. Хотя эксепшены, как правило, простые объекты, поэтому возможности ООП мало используются, но используются.

Например, у меня в приложении порядка 70 классов эксепшенов. Из них несколько — базовых — по одному классу на модуль. Все остальные — наследуются от базового класса своего модуля. Сделано это для удобства анализа логов.

Так же я использую несколько ИНТЕРФЕЙС-МАРКЕРОВ:

Обработчик по умолчанию, логирование

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

Откат изменений: так как операция не выполнена до конца, необходимо откатить все сделанные изменения. В противном случае мы испортим данные. Например, можно в CController::beforeAction() открыть транзакцию, в CController::afterAction() коммитить, а в случае ошибки сделать роллбэк в обработчике по умолчанию.

Это довольно грубый способ отката, плюс зачастую откат подразумевает не только откат транзакций, и знания о том, как правильно откатывать должны находиться в коде бизнес логики. В таких ситуациях следует воспользоваться вот таким приемом:

Получается что мы откатили изменения и бросили тот же эксепшн, что продолжить его обработку.

Логирование: так же обработчик по умолчанию позволяет нам выполнить какое-то кастомное логирование. Например, в моем приложении я все складываю в базу и использую собственное средство для анализа. На работе мы используем getsentry.com/welcome. В любом случае, эксепшн, дошедший до обработчика по умолчанию — скорее всего непредусмотренный эксепшн, и его необходимо логировать. Следует отметить, что в класс эксепшена можно добавить различную информацию, которую необходимо логировать для большего понимания причины возникновения ошибки.

Невозможность не заметить и перепутать

Огромным плюсом эксепшена является его однозначность: его не возможно не заметить и не возможно с чем-то спутать.

Из первого следует что мы всегда будем осведомлены о возникшей ошибке. И это замечательно — всегда лучше знать о проблеме, чем не знать.

Второй плюс становится очевиден в сравнении с кастомными методами обработки ошибки, например когда метод возвращает null если не нашла нужный объект и false в случае ошибки. В таком случае элементарно не заметить ошибку:

Эксепшн же невозможно пропустить.

Прекращение ошибочной операции

Но самое главное, и самое важное, что делает эксепшн — это прекращает дальнейшее выполнение операции. Операции, которая уже пошла не так. И, следовательно, результат которой непредсказуем.

Огромный минус самопальных механизмов обработки ошибок — необходимость самостоятельно проверять возникновение ошибки. Например, после каждой операции нам нужно писать что то типа:

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

Эксепшн защищает нас от подобных проблем: он либо ищет соответствующий обработчик ( его наличие означает что разработчик предусмотрел данную ситуацию, и все нормально), либо доходит до обработчика по умолчанию, который может откатить все изменения, залогировать ошибку, и выдать соответствующее предупреждение пользователю.

Когда следует вызывать эксепшены:

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

Встает вопрос: в каких ситуациях стоит вызывать эксепшн?

Если кратко — всегда! Если подробно: всегда, когда ты уверен что операция должна выполниться нормально, но что-то пошло не так, и ты не знаешь что с этим делать.

Посмотрим на простейший экшн добавления записи:

Когда мы введем некорректные данные поста эксепшн не вызывается. И это вполне соответствует формуле:

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

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

Далее идет выполнение операции и сохранение изменений.

Затем идет поствалидация — мы проверяем, действительно ли все сохранилось, и действительно ли статус изменился. На первый взгляд это может показаться бессмысленным, но: заказ вполне мог не сохранится (например, не прошел валидацию), а статус вполне мог быть изменен (например, кто-то набыдлокодил в CActiveRecord::beforeSave ). Поэтому эти действия необходимы, и, опять-таки, если что-то пошло не так — бросаем эксепшн, так как в пределах данного метода мы не знаем как обрабатывать эти ошибки.

Эксепшн vs возврат null

Следует отметить, что эксепшн следует бросать только в случае ошибки. Я видел как некоторые разработчики злоупотребляют ими, бросая их там, где не следовало бы. Особенно часто — когда метод возвращает объект: если не получается вернуть объект — бросается эксепшн.

Тут следует обратить внимание на обязанности метода. Например, СActiveRecord::find() не бросает эксепшн, и это логично — уровень его “знаний” не содержит информации о том, является ли ошибкой отсутствие результата. Другое дело, например, метод KladrService::resolveAddress() который в любом случае обязан вернуть объект адреса (иначе либо код неправильный, либо база не актуальная). В таком случае нужно бросать эксепшн, ибо отсутствие результата — это ошибка.

В целом же, описанная формула идеально определяет места, где необходимо бросать эксепшены. Но особо хотелось бы выделить 2 категории эксепшенов, которых нужно делать как можно больше:

Технические эксепшены

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

Вот несколько примеров:

Технические эксепшены помогут не допустить или отловить, имхо, большую часть багов в любом проекте. И неоспоримым плюсом их использования является отсутствие необходимости понимать предметную область: единственное что требуется — это дисциплина разработчика. Я призываю не лениться и вставлять такие проверки повсеместно.

Эксепшены утверждений

Эксепшены утверждений (по мотивам DDD ) вызываются когда мы обнаруживаем что нарушается какая-либо бизнес-логика. Безусловно, они тесно связанна с знаниями предметной области.

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

Например, есть метод добавления позиции в заказ:

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

Здесь можно подискутировать на тему необходимости подобных эксепшенов:
Например, можно написать тесты на методы перерасчета стоимости заказа, и проверка в теле метода — не более чем дублирование теста. Можно проверять возможность доставки заказа с новой позицией до добавления позиции (чтоб предупредить об этом пользователя, как минимум)

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

Поэтому в критичных местах такие эксепшены нужны однозначно.

Изменение логики для избегания эксепшна

Как я уже говорил, PHP разработчики боятся эксепшенов. Они боятся их появления, и боятся бросать их самостоятельно.

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

Вот пример: необходимо просто отобразить страницу по id (чтоб вы понимали — это реальный код из известного проекта)

Несмотря на простейшую и понятную задачу — здесь совершенно дикая логика.
Мало того, что она может показать пользователю совершенно не то что надо, так она еще и маскирует наши баги:

Тоже из реального проекта, и мотивация такая-же: вернуть хоть что-то, но не вызывать эксепшн, несмотря на то, что метод явно должен возвращать код города, а не региона.

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

Мое мнение — это недопустимо. Просто когда ты работаешь с большими деньгами (а я с ними работал довольно долго), вырабатывается определенные правила, и одно из них — прерывать операцию в случае любого подозрения на ошибку. Транзакция на 10 млн баксов: согласитесь, ее лучше отменить, чем перечислить деньги не тому человеку.

Конечно, обычно мы имеем дело с менее рискованными операциями. И в случае бага, например, инвалид не сможет оставить заявку на установку пандуса в подъезде. И разработчик на раслабоне (его ведь даже не оштрафуют) пренебрегает этими элементарными правилами, мол, подумаешь, мелочь какая. Проблема в том, что когда ему доверят что-то критически важное — вряд ли его подход измениться. Ибо проблема не в знаниях, и не в риске, а в дисциплине и в отношении к делу. И получается, что после таких программистов у нас где-то трубы зимой лопаются, где-то нефть разливается тоннами, где-то люди умирают десятками, где-то деньги воруются миллионами. Подумаешь, мелочь какая!

Собачки

Собачки вместо isset используют для лаконичности кода:

Действительно, выглядит намного короче, и на первый взгляд результат такой же. Но! опасно забывать что собачка — оператор игнорирования сообщений об ошибках. И @$policy->owner->address->locality вернет null не потому-что проверит существование цепочки объектов, а потому-что просто проигнорирует возникшую ошибку. А это совершенно разные вещи.

Проблем в том, что помимо игнорирования ошибки Trying to get property of non-object (которое и делает поведение собачки похожим на isset ), игнорируются все другие возможные ошибки.

PHP — это магический язык! При наличии всех этих магических методов ( __get, __set, __call, __callStatic, __invoke и пр.) мы не всегда можем сразу понять что происходит на самом деле.

Таким образом, столь необдуманное использование собачки потенциально создает огромное кол-во проблем.

Послесловие

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

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

Так что всем, кто прочел этот пост и подумал “что за бред”, “я все это знаю, но применять лень”, или “будет сосунок мне указывать” — я желаю совершить баг. Баг, за который оштрафуют или уволят. И тогда вы, возможно, вспомните этот пост, и задумаетесь: “возможно я и в правду что-то делаю не так”?

Совершите его как можно скорее. Ибо лучше один раз ошибиться, но прозреть, чем всю жизнь прожить быдлокоддером. Аминь.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *