Как находить и исправлять ошибки в PHP-модулях панели администратора

Раздел: Разработка PHP-приложений -> Разработка модулей панели администратора

Типичные ошибки и способы их устранения

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

Для этого в конфигурации сервера задаются следующие параметры:

error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/php_errors.log');

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

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

Как сделать, чтобы ошибки отображались только в Development-режиме?

Используется переменная окружения и условная настройка:

if (getenv('APP_ENV') === 'dev') {
    ini_set('display_errors', '1');
    error_reporting(E_ALL);
} else {
    ini_set('display_errors', '0');
    error_reporting(E_ALL & ~E_NOTICE & ~E_USER_NOTICE);
}

Проблема: в Production-режиме иногда забывают отключить display_errors, что приводит к утечке путей файлов и другой критической информации. Решение: проверять значение через конфигурационный файл приложения, а не доверять только php.ini.

Как обрабатывать непредвиденные исключения в модуле администратора без остановки всего скрипта?

Подходит глобальный обработчик через set_exception_handler:

set_exception_handler(function (Throwable $e) {
    error_log($e->__toString());
    http_response_code(500);
    echo json_encode(['error' => 'Внутренняя ошибка сервера']);
    exit;
});

Проблема: такой перехватчик не срабатывает для фатальных ошибок типа Parse Error, если они возникают до его регистрации. Решение: использовать register_shutdown_function и проверять error_get_last.

Как записывать ошибки с дополнительным контекстом (пользователь, модуль)?

Применяется библиотека Monolog. Пример интеграции в модуль:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$log = new Logger('admin_module');
$log->pushHandler(new StreamHandler('/var/log/admin_module.log', Logger::ERROR));
$log->error('Ошибка в обработчике формы', [
    'user_id' => $adminId,
    'module' => 'users_edit',
    'trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)
]);

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

Как перехватывать и анализировать запросы к API админ-панели с ошибками?

Вариант с middleware и логированием JSON-ответов:

$response = ['success' => false, 'error' => '', 'data' => []];
try {
    // логика модуля
    $response['data'] = $result;
    $response['success'] = true;
} catch (\Exception $e) {
    $response['error'] = $e->getMessage();
    error_log('API error: ' . $e->__toString());
    http_response_code(422);
} finally {
    header('Content-Type: application/json');
    echo json_encode($response);
    exit;
}

Проблема: исключения, выброшенные внутри фреймворка, могут не попасть в блок catch, если они не унаследованы от \Exception (например, \Error). Решение: использовать Throwable в PHP 7+.

Как избежать ошибок при работе с базой данных в модулях админ-панели?

Применение PDO в режиме исключений и транзакций:

try {
    $pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
    $pdo->beginTransaction();
    $stmt = $pdo->prepare('UPDATE users SET status = ? WHERE id = ?');
    $stmt->execute([$status, $id]);
    $pdo->commit();
} catch (PDOException $e) {
    $pdo->rollBack();
    error_log('DB Error: ' . $e->getMessage());
    // вывод понятного сообщения
    echo 'Операция не выполнена, обратитесь к администратору.';
}

Проблема: забывают установить PDO::ERRMODE_EXCEPTION, и ошибки становятся тихими. Решение: вынести конфигурацию PDO в общий класс Database.

Расширенные примеры с пояснениями

Пример
// Пример 1: Комбинированный обработчик ошибок и исключений
function customErrorHandler($severity, $message, $file, $line) {
    if (error_reporting() & $severity) {
        throw new ErrorException($message, 0, $severity, $file, $line);
    }
}
set_error_handler('customErrorHandler');

set_exception_handler(function (Throwable $e) {
    $logData = [
        'message' => $e->getMessage(),
        'file' => $e->getFile(),
        'line' => $e->getLine(),
        'code' => $e->getCode(),
        'trace' => $e->getTraceAsString()
    ];
    file_put_contents('/var/log/admin_errors.json', json_encode($logData) . PHP_EOL, FILE_APPEND);
    if (php_sapi_name() !== 'cli') {
        header('HTTP/1.1 500 Internal Server Error');
        include 'templates/error_page.php';
    }
    exit(1);
});

$result = 1 / 0; // вызовет ErrorException, затем перехватится исключением
В файл /var/log/admin_errors.json будет записана строка с JSON-объектом ошибки деления на ноль. Страница отобразит шаблон ошибки.
Пример
// Пример 2: Логирование с уровнем важности через Monolog
require 'vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SlackHandler;
use Monolog\Formatter\LineFormatter;

$log = new Logger('admin_panel');
$formatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n");
$streamHandler = new StreamHandler('/var/log/admin.log', Logger::DEBUG);
$streamHandler->setFormatter($formatter);
$log->pushHandler($streamHandler);

// Для критических ошибок отправляем в Slack
$slackHandler = new SlackHandler('xoxb-token', '#admin-errors', 'AdminBot', true, null, Logger::CRITICAL);
$log->pushHandler($slackHandler);

try {
    // код модуля
    throw new \RuntimeException('Недостаточно прав для операции');
} catch (\Exception $e) {
    $log->critical('Ошибка доступа', ['user' => $adminLogin, 'action' => 'delete_user']);
    $log->error($e->getMessage(), ['exception' => $e]);
}
В лог-файл попадут записи уровня DEBUG (если есть), ERROR и CRITICAL. В Slack уйдёт только критическая запись с контекстом.
Пример
// Пример 3: Отладка через Xdebug и профилирование
// Установка в php.ini:
zend_extension=xdebug.so
xdebug.mode=develop,debug,trace
xdebug.start_with_request=yes
xdebug.output_dir=/tmp/xdebug
xdebug.log=/tmp/xdebug.log

// В коде можно использовать var_dump с xdebug:
$data = ['user' => 'admin', 'role' => 'superadmin'];
\xdebug_var_dump($data);
Вывод будет содержать цвета и структурированный дамп переменной. Файл трассировки сохранится в /tmp/xdebug.
Пример
// Пример 4: Сбор ошибок при AJAX-запросах в админке
// Фронтенд отправляет fetch('/admin/api/users', {method:'POST', body: formData})
// Сервер возвращает JSON с ошибкой
header('Content-Type: application/json; charset=utf-8');
try {
    if (!isset($_SESSION['admin'])) {
        throw new \Exception('Неавторизованный доступ', 401);
    }
    // валидация и обработка
    echo json_encode(['success' => true, 'data' => $result]);
} catch (\Exception $e) {
    http_response_code($e->getCode() ?: 500);
    echo json_encode(['success' => false, 'error' => $e->getMessage()]);
    error_log('AJAX error: ' . $e->__toString());
}
При неавторизованном доступе клиент получит HTTP-код 401 и JSON с сообщением. Лог запишет стек вызовов.

Ошибки в админ-панели PHP - comments

En
Admin error php (php)