Add centralized error handler

- Add ErrorHandler class for consistent error handling and logging
- Provides methods for common error responses (401, 403, 404, 422, 500)
- Includes error logging to temp directory
- Update get_template.php to use ErrorHandler (example migration)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-29 10:55:15 -05:00
parent d2a8c73e2c
commit 8a8b1b0258
2 changed files with 309 additions and 44 deletions

View File

@@ -1,48 +1,50 @@
<?php <?php
/**
* Get Template API
* Returns a ticket template by ID
*/
require_once dirname(__DIR__) . '/helpers/ErrorHandler.php';
ErrorHandler::init();
try {
session_start(); session_start();
require_once dirname(__DIR__) . '/config/config.php'; require_once dirname(__DIR__) . '/config/config.php';
require_once dirname(__DIR__) . '/helpers/Database.php';
require_once dirname(__DIR__) . '/models/TemplateModel.php'; require_once dirname(__DIR__) . '/models/TemplateModel.php';
header('Content-Type: application/json'); header('Content-Type: application/json');
// Check authentication // Check authentication
if (!isset($_SESSION['user']) || !isset($_SESSION['user']['user_id'])) { if (!isset($_SESSION['user']) || !isset($_SESSION['user']['user_id'])) {
echo json_encode(['success' => false, 'error' => 'Not authenticated']); ErrorHandler::sendUnauthorizedError('Not authenticated');
exit;
} }
// Get template ID from query parameter // Get template ID from query parameter
$templateId = $_GET['template_id'] ?? null; $templateId = $_GET['template_id'] ?? null;
if (!$templateId || !is_numeric($templateId)) { if (!$templateId || !is_numeric($templateId)) {
echo json_encode(['success' => false, 'error' => 'Valid template ID required']); ErrorHandler::sendValidationError(
exit; ['template_id' => 'Valid template ID required'],
'Invalid request'
);
} }
// Cast to integer for safety // Cast to integer for safety
$templateId = (int)$templateId; $templateId = (int)$templateId;
// Create database connection
$conn = new mysqli(
$GLOBALS['config']['DB_HOST'],
$GLOBALS['config']['DB_USER'],
$GLOBALS['config']['DB_PASS'],
$GLOBALS['config']['DB_NAME']
);
if ($conn->connect_error) {
echo json_encode(['success' => false, 'error' => 'Database connection failed']);
exit;
}
// Get template // Get template
$conn = Database::getConnection();
$templateModel = new TemplateModel($conn); $templateModel = new TemplateModel($conn);
$template = $templateModel->getTemplateById($templateId); $template = $templateModel->getTemplateById($templateId);
$conn->close();
if ($template) { if ($template) {
echo json_encode(['success' => true, 'template' => $template]); echo json_encode(['success' => true, 'template' => $template]);
} else { } else {
echo json_encode(['success' => false, 'error' => 'Template not found']); ErrorHandler::sendNotFoundError('Template not found');
}
} catch (Exception $e) {
ErrorHandler::log($e->getMessage(), E_ERROR);
ErrorHandler::sendErrorResponse('Failed to retrieve template', 500, $e);
} }

263
helpers/ErrorHandler.php Normal file
View File

@@ -0,0 +1,263 @@
<?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);
}
}