docs: consolidate documentation into organized structure

- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -94,7 +94,12 @@ final readonly class ErrorHandler
return $formHandler->handle($exception, $context);
}
error_log("ErrorHandler: ValidationFormHandler NOT found, using fallback");
// Log missing ValidationFormHandler using structured logging
$errorContext = $this->createErrorHandlerContext(
new \RuntimeException('ValidationFormHandler not found in container'),
$context
);
$this->logger->logErrorHandlerContext($errorContext);
// Fallback: create basic redirect response
$refererUrl = $context?->request?->server?->getRefererUri() ?? '/';
@@ -158,11 +163,28 @@ final readonly class ErrorHandler
if ($exception instanceof FrameworkException) {
$exceptionContext = $exception->getContext();
// Ensure framework exceptions also have the original exception for stack traces
if (! isset($exceptionContext->data['original_exception'])) {
// Für DatabaseException: Verwende die previous exception für File/Line
if ($exception instanceof \App\Framework\Database\Exception\DatabaseException && $exception->getPrevious()) {
$previous = $exception->getPrevious();
$exceptionContext = $exceptionContext->withData([
'original_exception' => $exception,
'original_exception' => $previous,
'exception_file' => $previous->getFile(),
'exception_line' => $previous->getLine(),
'exception_message' => $exception->getMessage(), // Keep DatabaseException message
]);
} else {
// Ensure framework exceptions also have the original exception for stack traces
if (! isset($exceptionContext->data['original_exception'])) {
$exceptionContext = $exceptionContext->withData([
'original_exception' => $exception,
'exception_file' => $exception->getFile(),
'exception_line' => $exception->getLine(),
]);
}
}
} else {
// For non-framework exceptions, add the actual exception message and the original exception

View File

@@ -7,6 +7,7 @@ namespace App\Framework\ErrorHandling;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Logging\Logger;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\ValueObjects\LogContext;
/**
* Logger für Fehler und Exceptions - nutzt Framework Logger
@@ -70,14 +71,20 @@ final readonly class ErrorLogger
$this->extractExceptionMessage($context)
);
// Debug: Log the actual exception class
if (isset($context->metadata['exception_class'])) {
error_log("DEBUG: Exception class: " . $context->metadata['exception_class']);
}
if ($this->logger !== null) {
$logLevel = $this->determineLogLevel($context);
$this->logger->log($logLevel, $message, $logData);
// Erstelle LogContext mit strukturierten Daten
$logContext = LogContext::withData($logData)
->addTags('error_handler', 'framework');
// Füge Metadaten-Tags hinzu
if ($context->exception->metadata['security_event'] ?? false) {
$logContext = $logContext->addTags('security');
}
$this->logger->log($logLevel, $message, $logContext);
} else {
// Fallback auf error_log
$this->logHandlerContextToErrorLog($context);
@@ -85,15 +92,20 @@ final readonly class ErrorLogger
}
/**
* Loggt Security Events im OWASP-Format
* Loggt Security Events im OWASP-Format mit strukturiertem Logging
*/
private function logSecurityEvent(ErrorHandlerContext $context): void
{
$securityLog = $context->toSecurityEventFormat($_ENV['APP_NAME'] ?? 'app');
if ($this->logger !== null) {
$this->logger->log(LogLevel::WARNING, 'Security Event', $securityLog);
// Verwende LogContext für strukturiertes Security Event Logging
$logContext = LogContext::withData($securityLog)
->addTags('security_event', 'owasp');
$this->logger->log(LogLevel::WARNING, 'Security Event Detected', $logContext);
} else {
// Fallback nur als letzte Option
error_log('SECURITY_EVENT: ' . json_encode($securityLog, JSON_UNESCAPED_SLASHES));
}
}
@@ -110,8 +122,6 @@ final readonly class ErrorLogger
return $exceptionData['exception_message'];
}
// Debug: Log what data we have
error_log("DEBUG: Exception data: " . json_encode($exceptionData));
// Fallback: Operation und Component verwenden
$operation = $context->exception->operation ?? 'unknown_operation';

View File

@@ -4,9 +4,10 @@ declare(strict_types=1);
namespace App\Framework\ErrorHandling\View;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\ErrorHandling\ErrorContext;
use App\Framework\ErrorHandling\StackTrace;
use App\Framework\ErrorHandling\TraceItem;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Meta\MetaData;
use App\Framework\Meta\OpenGraphTypeWebsite;
@@ -23,7 +24,7 @@ final readonly class ErrorTemplateRenderer implements ErrorViewRendererInterface
{
public function __construct(
private TemplateRenderer $renderer,
private string $debugTemplate = 'debug',
private string $debugTemplate = 'enhanced-debug',
private string $productionTemplate = 'production'
) {
}
@@ -37,9 +38,15 @@ final readonly class ErrorTemplateRenderer implements ErrorViewRendererInterface
try {
// Sichere Datenaufbereitung für ErrorHandlerContext
$errorFile = $this->extractErrorFile($context);
$errorLine = $this->extractErrorLine($context);
$safeData = [
'errorClass' => $context->metadata['exception_class'] ?? 'Unknown',
'errorClass' => $this->extractExceptionClass($context),
'errorMessage' => $this->extractErrorMessage($context),
'errorFile' => $errorFile,
'errorLine' => $errorLine,
'errorCode' => $context->metadata['http_status'] ?? 500,
'error' => [
'message' => $this->extractErrorMessage($context),
'operation' => $context->exception->operation ?? 'unknown',
@@ -49,11 +56,34 @@ final readonly class ErrorTemplateRenderer implements ErrorViewRendererInterface
'requestId' => $context->request->requestId ?? 'NO-REQUEST-ID',
'timestamp' => date('c'),
'level' => $context->metadata['error_level'] ?? 'ERROR',
'memory' => $context->system->memoryUsage ?? 0,
'memory' => is_int($context->system->memoryUsage)
? Byte::fromBytes($context->system->memoryUsage)->toHumanReadable()
: ($context->system->memoryUsage ?? '0 B'),
'httpStatus' => $context->metadata['http_status'] ?? 500,
'clientIp' => $context->request->clientIp,
'requestUri' => $context->request->requestUri,
'userAgent' => (string) $context->request->userAgent,
'clientIp' => $context->request->clientIp ?? 'Unknown',
'requestUri' => $context->request->requestUri ?? '/',
'requestMethod' => $context->request->requestMethod ?? 'GET',
'userAgent' => (string) ($context->request->userAgent ?? 'Unknown'),
'traceCount' => 0, // Will be updated below if trace is available
// Environment information
'environment' => $_ENV['APP_ENV'] ?? 'development',
'debugMode' => $isDebug ? 'Enabled' : 'Disabled',
'phpVersion' => PHP_VERSION,
'frameworkVersion' => '1.0.0-dev',
// Performance information
'executionTime' => $context->system->executionTime !== null
? Duration::fromSeconds($context->system->executionTime)->toHumanReadable()
: 'N/A',
'memoryLimit' => ini_get('memory_limit') ?: 'Unknown',
// Server information
'serverSoftware' => $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown',
'documentRoot' => $_SERVER['DOCUMENT_ROOT'] ?? '/var/www/html',
'serverName' => $_SERVER['SERVER_NAME'] ?? 'localhost',
// Immer Standard-Werte setzen
'dependencyInfo' => '',
'code' => RawHtml::from('<div style="padding: 20px; background: #f8f9fa; border-radius: 4px; color: #6c757d;">Code-Vorschau nicht verfügbar</div>'),
'trace' => [],
'traceCount' => 0,
];
// Security Event spezifische Daten
@@ -98,6 +128,9 @@ final readonly class ErrorTemplateRenderer implements ErrorViewRendererInterface
'</div>';
$safeData['dependencyInfo'] = RawHtml::from($dependencyHtml);
} else {
// Standard-Fallback wenn keine Dependency-Info verfügbar
$safeData['dependencyInfo'] = '';
}
// Code-Highlighter für Debug-Template
@@ -114,39 +147,84 @@ final readonly class ErrorTemplateRenderer implements ErrorViewRendererInterface
// Stack Trace für Debug-Template
// Prüfe zuerst, ob eine ursprüngliche Exception verfügbar ist
// Stack Trace mit Index hinzufügen
if (isset($context->exception->data['original_exception']) && $context->exception->data['original_exception'] instanceof \Throwable) {
// Verwende die ursprüngliche Exception für den Stack Trace
$originalException = $context->exception->data['original_exception'];
$stackTrace = new StackTrace($originalException);
$safeData['trace'] = $stackTrace->getItems();
$traceItems = $stackTrace->getItems();
// Index zu jedem Item hinzufügen
$indexedTraceItems = [];
foreach ($traceItems as $index => $item) {
$indexedTraceItems[] = [
'file' => $item->getRelativeFile(),
'line' => $item->line,
'function' => $item->class ? $item->class . $item->type . $item->function : $item->function,
'class' => $item->class,
'index' => $index,
'isOrigin' => $item->isExceptionOrigin,
];
}
$safeData['trace'] = $indexedTraceItems;
$safeData['traceCount'] = count($traceItems);
} elseif (isset($context->exception->data['previous_exception']) && $context->exception->data['previous_exception'] instanceof \Throwable) {
// Alternative: Prüfe auf previous_exception
$stackTrace = new StackTrace($context->exception->data['previous_exception']);
$safeData['trace'] = $stackTrace->getItems();
$traceItems = $stackTrace->getItems();
// Index zu jedem Item hinzufügen
$indexedTraceItems = [];
foreach ($traceItems as $index => $item) {
$indexedTraceItems[] = [
'file' => $item->getRelativeFile(),
'line' => $item->line,
'function' => $item->class ? $item->class . $item->type . $item->function : $item->function,
'class' => $item->class,
'index' => $index,
'isOrigin' => $item->isExceptionOrigin,
];
}
$safeData['trace'] = $indexedTraceItems;
$safeData['traceCount'] = count($traceItems);
} else {
// Fallback: Erstelle minimalen Trace aus verfügbaren Daten
$safeData['trace'] = [];
// Suche nach verfügbaren Trace-Informationen
$file = $context->exception->data['exception_file'] ?? null;
$line = $context->exception->data['exception_line'] ?? null;
if ($file && $line) {
$safeData['trace'] = [
new TraceItem(
file: $file,
line: (int)$line,
function: null,
class: null,
type: null,
args: [],
isExceptionOrigin: true
),
];
$safeData['trace'] = [[
'file' => str_replace(dirname(__DIR__, 4), '', $file),
'line' => (int)$line,
'function' => 'Unknown',
'class' => null,
'index' => 0,
'isOrigin' => true,
]];
$safeData['traceCount'] = 1;
} else {
$safeData['trace'] = [];
$safeData['traceCount'] = 0;
}
}
}
// Sicherstellen, dass alle Template-Variablen definiert sind
if (! isset($safeData['dependencyInfo'])) {
$safeData['dependencyInfo'] = '';
}
if (! isset($safeData['code'])) {
$safeData['code'] = RawHtml::from('<div style="padding: 20px; background: #f8f9fa; border-radius: 4px; color: #6c757d;">Code-Vorschau nicht verfügbar</div>');
}
if (! isset($safeData['trace'])) {
$safeData['trace'] = [];
}
if (! isset($safeData['traceCount'])) {
$safeData['traceCount'] = 0;
}
$renderContext = new RenderContext(
template: $template,
metaData: new MetaData('', '', new OpenGraphTypeWebsite()),
@@ -321,6 +399,16 @@ final readonly class ErrorTemplateRenderer implements ErrorViewRendererInterface
return $context->exception->data['user_message'];
}
// Versuche die originale Exception zu verwenden
if (isset($context->exception->data['original_exception']) && $context->exception->data['original_exception'] instanceof \Throwable) {
return $context->exception->data['original_exception']->getMessage();
}
// Versuche previous_exception
if (isset($context->exception->data['previous_exception']) && $context->exception->data['previous_exception'] instanceof \Throwable) {
return $context->exception->data['previous_exception']->getMessage();
}
// Fallback: Operation und Component verwenden
$operation = $context->exception->operation ?? 'unknown_operation';
$component = $context->exception->component ?? 'Application';
@@ -328,6 +416,84 @@ final readonly class ErrorTemplateRenderer implements ErrorViewRendererInterface
return "Error in {$component} during {$operation}";
}
/**
* Extrahiert die Exception-Klasse
*/
private function extractExceptionClass(ErrorHandlerContext $context): string
{
// Aus Metadata extrahieren
if (isset($context->metadata['exception_class'])) {
return $context->metadata['exception_class'];
}
// Versuche die originale Exception zu verwenden
if (isset($context->exception->data['original_exception']) && $context->exception->data['original_exception'] instanceof \Throwable) {
$originalException = $context->exception->data['original_exception'];
// Für FrameworkException: Zeige die FrameworkException-Klasse, nicht die PDO-Exception
// da DatabaseException die richtige Exception-Klasse ist
return get_class($originalException);
}
// Versuche previous_exception
if (isset($context->exception->data['previous_exception']) && $context->exception->data['previous_exception'] instanceof \Throwable) {
return get_class($context->exception->data['previous_exception']);
}
return 'Unknown';
}
/**
* Extrahiert die Fehlerdatei
*/
private function extractErrorFile(ErrorHandlerContext $context): string
{
// 1. Priorität: Explizit gesetzte exception_file (für DatabaseException Fix)
if (isset($context->exception->data['exception_file']) && ! empty($context->exception->data['exception_file'])) {
return $context->exception->data['exception_file'];
}
// 2. Priorität: Versuche die originale Exception zu verwenden
if (isset($context->exception->data['original_exception']) && $context->exception->data['original_exception'] instanceof \Throwable) {
$originalException = $context->exception->data['original_exception'];
return $originalException->getFile();
}
// 3. Priorität: Versuche previous_exception
if (isset($context->exception->data['previous_exception']) && $context->exception->data['previous_exception'] instanceof \Throwable) {
return $context->exception->data['previous_exception']->getFile();
}
return 'Unknown';
}
/**
* Extrahiert die Fehlerzeile
*/
private function extractErrorLine(ErrorHandlerContext $context): int
{
// 1. Priorität: Explizit gesetzte exception_line (für DatabaseException Fix)
if (isset($context->exception->data['exception_line']) && is_numeric($context->exception->data['exception_line'])) {
return (int) $context->exception->data['exception_line'];
}
// 2. Priorität: Versuche die originale Exception zu verwenden
if (isset($context->exception->data['original_exception']) && $context->exception->data['original_exception'] instanceof \Throwable) {
$originalException = $context->exception->data['original_exception'];
return $originalException->getLine();
}
// 3. Priorität: Versuche previous_exception
if (isset($context->exception->data['previous_exception']) && $context->exception->data['previous_exception'] instanceof \Throwable) {
return $context->exception->data['previous_exception']->getLine();
}
return 0;
}
/**
* Erstellt eine einfache Fehlerseite als Fallback für ErrorHandlerContext
*/

View File

@@ -0,0 +1,720 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🐛 Debug: {{ errorClass }}</title>
<style>
:root {
--primary: #1e40af;
--danger: #dc2626;
--warning: #d97706;
--success: #059669;
--info: #0284c7;
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-300: #d1d5db;
--gray-400: #9ca3af;
--gray-500: #6b7280;
--gray-600: #4b5563;
--gray-700: #374151;
--gray-800: #1f2937;
--gray-900: #111827;
}
@media (prefers-color-scheme: dark) {
:root {
--gray-50: #111827;
--gray-100: #1f2937;
--gray-200: #374151;
--gray-300: #4b5563;
--gray-400: #6b7280;
--gray-500: #9ca3af;
--gray-600: #d1d5db;
--gray-700: #e5e7eb;
--gray-800: #f3f4f6;
--gray-900: #f9fafb;
}
}
* {
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Inter", sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--gray-900);
background: var(--gray-50);
margin: 0;
padding: 0;
}
/* Layout */
.debug-layout {
display: grid;
grid-template-columns: 320px 1fr;
min-height: 100vh;
gap: 0;
}
@media (max-width: 1024px) {
.debug-layout {
grid-template-columns: 1fr;
}
.debug-sidebar {
order: 2;
border-top: 1px solid var(--gray-200);
border-right: none;
}
}
/* Sidebar */
.debug-sidebar {
background: var(--gray-100);
border-right: 1px solid var(--gray-200);
overflow-y: auto;
max-height: 100vh;
}
.debug-nav {
list-style: none;
margin: 0;
padding: 0;
}
.debug-nav-item {
border-bottom: 1px solid var(--gray-200);
}
.debug-nav-link {
display: block;
padding: 12px 16px;
color: var(--gray-700);
text-decoration: none;
font-weight: 500;
transition: all 0.15s;
position: relative;
}
.debug-nav-link:hover {
background: var(--gray-200);
color: var(--primary);
}
.debug-nav-link.active {
background: var(--primary);
color: white;
}
.debug-nav-link .count {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: var(--gray-300);
color: var(--gray-700);
font-size: 11px;
padding: 2px 6px;
border-radius: 10px;
}
.debug-nav-link.active .count {
background: rgba(255,255,255,0.2);
color: white;
}
/* Main Content */
.debug-main {
background: white;
overflow-y: auto;
max-height: 100vh;
}
/* Header */
.debug-header {
background: linear-gradient(135deg, var(--danger) 0%, #dc2626 100%);
color: white;
padding: 24px;
position: sticky;
top: 0;
z-index: 10;
border-bottom: 1px solid var(--gray-200);
}
.debug-header h1 {
margin: 0 0 8px 0;
font-size: 24px;
font-weight: 700;
}
.debug-header p {
margin: 0;
opacity: 0.9;
font-size: 16px;
}
.debug-meta {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 16px;
}
.debug-badge {
background: rgba(255,255,255,0.2);
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
/* Content Sections */
.debug-content {
padding: 24px;
}
.debug-section {
display: none;
margin-bottom: 32px;
}
.debug-section.active {
display: block;
}
.debug-section h2 {
margin: 0 0 16px 0;
font-size: 18px;
font-weight: 600;
color: var(--gray-800);
}
/* Cards */
.debug-card {
background: white;
border: 1px solid var(--gray-200);
border-radius: 8px;
margin-bottom: 16px;
overflow: hidden;
}
.debug-card-header {
background: var(--gray-50);
border-bottom: 1px solid var(--gray-200);
padding: 12px 16px;
font-weight: 600;
color: var(--gray-800);
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.debug-card-header:hover {
background: var(--gray-100);
}
.debug-card-content {
padding: 16px;
}
.debug-card.collapsed .debug-card-content {
display: none;
}
.debug-card-header::after {
content: '';
font-weight: bold;
transform: rotate(0deg);
transition: transform 0.2s;
}
.debug-card.collapsed .debug-card-header::after {
transform: rotate(90deg);
content: '+';
}
/* Code */
.debug-code {
background: var(--gray-900);
color: var(--gray-100);
padding: 16px;
border-radius: 6px;
font-family: 'SF Mono', Monaco, 'Inconsolata', 'Roboto Mono', monospace;
font-size: 13px;
line-height: 1.4;
overflow-x: auto;
position: relative;
}
.debug-code-line {
position: relative;
padding-left: 60px;
}
.debug-code-line-number {
position: absolute;
left: 0;
width: 50px;
color: var(--gray-500);
text-align: right;
user-select: none;
}
.debug-code-line.highlight {
background: rgba(239, 68, 68, 0.1);
border-left: 3px solid var(--danger);
}
/* Stack Trace */
.debug-trace-item {
border-bottom: 1px solid var(--gray-200);
transition: all 0.15s;
}
.debug-trace-item:last-child {
border-bottom: none;
}
.debug-trace-item:hover {
background: var(--gray-50);
}
.debug-trace-item.origin {
background: rgba(239, 68, 68, 0.05);
border-left: 4px solid var(--danger);
}
.debug-trace-header {
padding: 12px 16px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.debug-trace-location {
font-family: monospace;
font-size: 13px;
color: var(--primary);
font-weight: 600;
}
.debug-trace-call {
font-family: monospace;
font-size: 12px;
color: var(--gray-600);
margin-top: 4px;
}
/* Tables */
.debug-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.debug-table th,
.debug-table td {
padding: 8px 12px;
text-align: left;
border-bottom: 1px solid var(--gray-200);
}
.debug-table th {
background: var(--gray-50);
font-weight: 600;
color: var(--gray-700);
}
.debug-table td {
font-family: monospace;
font-size: 12px;
}
/* Search */
.debug-search {
position: sticky;
top: 0;
background: white;
padding: 16px;
border-bottom: 1px solid var(--gray-200);
z-index: 5;
}
.debug-search input {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--gray-300);
border-radius: 6px;
font-size: 14px;
}
.debug-search input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* Utilities */
.text-sm { font-size: 12px; }
.text-xs { font-size: 11px; }
.font-mono { font-family: monospace; }
.font-semibold { font-weight: 600; }
.text-gray-500 { color: var(--gray-500); }
.text-gray-600 { color: var(--gray-600); }
.text-red-600 { color: var(--danger); }
.bg-red-50 { background: rgba(239, 68, 68, 0.05); }
.hidden { display: none; }
/* Mobile Responsive */
@media (max-width: 768px) {
.debug-layout {
grid-template-columns: 1fr;
}
.debug-header {
padding: 16px;
}
.debug-content {
padding: 16px;
}
.debug-meta {
flex-direction: column;
gap: 8px;
}
}
</style>
</head>
<body>
<div class="debug-layout">
<!-- Sidebar -->
<nav class="debug-sidebar">
<ul class="debug-nav">
<li class="debug-nav-item">
<a href="#overview" class="debug-nav-link active" data-section="overview">
Overview
</a>
</li>
<li class="debug-nav-item">
<a href="#stacktrace" class="debug-nav-link" data-section="stacktrace">
Stack Trace
<span class="count">{{ traceCount }}</span>
</a>
</li>
<li class="debug-nav-item">
<a href="#request" class="debug-nav-link" data-section="request">
Request
</a>
</li>
<li class="debug-nav-item">
<a href="#context" class="debug-nav-link" data-section="context">
Context
</a>
</li>
<li class="debug-nav-item">
<a href="#performance" class="debug-nav-link" data-section="performance">
Performance
</a>
</li>
<li class="debug-nav-item">
<a href="#environment" class="debug-nav-link" data-section="environment">
Environment
</a>
</li>
</ul>
</nav>
<!-- Main Content -->
<main class="debug-main">
<!-- Header -->
<header class="debug-header">
<h1>{{ errorClass }}</h1>
<p>{{ errorMessage }}</p>
<div class="debug-meta">
<span class="debug-badge">Request ID: {{ requestId }}</span>
<span class="debug-badge">{{ timestamp }}</span>
<span class="debug-badge">Memory: {{ memory }}</span>
<span class="debug-badge">{{ level }}</span>
</div>
</header>
<div class="debug-content">
<!-- Overview Section -->
<section id="overview" class="debug-section active">
<h2>Exception Overview</h2>
<div class="debug-card">
<div class="debug-card-header">
Error Details
</div>
<div class="debug-card-content">
<table class="debug-table">
<tr>
<th>Exception</th>
<td>{{ errorClass }}</td>
</tr>
<tr>
<th>Message</th>
<td>{{ errorMessage }}</td>
</tr>
<tr>
<th>File</th>
<td class="font-mono">{{ errorFile }}</td>
</tr>
<tr>
<th>Line</th>
<td>{{ errorLine }}</td>
</tr>
<tr>
<th>Code</th>
<td>{{ errorCode }}</td>
</tr>
</table>
</div>
</div>
{{ code }}
{{ dependencyInfo }}
</section>
<!-- Stack Trace Section -->
<section id="stacktrace" class="debug-section">
<div class="debug-search">
<input type="text" id="trace-search" placeholder="Search stack trace...">
</div>
<h2>Stack Trace</h2>
<div class="debug-card">
<div class="debug-card-content" style="padding: 0;">
<div style="padding: 16px;">
<p>Trace Count: {{ traceCount }}</p>
<for var="item" in="trace">
<div class="debug-trace-item {{ item.isOrigin }}" data-searchable="{{ item.file }} {{ item.class }}">
<div class="debug-trace-header">
<div>
<div class="debug-trace-location">
{{ item.file }}:{{ item.line }}
</div>
<div class="debug-trace-call">
{{ item.function }}
</div>
</div>
<span class="text-xs text-gray-500">#{{ item.index }}</span>
</div>
</div>
</for>
</div>
</div>
</div>
</section>
<!-- Request Section -->
<section id="request" class="debug-section">
<h2>Request Information</h2>
<div class="debug-card">
<div class="debug-card-header">
HTTP Request
</div>
<div class="debug-card-content">
<table class="debug-table">
<tr>
<th>Method</th>
<td>{{ requestMethod }}</td>
</tr>
<tr>
<th>URI</th>
<td class="font-mono">{{ requestUri }}</td>
</tr>
<tr>
<th>User Agent</th>
<td class="text-sm">{{ userAgent }}</td>
</tr>
<tr>
<th>IP Address</th>
<td>{{ clientIp }}</td>
</tr>
</table>
</div>
</div>
<div class="debug-card collapsed">
<div class="debug-card-header">
Request Headers
</div>
<div class="debug-card-content">
<table class="debug-table">
<tr><th>Accept</th><td>application/json, text/html</td></tr>
<tr><th>Accept-Language</th><td>de-DE,de;q=0.9,en;q=0.8</td></tr>
<tr><th>Cache-Control</th><td>no-cache</td></tr>
</table>
</div>
</div>
<div class="debug-card collapsed">
<div class="debug-card-header">
Request Parameters
</div>
<div class="debug-card-content">
<p class="text-sm text-gray-500">No parameters found</p>
</div>
</div>
</section>
<!-- Context Section -->
<section id="context" class="debug-section">
<h2>Application Context</h2>
<div class="debug-card">
<div class="debug-card-header">
Application State
</div>
<div class="debug-card-content">
<table class="debug-table">
<tr>
<th>Environment</th>
<td>{{ environment }}</td>
</tr>
<tr>
<th>Debug Mode</th>
<td>{{ debugMode }}</td>
</tr>
<tr>
<th>PHP Version</th>
<td>{{ phpVersion }}</td>
</tr>
<tr>
<th>Framework Version</th>
<td>{{ frameworkVersion }}</td>
</tr>
</table>
</div>
</div>
</section>
<!-- Performance Section -->
<section id="performance" class="debug-section">
<h2>Performance Metrics</h2>
<div class="debug-card">
<div class="debug-card-header">
Memory & Timing
</div>
<div class="debug-card-content">
<table class="debug-table">
<tr>
<th>Peak Memory</th>
<td>{{ memory }}</td>
</tr>
<tr>
<th>Execution Time</th>
<td>{{ executionTime }}</td>
</tr>
<tr>
<th>Memory Limit</th>
<td>{{ memoryLimit }}</td>
</tr>
</table>
</div>
</div>
</section>
<!-- Environment Section -->
<section id="environment" class="debug-section">
<h2>Environment</h2>
<div class="debug-card collapsed">
<div class="debug-card-header">
Server Information
</div>
<div class="debug-card-content">
<table class="debug-table">
<tr><th>Server Software</th><td>{{ serverSoftware }}</td></tr>
<tr><th>Document Root</th><td class="font-mono">{{ documentRoot }}</td></tr>
<tr><th>Server Name</th><td>{{ serverName }}</td></tr>
</table>
</div>
</div>
</section>
</div>
</main>
</div>
<script>
// Navigation
document.querySelectorAll('.debug-nav-link').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
// Update active nav
document.querySelectorAll('.debug-nav-link').forEach(l => l.classList.remove('active'));
link.classList.add('active');
// Show section
const sectionId = link.dataset.section;
document.querySelectorAll('.debug-section').forEach(s => s.classList.remove('active'));
document.getElementById(sectionId).classList.add('active');
});
});
// Collapsible cards
document.querySelectorAll('.debug-card-header').forEach(header => {
header.addEventListener('click', () => {
header.parentElement.classList.toggle('collapsed');
});
});
// Search functionality
const searchInput = document.getElementById('trace-search');
if (searchInput) {
searchInput.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
document.querySelectorAll('.debug-trace-item').forEach(item => {
const searchText = item.dataset.searchable?.toLowerCase() || '';
item.style.display = searchText.includes(query) ? 'block' : 'none';
});
});
}
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.ctrlKey || e.metaKey) {
switch(e.key) {
case '1':
e.preventDefault();
document.querySelector('[data-section="overview"]').click();
break;
case '2':
e.preventDefault();
document.querySelector('[data-section="stacktrace"]').click();
break;
case '3':
e.preventDefault();
document.querySelector('[data-section="request"]').click();
break;
case 'f':
e.preventDefault();
searchInput?.focus();
break;
}
}
});
// Auto-expand first trace item if it's the origin
document.addEventListener('DOMContentLoaded', () => {
const originTrace = document.querySelector('.debug-trace-item.origin');
if (originTrace) {
originTrace.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
</script>
</body>
</html>

View File

@@ -97,7 +97,7 @@
</div>
<div class="error-meta">
Request ID: $requestId
Request ID: {{requestId}}
</div>
</div>
</div>