Hello. I am writing my own library for application development. Now there is a serious question about how to handle errors and exceptions. It was decided to use the methods of registering errors, fatal errors and exceptions. I noticed the oddity that set_error_handler and register_shutdown_function with headers already sent by fire twice. The problem now is that I cannot redirect to the error page: Cannot modify header information - headers already sent by . An error caused specifically in the view to check, that is, after rendering the page.

In general, the task is to display either an error page with details, or an error page without them.

How can I solve the problem of duplicating errors and redirect if necessary?

 <?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('Ymd H:i:s') . "] <b>" . self::getErrorName($errno) . "</b> Π² Ρ„Π°ΠΉΠ»Π΅ {$errfile} Π½Π° строкС <b>{$errline}</b>:<br>{$errstr}<br>"; $message = "[" . date('Ymd 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; } } 

    2 answers 2

    It is not clear what the redirect is.

    Redirect is the most wrong thing to do in case of an error.

    There is such a thing as the HTTP return codes. The thing is extremely useful and convenient.
    She tells the client how his request ended.
    Accordingly, it is not necessary to deceive the client. And you should give exactly the return code that corresponds to the page status.
    If after completing the processing of the request, you should request another URL - then send the 3xx code and the Location: header. But if the request was completed with an error, then the only correct response code would be 500, without any redirects.

    The code for displaying the page is different only by checking the variable that is responsible for the mode of the site - combat or in development. And depending on this, similarities are derived or not. The return code remains the same - 500, without redirects.

    Catching errors during rendering is one of the few cases when enabling buffering is justified. But you should not get involved in buffering - like any error message, Cannot modify header information serves to help the programmer. And without the need to plug this error should not be. That is, include buffering only just before the output starts in View.

    • Thanks for the answer. Regarding HTTP return codes: if there is an error , say No such file or directory , then the return code will be 200. Others it acquires in the case of a fatal error or exception . At the moment I am making a mechanism. Fully agree with the error 500 and pages. At the moment, there is a problem and a lack of understanding of exactly working with errors and redirection to a stylized error page. As I understand it, the redirect to the page with the code can be done, but you need to enable buffering before rendering, and this is an adequate solution, right? - mepihindeveloper
    • if the request was completed with an error, then the only correct response code will be 500, without any redirects whatever - Ipatiev
    • and if you need to show a beautiful page? That will have to do a redirect, as the url can be custom (CNC or not) - mepihindeveloper
    • I am not able to understand this problem. Why can't the error handler show a beautiful page by itself? what have url? Listen up Before you "write your own library for developing applications," do yourself a favor, familiarize yourself with how existing libraries work. At least the most basic principles. - Ipatiev
    • Whether the following is adequate: I call exception only in case of a crash of the application or methods (database connections) that are critical. errors in the developer mode, I display a special page, and in the case of the release mode, all errors are not displayed and are treated as a function, returning false with an array of error texts, if necessary. - mepihindeveloper

    You need to buffer rendering the view with ob_start () and output it to the stream only if everything is ok. In this case, no "headers already sent by" will occur.

    • Sergey, thanks for the answer. Do I understand correctly that if there is an error (inside the view), then it will also be understood using buffering? - mepihindeveloper
    • one
      of course, will be caught - Sergey Konovalov