Files
tinker_tickets/helpers/ErrorHandler.php

264 lines
7.4 KiB
PHP
Raw Normal View History

<?php
/**
* Centralized Error Handler
*
* Provides consistent error handling, logging, and response formatting
* across the application.
*/
class ErrorHandler {
private static $logFile = null;
private static $initialized = false;
/**
* Initialize error handling
*
* @param bool $displayErrors Whether to display errors (false in production)
*/
public static function init($displayErrors = false) {
if (self::$initialized) {
return;
}
// Set error reporting
error_reporting(E_ALL);
ini_set('display_errors', $displayErrors ? '1' : '0');
ini_set('log_errors', '1');
// Set up log file
self::$logFile = sys_get_temp_dir() . '/tinker_tickets_errors.log';
ini_set('error_log', self::$logFile);
// Register handlers
set_error_handler([self::class, 'handleError']);
set_exception_handler([self::class, 'handleException']);
register_shutdown_function([self::class, 'handleShutdown']);
self::$initialized = true;
}
/**
* Handle PHP errors
*
* @param int $errno Error level
* @param string $errstr Error message
* @param string $errfile File where error occurred
* @param int $errline Line number
* @return bool
*/
public static function handleError($errno, $errstr, $errfile, $errline) {
// Don't handle suppressed errors
if (!(error_reporting() & $errno)) {
return false;
}
$errorType = self::getErrorTypeName($errno);
$message = "$errorType: $errstr in $errfile on line $errline";
self::log($message, $errno);
// For fatal errors, throw exception
if (in_array($errno, [E_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR])) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
return true;
}
/**
* Handle uncaught exceptions
*
* @param Throwable $exception
*/
public static function handleException($exception) {
$message = sprintf(
"Uncaught %s: %s in %s on line %d\nStack trace:\n%s",
get_class($exception),
$exception->getMessage(),
$exception->getFile(),
$exception->getLine(),
$exception->getTraceAsString()
);
self::log($message, E_ERROR);
// Send error response if headers not sent
if (!headers_sent()) {
self::sendErrorResponse(
'An unexpected error occurred',
500,
$exception
);
}
}
/**
* Handle fatal errors on shutdown
*/
public static function handleShutdown() {
$error = error_get_last();
if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
$message = sprintf(
"Fatal Error: %s in %s on line %d",
$error['message'],
$error['file'],
$error['line']
);
self::log($message, E_ERROR);
if (!headers_sent()) {
self::sendErrorResponse('A fatal error occurred', 500);
}
}
}
/**
* Log an error message
*
* @param string $message Error message
* @param int $level Error level
* @param array $context Additional context
*/
public static function log($message, $level = E_USER_NOTICE, $context = []) {
$timestamp = date('Y-m-d H:i:s');
$levelName = self::getErrorTypeName($level);
$logMessage = "[$timestamp] [$levelName] $message";
if (!empty($context)) {
$logMessage .= " | Context: " . json_encode($context);
}
error_log($logMessage);
}
/**
* Send a JSON error response
*
* @param string $message User-facing error message
* @param int $httpCode HTTP status code
* @param Throwable|null $exception Original exception (for debug info)
*/
public static function sendErrorResponse($message, $httpCode = 500, $exception = null) {
http_response_code($httpCode);
if (!headers_sent()) {
header('Content-Type: application/json');
}
$response = [
'success' => false,
'error' => $message
];
// Add debug info in development (check for debug mode)
if (isset($GLOBALS['config']['DEBUG']) && $GLOBALS['config']['DEBUG'] && $exception) {
$response['debug'] = [
'type' => get_class($exception),
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine()
];
}
echo json_encode($response);
exit;
}
/**
* Send a validation error response
*
* @param array $errors Array of validation errors
* @param string $message Overall error message
*/
public static function sendValidationError($errors, $message = 'Validation failed') {
http_response_code(422);
if (!headers_sent()) {
header('Content-Type: application/json');
}
echo json_encode([
'success' => false,
'error' => $message,
'validation_errors' => $errors
]);
exit;
}
/**
* Send a not found error response
*
* @param string $message Error message
*/
public static function sendNotFoundError($message = 'Resource not found') {
self::sendErrorResponse($message, 404);
}
/**
* Send an unauthorized error response
*
* @param string $message Error message
*/
public static function sendUnauthorizedError($message = 'Authentication required') {
self::sendErrorResponse($message, 401);
}
/**
* Send a forbidden error response
*
* @param string $message Error message
*/
public static function sendForbiddenError($message = 'Access denied') {
self::sendErrorResponse($message, 403);
}
/**
* Get error type name from error number
*
* @param int $errno Error number
* @return string Error type name
*/
private static function getErrorTypeName($errno) {
$types = [
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',
];
return $types[$errno] ?? 'UNKNOWN';
}
/**
* Get recent error log entries
*
* @param int $lines Number of lines to return
* @return array Log entries
*/
public static function getRecentErrors($lines = 50) {
if (self::$logFile === null || !file_exists(self::$logFile)) {
return [];
}
$file = file(self::$logFile);
if ($file === false) {
return [];
}
return array_slice($file, -$lines);
}
}