Создание обработчика ошибок для фреймворка

Andre Kowalsy
106 раз
7 мин чтения
Опубликовано: 23-07-2025
Обновлено: 24-03-2026
Категории: Пишем свою CMS

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

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

Зачем нужен класс обработчик ошибок?

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

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

Создание класса ErrorHandler

В ядре фреймворка создаем класс ErrorHandler. Мы немного расширим его функционал. Например, 404 ошибка в предыдущей версии фреймворка имела свой отдельный шаблон. Теперь мы реализуем стандартный шаблон для страницы 404, который будет выводить сообщение «Страница не найдена» и генерировать код 404.

namespace blog;

class ErrorHandler {
    
public function __construct()
{
    if (DEBUG) {
        error_reporting(-1);
    } else {
        error_reporting(0);
    }
    set_exception_handler([$this, 'exceptionHandler']);
    set_error_handler([$this, 'errorHandler']);
    ob_start();
    register_shutdown_function([$this, 'fatalErrorHandler']);
}

public function errorHandler($errno, $errstr, $errfile, $errline)
{
    $this->logError($errstr, $errfile, $errline);
    $this->displayError($errno, $errstr, $errfile, $errline);
}

public function fatalErrorHandler()
{
    $error = error_get_last();
    if (!empty($error) && $error['type'] & (E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR)) {
        $this->logError($error['message'], $error['file'], $error['line']);
        ob_end_clean();
        $this->displayError($error['type'], $error['message'], $error['file'], $error['line']);
    } else {
        ob_end_flush();
    }
}

public function exceptionHandler(\Throwable $e)
{
    $this->logError($e->getMessage(), $e->getFile(), $e->getLine());
    $this->displayError('Исключение', $e->getMessage(), $e->getFile(), $e->getLine(), $e->getCode());
}

protected function logError($message = '', $file = '', $line = '')
{
    file_put_contents(
        LOGS . '/errors.log',
        "[" . date('Y-m-d H:i:s') . "] Текст ошибки: {$message} | Файл: {$file} | Строка: {$line}\n=================\n",
        FILE_APPEND);
}

protected function displayError($errno, $errstr, $errfile, $errline, $responce = 500)
{
    if ($responce == 0) {
        $responce = 404;
    }
    http_response_code($responce);
    if ($responce == 404 && !DEBUG) {
        require WWW . '/errors/404.php';
        die;
    }
    if (DEBUG) {
        require WWW . '/errors/development.php';
    } else {
        require WWW . '/errors/production.php';
    }
    die;
}
}
Настройка обработчиков ошибок

В конструкторе мы используем стандартные функции PHP: set_exception_handler и set_error_handler. Также учтем константу DEBUG, которая имеет значение 1 (включен режим отладки) или 0 (режим продакшена).

if (defined('DEBUG') && DEBUG) {
    error_reporting(E_ALL);
} else {
    error_reporting(0);
}
Обработка исключений

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

public function exceptionHandler($exception) {
    $this->logError($exception->getMessage(), $exception->getFile(), $exception->getLine());
    $this->displayError($exception);
}
Логирование и отображение ошибок

У нас будет два метода: logError и displayError.

private function logError($message, $file, $line) {
    $logFile = __DIR__ . '/logs/errors.log';
    $logContent = "[" . date("Y-m-d H:i:s") . "] Error: $message in $file on line $line\n";
    file_put_contents($logFile, $logContent, FILE_APPEND);
}

private function displayError($exception) {
    http_response_code(500);
    require 'public/errors/production.php'; // Показываем страничку с ошибкой
}
Реализация страницы 404

Создаем страничку 404 в папке public/errors/404.php. В ней будет простое сообщение, информирующее пользователя о том, что страница не найдена.

404 Not Found";
echo "Страница не найдена.";

Тестирование обработчика ошибок

Теперь давайте протестируем созданный класс. В конструкторе класса App создадим его экземпляр:

class App {
    public function __construct() {
        new ErrorHandler();
    }
}

Давайте подробно разберем код!

Основные технологии
  • PHP: серверный язык программирования.
  • Namespaces: для организации кода и предотвращения конфликтов имен.
  • Обработка ошибок и исключений: механизмы для управления ошибками и их логирования.
Структура проекта
/vendor
    └── /blog
        ├── ErrorHandler.php
/config
    └── params.php
/public
    ├── index.php
    └── /errors
        ├── /images 
               └── 404.png
        ├── development.php
        ├── production.php
        └── 404.php
/tmp
    └── /logs
  • vendor/blog/: Папка, содержащая классы для нашего приложения.
  • public/errors/: Папка для хранения шаблонов ошибок.
  • tmp/logs/: Папка для хранения логов ошибок.
Разбор кода
1. Класс ErrorHandler
namespace blog;

class ErrorHandler
{
    public function __construct()
    {
        if (DEBUG) {
            error_reporting(-1);
        } else {
            error_reporting(0);
        }
        set_exception_handler([$this, 'exceptionHandler']);
        set_error_handler([$this, 'errorHandler']);
        ob_start();
        register_shutdown_function([$this, 'fatalErrorHandler']);
    }
    // ...
}

Объяснение:

  • В конструкторе класса ErrorHandler мы настраиваем обработку ошибок. Если включен режим отладки (DEBUG), мы выводим все ошибки, иначе — ничего не выводим.
  • Устанавливаем обработчики для исключений и ошибок, а также начинаем буферизацию вывода с помощью ob_start(). Это позволяет управлять выводом ошибок.
2. Обработка ошибок
public function errorHandler($errno, $errstr, $errfile, $errline)
{
    $this->logError($errstr, $errfile, $errline);
    $this->displayError($errno, $errstr, $errfile, $errline);
}

Объяснение:

  • Метод errorHandler принимает параметры ошибки и вызывает методы для логирования и отображения ошибки. Это позволяет сохранить информацию об ошибке в логах и показать пользователю понятное сообщение.
3. Логирование ошибок
protected function logError($message = '', $file = '', $line = '')
{
    file_put_contents(LOGS .'/errors.log', "[". date('Y-m-d H:i:s') . "] Текст ошибки: {$message} | Файл: {$file} | Строка: {$line}\n=================\n", FILE_APPEND);
}

Объяснение:

  • Метод logError записывает информацию об ошибке в файл errors.log. Это полезно для последующего анализа и устранения проблем.
4. Обработка фатальных ошибок
public function fatalErrorHandler()
{
    $error = error_get_last();
    if (!empty($error)) {
        $this->logError($error['message'], $error['file'], $error['line']);
        ob_end_clean();
        $this->displayError($error['type'], $error['message'], $error['file'], $error['line']);
    } else {
        ob_end_flush();
    }
}

Объяснение:

  • Метод fatalErrorHandler обрабатывает фатальные ошибки, которые могут произойти в приложении. Он логирует ошибку и отображает сообщение, если необходимо.
5. Обработка исключений
public function exceptionHandler(\Throwable $e)
{
    $this->logError($e->getMessage(), $e->getFile(), $e->getLine());
    $this->displayError('Исключение', $e->getMessage(), $e->getFile(), $e->getLine(), $e->getCode());
}

Объяснение:

  • Метод exceptionHandler принимает объект исключения и логирует его сообщение, файл и строку, где произошло исключение. После этого он вызывает displayError, чтобы показать пользователю понятное сообщение об ошибке.
6. Отображение ошибок
public function displayError($errno, $errstr, $errfile, $errline, $response = 500)
{
    if ($response == 0) {
        $response = 404;
    }
    http_response_code($response);
    if ($response == 404 && !DEBUG) {
        require WWW .'/errors/404.php';
        die();
    }
    if (DEBUG) {
        require WWW .'/errors/development.php';
    } else {
        require WWW .'/errors/production.php';
    }
    die();
}

Объяснение:

  • Метод displayError отвечает за показ сообщения об ошибке пользователю, отображая детальную информацию в режиме отладки или очищая информацию для конечных пользователей.
Шаблон 404.php

Важно отметить, что в наше блоге nikolayogreba.ru проекте мы в основном используете шаблон по следующему адресу: https://nikolayogreba.ru/404. Однако иногда может отображаться шаблон по умолчанию, поэтому важно иметь возможность кастомизировать страницу ошибки 404.

Создание файла development.php

Теперь давайте создадим файл development.php в каталоге errors/ с содержимым:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Ошибка</title>
</head>
<body>

<h1>Произошла ошибка</h1>
<p><b>Код ошибки:</b> <?=$errno;?></p>
<p><b>Текст ошибки:</b> <?=$errstr;?></p>
<p><b>Файл, в котором произошла ошибка:</b> <?=$errfile;?></p>
<p><b>Строка, в которой произошла ошибка:</b> <?=$errline;?></p>

</body>
</html>

Объяснение:

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

Создание файла production.php

Также создадим файл production.php в том же каталоге с содержимым:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Ошибка</title>
</head>
<body>

<h1>Произошла ошибка</h1>
<p><a href="<?= PATH; ?>">Вернуться на главную</a></p>

</body>
</html>

Объяснение:

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

Использование шаблона 404

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

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Ошибка - 404</title>
    <style type="text/css">
        body {
            font-family: Arial, Helvetica, sans-serif;
        }
        .wrap {
            width: 1000px;
            margin: 0 auto;
        }
        .logo {
            width: 430px;
            position: absolute;
            top: 25%;
            left: 35%;
        }
        p a {
            color: #eee;
            font-size: 13px;
            margin-left: 30px;
            padding: 5px;
            background: #FF3366;
            text-decoration: none;
            border-radius: .3em;
        }
        p a:hover {
            color: #fff;
        }
        .footer {
            position: absolute;
            bottom: 10px;
            right: 10px;
            font-size: 12px;
            color: #aaa;
        }
        .footer a {
            color: #666;
            text-decoration: none;
        }
    </style>
</head>
<body>
<div class="wrap">
    <div class="logo">
        <img src="/errors/images/404.png" width="500" alt="Ошибка 404" />
        <p><a href="<?= PATH; ?>">Вернуться на главную</a></p>
    </div>
</div>
<div class="footer">
    Дизайн с помощью - <a href="http://w3layouts.com">W3layouts</a>
</div>
</body>
</html>

Объяснение:

  • Этот шаблон отображает страницу ошибки 404 с изображением и ссылкой для возврата на главную страницу. Он использует стили для улучшения внешнего вида и удобства пользователя.

Объяснение:

  • Этот шаблон отображает страницу ошибки 404 с изображением и ссылкой для возврата на главную страницу. Он использует стили для улучшения внешнего вида и удобства пользователя.

Заключение

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

Выбросим исключение для проверки: \public в index.php

throw new Exception("Тестовое исключение");

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

Если у вас есть вопросы или вы хотите узнать больше о других аспектах разработки фреймворка на PHP, не стесняйтесь задавать их в комментариях!