Как находить и исправлять ошибки в 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 с сообщением. Лог запишет стек вызовов.