Всем привет. Пишу свою библиотеку для разработки приложений. Сейчас встал серьезный вопрос о том, как обрабатывать ошибки и исключения. Было решено использовать методы регистрации ошибок, фатальных ошибок и исключений. Заметил странность, что set_error_handler
и register_shutdown_function
при headers already sent by срабатывает дважды. Проблема сейчас заключается в том, что я не могу сделать редирект на страницу ошибки: Cannot modify header information - headers already sent by
. Ошибку вызвал специально в view, чтобы проверить, то есть после рендеринга страницы.
А вообще задача стоит в том, чтобы отобразить либо страницу ошибки с подробностями, либо страницу ошибки без них.
Каким образом я могу решить проблему с дублированием ошибок и сделать редирект при необходимости?
<?php
namespace Core;
use Helpers\Config;
use Helpers\File;
use Helpers\Url;
/**
* Class ErrorHandler
* Обработчик ошибок и исключений
* @package Сore
*/
class ErrorHandler
{
/**
* @var array Константа ассоциаций кода ошибок к тексту
*/
const ERRORS = [
E_ERROR => 'ERROR',
E_WARNING => 'WARNING',
E_PARSE => 'PARSE',
E_NOTICE => 'NOTICE',
E_CORE_ERROR => 'CORE_ERROR',
E_CORE_WARNING => 'CORE_WARNING',
E_COMPILE_ERROR => 'COMPILE_ERROR',
E_COMPILE_WARNING => 'COMPILE_WARNING',
E_USER_ERROR => 'USER_ERROR',
E_USER_WARNING => 'USER_WARNING',
E_USER_NOTICE => 'USER_NOTICE',
E_STRICT => 'STRICT',
E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR',
E_DEPRECATED => 'DEPRECATED',
E_USER_DEPRECATED => 'USER_DEPRECATED',
];
public static $status;
/**
* Регистрация методов управления ошибками и исключениями.
* Включение отображения ошибок
*/
public static function register()
{
ini_set('display_errors', 1);
error_reporting(E_ALL);
# Управление ошибками
set_error_handler([__CLASS__, 'errorHandler']);
# Управление фатальными ошибками
register_shutdown_function([__CLASS__, 'fatalErrorHandler']);
# Управление исключениями
set_exception_handler([__CLASS__, 'exceptionHandler']);
}
/**
* Управление ошибками
* @param $errno Код (номер) ошибки
* @param $errstr Сообщение ошибки
* @param $errfile Файл ошибки
* @param $errline Строка в файле ошикбки
* @return bool Предотвращение дальнейшего выполнения
*/
public static function errorHandler($errno, $errstr, $errfile, $errline)
{
self::show($errno, $errstr, $errfile, $errline);
# Не передается ошибка далее на обработку
return true;
}
/**
* Отображение ошибок на экран
* @param $errno Код (номер) ошибки
* @param $errstr Сообщение ошибки
* @param $errfile Файл ошибки
* @param $errline Строка в файле ошикбки
*/
private static function show($errno, $errstr, $errfile, $errline)
{
http_response_code(self::$status);
echo "[" . date('Y-m-d H:i:s') . "] <b>" . self::getErrorName($errno) .
"</b> в файле {$errfile} на строке <b>{$errline}</b>:<br>{$errstr}<br>";
$message = "[" . date('Y-m-d H:i:s') . "]" . self::getErrorName($errno) .
" в файле {$errfile} на строке {$errline}:" . PHP_EOL . $errstr . PHP_EOL . PHP_EOL;
self::writeLog($message);
}
/**
* Получение наименования класса ошибки по ее коду
* @param $error integer Номер ошибки
* @return string Наименование класса ошибки
*/
private static function getErrorName($error)
{
$error_name = $error;
if (array_key_exists($error, self::ERRORS)) {
$error_name = self::ERRORS[$error];
}
return $error_name;
}
private static function writeLog($message)
{
$errors_config_section = Config::getSettings('logs', 'errors');
$log_file = File::createFile($errors_config_section['filename'], $errors_config_section['extension'], LOG);
File::write($log_file, $message);
header("Location: " . Url::to($errors_config_section['controller'], self::$status));
}
/**
* Управление фатальными ошибками
* @return bool Предотвращение дальнейшего выполнения
*/
public static function fatalErrorHandler(int $status = 500)
{
self::$status = $status;
if (!empty($error = error_get_last()) && $error['type'] && (E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR)) {
ob_get_clean();
self::show($error['type'], $error['message'], $error['file'], $error['line']);
}
return true;
}
/**
* Управление исключениями
* @param \Exception|\Error $ex Исключение
* @return bool Предотвращение дальнейшего выполнения
*/
public static function exceptionHandler($ex, int $status = 500)
{
self::$status = $status;
self::show(get_class($ex), $ex->getMessage(), $ex->getFile(), $ex->getLine());
return true;
}
}
Непонятно, при чем тут редирект.
Редирект - это самое неправильное, что только можно сделать при ошибке.
Существует такая вещь, как коды возврата НТТР. Вещь чрезвычайно полезная и удобная.
Она сообщает клиенту, чем завершился его запрос.
Соответственно, обманывать клиента не следует. А следует отдавать ровно тот код возврата, который соответствует статусу страницы.
Если после завершения обработки запроса следует запросить другой урл - тогда отправляем код 3хх и заголовок Location:. Но если выполнение запроса завершилось с ошибкой, то единственно правильным кодом ответа будет 500, без каких-бы то ни было редиректов.
Код отображения страницы оибки отличается только проверкой переменной, которая отвечает за режим сайта - боевой или в разработке. И в зависимости от этот выводятся подобности, или нет. Код же возврата остается одним и тем же - 500, без редиректов.
Отлов ошибок при рендеринге - это один из немногих случаев, когда включение буфеиринга оправдано. Но не следует увлекаться буфферингом - как и любое сообщение об ошибке, Cannot modify header information служит для помощи программисту. И без нужды затыкать эту ошибку не следует. То есть, буфферинг включать только перед тем, как начинается непосредственно вывод во View
Вам нужно буферизовать рендеринг view с помощью ob_start() и выводить его в поток только если все ок. В этом случае никакого "headers already sent by" не произойдет.
Перевод документов на английский язык: Важность и ключевые аспекты
Какие существуют виды рекламных бордов и как выбрать подходящий?
Собственно есть сервер на нем настроен apache+php и отдельно крутится на nodejs nuxt, стал вопрос, как увязать их вместеNUXT отвечает за фронтэнд и php за бекэнд...
Ввести 2 предложения, а затем исключить слова во втором предложении, которые имеются в первомПосле чего вывести полученное второе предложение