seedproject-web/api/system/ErrorHandler.php
Carlos Arias 1559ce017d chore: scaffold SeedProject base (Phase 1)
Clean-room copy of the reusable engines from comiida, with all
instance data, secrets, dependencies, and build output excluded:
- app/         Astro theme skeleton (no comiida blog posts; hero image -> placeholder)
- api/         SeedProject PHP framework (no vendor/.env/config.php)
- content-pipeline/  engine only (scripts/admin/prompts; empty runtime state)
- astroagent.config.json + app/.astroagent/skills

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYHWLHihq3v9nxNwoPCKSn
2026-07-04 22:53:10 +00:00

170 lines
7.7 KiB
PHP

<?php
class ErrorHandler {
private static array $errorTypeMap = [
E_ERROR => 'E_ERROR',
E_WARNING => 'E_WARNING',
E_PARSE => 'E_PARSE',
E_NOTICE => 'E_NOTICE',
E_CORE_ERROR => 'E_CORE_ERROR',
E_CORE_WARNING => 'E_CORE_WARNING',
E_COMPILE_ERROR => 'E_COMPILE_ERROR',
E_COMPILE_WARNING => 'E_COMPILE_WARNING',
E_USER_ERROR => 'E_USER_ERROR',
E_USER_WARNING => 'E_USER_WARNING',
E_USER_NOTICE => 'E_USER_NOTICE',
E_STRICT => 'E_STRICT',
E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
E_DEPRECATED => 'E_DEPRECATED',
E_USER_DEPRECATED => 'E_USER_DEPRECATED',
];
private static array $errorTypeLabels = [
E_ERROR => 'Fatal Error',
E_WARNING => 'Warning',
E_PARSE => 'Parse Error',
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 Standards',
E_RECOVERABLE_ERROR => 'Recoverable Error',
E_DEPRECATED => 'Deprecated',
E_USER_DEPRECATED => 'User Deprecated',
];
private static array $fatalTypes = [
E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR,
E_USER_ERROR, E_RECOVERABLE_ERROR,
];
public static function handleError($errno, $errstr, $errfile, $errline) {
if (!(error_reporting() & $errno)) {
return;
}
// Only persist fatal/critical errors — skip warnings, notices, deprecated, strict
if (in_array($errno, self::$fatalTypes)) {
self::persistError($errno, $errstr, $errfile, $errline);
self::logError($errno, $errstr, $errfile, $errline);
}
self::displayError($errno, $errstr, $errfile, $errline);
return true;
}
public static function handleException($exception) {
$trace = $exception->getTraceAsString();
self::persistError(E_ERROR, $exception->getMessage(), $exception->getFile(), $exception->getLine(), $trace, 'Exception');
self::logError(E_ERROR, $exception->getMessage(), $exception->getFile(), $exception->getLine());
self::displayError(E_ERROR, $exception->getMessage(), $exception->getFile(), $exception->getLine(), $trace);
}
public static function handleShutdown() {
$error = error_get_last();
if ($error !== null && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) {
self::persistError($error['type'], $error['message'], $error['file'], $error['line']);
self::logError($error['type'], $error['message'], $error['file'], $error['line']);
self::displayError($error['type'], $error['message'], $error['file'], $error['line']);
}
}
// ─── JSON flat-file persistence ───────────────────────────────────────────
private static function persistError($errno, $errstr, $errfile, $errline, $trace = null, $forcedType = null) {
try {
$errorType = $forcedType ?? (self::$errorTypeMap[$errno] ?? 'UNKNOWN');
$userId = null;
$orgId = null;
if (isset($_SESSION['login'])) {
$userId = $_SESSION['login']['userid'] ?? null;
$orgId = $_SESSION['login']['org_id'] ?? null;
}
$url = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http')
. '://' . ($_SERVER['HTTP_HOST'] ?? '') . ($_SERVER['REQUEST_URI'] ?? '');
$method = $_SERVER['REQUEST_METHOD'] ?? null;
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? null;
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? null;
$referer = $_SERVER['HTTP_REFERER'] ?? null;
$entry = [
'id' => uniqid('err_', true),
'userid' => $userId,
'org_id' => $orgId,
'errortype' => $errorType,
'errorcode' => (string)$errno,
'url' => substr($url, 0, 512),
'file' => substr($errfile, 0, 512),
'line' => (int)$errline,
'fullerror' => $errstr,
'trace' => $trace,
'method' => $method ? substr($method, 0, 10) : null,
'ip_address' => $ipAddress ? substr($ipAddress, 0, 45) : null,
'user_agent' => $userAgent ? substr($userAgent, 0, 512) : null,
'referer' => $referer ? substr($referer, 0, 512) : null,
'status' => 'unresolved',
'createdate' => date('Y-m-d H:i:s'),
];
$logPath = __DIR__ . '/errors.json';
$entries = [];
if (file_exists($logPath)) {
$raw = file_get_contents($logPath);
$decoded = json_decode($raw, true);
if (is_array($decoded)) {
$entries = $decoded;
}
}
array_unshift($entries, $entry);
if (count($entries) > 1000) {
$entries = array_slice($entries, 0, 1000);
}
file_put_contents($logPath, json_encode($entries, JSON_PRETTY_PRINT), LOCK_EX);
} catch (\Throwable $e) {
// Silently fail — never let error logging crash the app
error_log('ErrorHandler::persistError failed: ' . $e->getMessage());
}
}
// ─── File logging ─────────────────────────────────────────────────────────
private static function logError($errno, $errstr, $errfile, $errline) {
$message = date('[Y-m-d H:i:s]') . " Error: [$errno] $errstr in $errfile on line $errline\n";
error_log($message, 3, __DIR__ . '/app_errors.log');
}
// ─── Display (debug mode only) ────────────────────────────────────────────
private static function displayError($errno, $errstr, $errfile, $errline, $trace = null) {
$errorType = self::$errorTypeLabels[$errno] ?? 'Unknown Error';
if (defined('DEBUG') && DEBUG) {
echo "<div style='background-color:#f8d7da;color:#721c24;padding:10px;margin:10px;border:1px solid #f5c6cb;border-radius:5px;'>";
echo "<h1 style='color:#721c24;'>" . htmlspecialchars($errorType) . " Occurred</h1>";
echo "<p><strong>Message:</strong> " . htmlspecialchars($errstr) . "</p>";
echo "<p><strong>File:</strong> " . htmlspecialchars($errfile) . "</p>";
echo "<p><strong>Line:</strong> " . htmlspecialchars((string)$errline) . "</p>";
if ($trace) {
echo "<h2>Stack Trace:</h2>";
echo "<pre>" . htmlspecialchars($trace) . "</pre>";
}
echo "<h2>Request Details:</h2><pre>";
echo "URL: " . htmlspecialchars($_SERVER['REQUEST_URI'] ?? '') . "\n";
echo "Method: " . htmlspecialchars($_SERVER['REQUEST_METHOD'] ?? '') . "\n";
echo "Time: " . date('Y-m-d H:i:s') . "\n";
echo "IP: " . htmlspecialchars($_SERVER['REMOTE_ADDR'] ?? '') . "\n";
echo "</pre></div>";
}
}
}