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:
@@ -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
263
helpers/ErrorHandler.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user