feat(Production): Complete production deployment infrastructure

- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -1,48 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Framework\ErrorHandling;
use App\Framework\Http\RequestId;
final readonly class ErrorContext
{
public StackTrace $stackTrace;
public function __construct(
public \Throwable $exception,
public ErrorLevel $level,
public RequestId $requestId,
public array $additionalData = []
) {
$this->stackTrace = new StackTrace($exception);
}
/**
* Gibt eine HTML-formatierte Version der Fehlermeldung zurück
*/
public function getFormattedMessage(): string
{
return nl2br(htmlspecialchars($this->exception->getMessage(), ENT_QUOTES, 'UTF-8'));
}
/**
* Gibt die Fehlerklasse zurück (ohne Namespace)
*/
public function getErrorClass(): string
{
$class = get_class($this->exception);
$parts = explode('\\', $class);
return end($parts);
}
/**
* Gibt den vollständigen Klassennamen zurück
*/
public function getFullErrorClass(): string
{
return get_class($this->exception);
}
}

View File

@@ -7,9 +7,12 @@ namespace App\Framework\ErrorHandling;
use App\Framework\Config\Environment;
use App\Framework\Config\EnvKey;
use App\Framework\DI\Container;
use App\Framework\ErrorAggregation\ErrorAggregatorInterface;
use App\Framework\ErrorHandling\View\ApiErrorRenderer;
use App\Framework\ErrorHandling\View\ErrorResponseFactory;
use App\Framework\ErrorHandling\View\ErrorTemplateRenderer;
use App\Framework\ErrorReporting\ErrorReporterInterface;
use App\Framework\Exception\Core\ErrorSeverity;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
@@ -38,12 +41,14 @@ final readonly class ErrorHandler
private bool $isDebugMode;
public function __construct(
private ResponseEmitter $emitter,
private Container $container,
private RequestIdGenerator $requestIdGenerator,
?Logger $logger = null,
?bool $isDebugMode = null,
?SecurityEventHandler $securityHandler = null
private ResponseEmitter $emitter,
private Container $container,
private RequestIdGenerator $requestIdGenerator,
private ErrorAggregatorInterface $errorAggregator,
private ErrorReporterInterface $errorReporter,
?Logger $logger = null,
?bool $isDebugMode = null,
?SecurityEventHandler $securityHandler = null
) {
$this->isDebugMode = $isDebugMode ?? $this->getDebugModeFromEnvironment();
$this->logger = new ErrorLogger($logger);
@@ -71,6 +76,12 @@ final readonly class ErrorHandler
// Standard Error Logging mit ErrorHandlerContext
$this->logger->logErrorHandlerContext($errorHandlerContext);
// Error Aggregation - automatische Pattern-Erkennung und Alerting
$this->errorAggregator->processError($errorHandlerContext);
// Error Reporting - zentrale Fehlersammlung mit Context Enrichment
$this->errorReporter->reportThrowable($e, 'error', $errorHandlerContext->toArray());
// Security Event Logging delegieren
$this->securityHandler->handleIfSecurityException($e, $context);
@@ -113,6 +124,12 @@ final readonly class ErrorHandler
// Logge den Fehler mit ErrorHandlerContext
$this->logger->logErrorHandlerContext($errorHandlerContext);
// Error Aggregation - automatische Pattern-Erkennung und Alerting
$this->errorAggregator->processError($errorHandlerContext);
// Error Reporting - zentrale Fehlersammlung mit Context Enrichment
$this->errorReporter->reportThrowable($e, 'error', $errorHandlerContext->toArray());
// Security Event Logging delegieren
$this->securityHandler->handleIfSecurityException($e);
@@ -244,11 +261,79 @@ final readonly class ErrorHandler
{
$metadata = [
'exception_class' => get_class($exception),
'error_level' => $this->determineErrorLevel($exception)->name,
'error_level' => $this->determineErrorSeverity($exception)->name,
];
// HTTP-Status-Code basierend auf Exception-Typ
$metadata['http_status'] = match (true) {
// Enhanced: Add ErrorCode metadata if FrameworkException
if ($exception instanceof FrameworkException) {
$errorCode = $exception->getErrorCode();
if ($errorCode !== null) {
$metadata['error_code'] = $errorCode->getValue();
$metadata['error_category'] = $errorCode->getCategory();
$metadata['error_severity'] = $errorCode->getSeverity()->value;
$metadata['is_recoverable'] = $errorCode->isRecoverable();
// Add recovery hint for debug mode or API responses
if ($this->isDebugMode) {
$metadata['recovery_hint'] = $errorCode->getRecoveryHint();
}
// Add Retry-After header if applicable
$retryAfter = $errorCode->getRetryAfterSeconds();
if ($retryAfter !== null) {
$metadata['additional_headers'] = array_merge(
$metadata['additional_headers'] ?? [],
['Retry-After' => (string) $retryAfter]
);
}
}
}
// HTTP-Status-Code: ErrorCode-based first, then fallback to exception-type mapping
$metadata['http_status'] = $this->determineHttpStatus($exception);
// Zusätzliche Header für spezielle Exceptions
if ($exception instanceof \App\Framework\Exception\Http\RateLimitExceededException) {
$metadata['additional_headers'] = array_merge(
$metadata['additional_headers'] ?? [],
$exception->getRateLimitHeaders()
);
}
if ($exception instanceof \App\Framework\Exception\Http\InvalidContentTypeException) {
$metadata['additional_headers'] = array_merge(
$metadata['additional_headers'] ?? [],
$exception->getResponseHeaders()
);
}
return $metadata;
}
/**
* Determine HTTP status code with ErrorCode-based prioritization
*/
private function determineHttpStatus(Throwable $exception): int
{
// Priority 1: ErrorCode-based status mapping for FrameworkException
if ($exception instanceof FrameworkException) {
$errorCode = $exception->getErrorCode();
if ($errorCode !== null) {
$category = $errorCode->getCategory();
return match ($category) {
'AUTH' => 401, // Authentication errors
'AUTHZ' => 403, // Authorization errors
'VAL' => 400, // Validation errors
'HTTP' => $this->mapHttpCategoryToStatus($exception),
'DB', 'QUEUE', 'CACHE', 'FILE' => 500, // Infrastructure errors
default => 500,
};
}
}
// Priority 2: Legacy exception-type mapping
return match (true) {
$exception instanceof \App\Framework\Exception\Authentication\InvalidCredentialsException,
$exception instanceof \App\Framework\Exception\Authentication\TokenExpiredException,
$exception instanceof \App\Framework\Exception\Authentication\SessionTimeoutException => 401,
@@ -268,57 +353,72 @@ final readonly class ErrorHandler
default => 500,
};
// Zusätzliche Header für spezielle Exceptions
if ($exception instanceof \App\Framework\Exception\Http\RateLimitExceededException) {
$metadata['additional_headers'] = $exception->getRateLimitHeaders();
}
if ($exception instanceof \App\Framework\Exception\Http\InvalidContentTypeException) {
$metadata['additional_headers'] = $exception->getResponseHeaders();
}
return $metadata;
}
private function determineErrorLevel(Throwable $exception): ErrorLevel
/**
* Map HTTP category ErrorCodes to specific status codes
*/
private function mapHttpCategoryToStatus(FrameworkException $exception): int
{
$errorCode = $exception->getErrorCode();
// Use numeric code for fine-grained HTTP status mapping
return match ($errorCode->getNumericCode()) {
404 => 404, // HTTP_NOT_FOUND
429 => 429, // HTTP_RATE_LIMIT_EXCEEDED
413 => 413, // HTTP_OVERSIZED_REQUEST
415 => 415, // HTTP_INVALID_CONTENT_TYPE
default => 500,
};
}
private function determineErrorSeverity(Throwable $exception): ErrorSeverity
{
// Priority 1: Use ErrorCode.getSeverity() for FrameworkException
if ($exception instanceof FrameworkException) {
$errorCode = $exception->getErrorCode();
if ($errorCode !== null) {
return $errorCode->getSeverity();
}
}
// Priority 2: Legacy exception-type mapping
return match(true) {
$exception instanceof \Error => ErrorLevel::CRITICAL,
$exception instanceof \ErrorException => $this->determineErrorExceptionLevel($exception),
$exception instanceof SecurityException => $this->determineSecurityErrorLevel($exception),
$exception instanceof \RuntimeException => ErrorLevel::ERROR,
$exception instanceof \LogicException => ErrorLevel::WARNING,
default => ErrorLevel::ERROR,
$exception instanceof \Error => ErrorSeverity::CRITICAL,
$exception instanceof \ErrorException => $this->determineErrorExceptionSeverity($exception),
$exception instanceof SecurityException => $this->determineSecurityErrorSeverity($exception),
$exception instanceof \RuntimeException => ErrorSeverity::ERROR,
$exception instanceof \LogicException => ErrorSeverity::WARNING,
default => ErrorSeverity::ERROR,
};
}
/**
* Bestimmt den Error-Level für SecurityExceptions basierend auf dem Security-Level
*/
private function determineSecurityErrorLevel(SecurityException $exception): ErrorLevel
private function determineSecurityErrorSeverity(SecurityException $exception): ErrorSeverity
{
return match($exception->getSecurityLevel()) {
SecurityLogLevel::DEBUG => ErrorLevel::DEBUG,
SecurityLogLevel::INFO => ErrorLevel::INFO,
SecurityLogLevel::WARN => ErrorLevel::WARNING,
SecurityLogLevel::ERROR => ErrorLevel::ERROR,
SecurityLogLevel::FATAL => ErrorLevel::CRITICAL,
SecurityLogLevel::DEBUG => ErrorSeverity::DEBUG,
SecurityLogLevel::INFO => ErrorSeverity::INFO,
SecurityLogLevel::WARN => ErrorSeverity::WARNING,
SecurityLogLevel::ERROR => ErrorSeverity::ERROR,
SecurityLogLevel::FATAL => ErrorSeverity::CRITICAL,
};
}
/**
* Bestimmt den Fehler-Level basierend auf dem ErrorException-Severity-Level
*/
private function determineErrorExceptionLevel(\ErrorException $exception): ErrorLevel
private function determineErrorExceptionSeverity(\ErrorException $exception): ErrorSeverity
{
return match($exception->getSeverity()) {
E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR => ErrorLevel::CRITICAL,
E_WARNING, E_CORE_WARNING, E_COMPILE_WARNING, E_USER_WARNING => ErrorLevel::WARNING,
E_NOTICE, E_USER_NOTICE => ErrorLevel::NOTICE,
E_DEPRECATED, E_USER_DEPRECATED => ErrorLevel::INFO,
E_STRICT => ErrorLevel::DEBUG,
default => ErrorLevel::ERROR,
E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR => ErrorSeverity::CRITICAL,
E_WARNING, E_CORE_WARNING, E_COMPILE_WARNING, E_USER_WARNING => ErrorSeverity::WARNING,
E_NOTICE, E_USER_NOTICE => ErrorSeverity::NOTICE,
E_DEPRECATED, E_USER_DEPRECATED => ErrorSeverity::INFO,
E_STRICT => ErrorSeverity::DEBUG,
default => ErrorSeverity::ERROR,
};
}

View File

@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Framework\ErrorHandling;
enum ErrorLevel
{
case CRITICAL;
case ERROR;
case WARNING;
case NOTICE;
case INFO;
case DEBUG;
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\ErrorHandling;
use App\Framework\Exception\Core\ErrorSeverity;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Logging\Logger;
use App\Framework\Logging\LogLevel;
@@ -42,7 +43,7 @@ final readonly class ErrorLogger
];
if ($this->logger !== null) {
$logLevel = $this->mapErrorLevelToFrameworkLevel($context->level);
$logLevel = $this->mapErrorSeverityToLogLevel($context->level);
$this->logger->log($logLevel, $message, $contextData);
} else {
// Fallback auf error_log
@@ -204,23 +205,23 @@ final readonly class ErrorLogger
error_log($message);
// Bei kritischen Fehlern auch den Stacktrace loggen
if ($context->level === ErrorLevel::CRITICAL) {
if ($context->level === ErrorSeverity::CRITICAL) {
error_log("Stack trace: \n" . $exception->getTraceAsString());
}
}
/**
* Mappt interne ErrorLevel auf Framework LogLevel
* Mappt ErrorSeverity auf Framework LogLevel
*/
private function mapErrorLevelToFrameworkLevel(ErrorLevel $level): LogLevel
private function mapErrorSeverityToLogLevel(ErrorSeverity $severity): LogLevel
{
return match($level) {
ErrorLevel::CRITICAL => LogLevel::CRITICAL,
ErrorLevel::ERROR => LogLevel::ERROR,
ErrorLevel::WARNING => LogLevel::WARNING,
ErrorLevel::NOTICE => LogLevel::NOTICE,
ErrorLevel::INFO => LogLevel::INFO,
ErrorLevel::DEBUG => LogLevel::DEBUG,
return match($severity) {
ErrorSeverity::CRITICAL => LogLevel::CRITICAL,
ErrorSeverity::ERROR => LogLevel::ERROR,
ErrorSeverity::WARNING => LogLevel::WARNING,
ErrorSeverity::NOTICE => LogLevel::NOTICE,
ErrorSeverity::INFO => LogLevel::INFO,
ErrorSeverity::DEBUG => LogLevel::DEBUG,
};
}
}

View File

@@ -1,76 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Framework\ErrorHandling;
use App\Framework\Http\Status;
/**
* Hilfsklasse zur Konvertierung von Exceptions in HTTP-Status-Codes.
*/
final class ExceptionConverter
{
/**
* Bestimmt den HTTP-Status basierend auf dem Exception-Typ
*/
public static function getStatusFromException(\Throwable $e): Status
{
return match (true) {
// Routing-Fehler
$e instanceof \App\Framework\Router\Exception\RouteNotFound => Status::NOT_FOUND,
$e instanceof \App\Framework\Router\Exception\MethodNotAllowed => Status::METHOD_NOT_ALLOWED,
// Validierungs-Fehler
$e instanceof \App\Framework\Validation\Exceptions\ValidationException => Status::BAD_REQUEST,
// API-Fehler
$e instanceof \App\Framework\Api\ApiException => Status::BAD_GATEWAY,
// Datenbank-Fehler
$e instanceof \PDOException => Status::INTERNAL_SERVER_ERROR,
// Standard
default => Status::INTERNAL_SERVER_ERROR,
};
}
/**
* Erstellt den Response-Body basierend auf der Exception
*/
public static function getResponseBody(\Throwable $e, bool $debug = false, ?string $requestId = null): array
{
if ($debug) {
// Im Debug-Modus detaillierte Informationen zurückgeben
return [
'error' => true,
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => explode("\n", $e->getTraceAsString()),
'request_id' => $requestId,
];
}
// Im Produktionsmodus nur minimale Informationen
return [
'error' => true,
'message' => self::getUserFriendlyMessage($e),
'request_id' => $requestId,
];
}
/**
* Gibt eine benutzerfreundliche Fehlermeldung basierend auf dem Exception-Typ zurück
*/
public static function getUserFriendlyMessage(\Throwable $e): string
{
return match (true) {
$e instanceof \App\Framework\Router\Exception\RouteNotFound => 'Die angeforderte Seite wurde nicht gefunden.',
$e instanceof \App\Framework\Router\Exception\MethodNotAllowed => 'Diese Anfragemethode ist nicht erlaubt.',
$e instanceof \App\Framework\Validation\Exceptions\ValidationException => 'Die eingegebenen Daten sind ungültig.',
$e instanceof \App\Framework\Api\ApiException => 'Es ist ein Fehler bei der Kommunikation mit einem externen Dienst aufgetreten.',
default => 'Es ist ein interner Fehler aufgetreten.',
};
}
}

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace App\Framework\ErrorHandling\View;
use App\Framework\ErrorHandling\ErrorContext;
use App\Framework\ErrorHandling\ExceptionConverter;
use App\Framework\Exception\ErrorHandlerContext;
/**
@@ -78,32 +76,6 @@ final readonly class ApiErrorRenderer implements ErrorViewRendererInterface
return json_encode($responseData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
/**
* Rendert einen API-Fehler als JSON (Legacy)
*/
public function render(ErrorContext $context, bool $isDebug = false): string
{
$responseData = [
'error' => true,
'message' => $isDebug ? $context->exception->getMessage() : ExceptionConverter::getUserFriendlyMessage($context->exception),
'requestId' => $context->requestId,
'timestamp' => $context->additionalData['timestamp'] ?? date('c'),
];
// Im Debug-Modus zusätzliche Informationen hinzufügen
if ($isDebug) {
$responseData['details'] = [
'exception' => get_class($context->exception),
'file' => $context->exception->getFile(),
'line' => $context->exception->getLine(),
'trace' => explode("\n", $context->exception->getTraceAsString()),
'level' => $context->level->name,
];
}
return json_encode($responseData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
/**
* Extrahiert benutzerfreundliche Nachricht aus ErrorHandlerContext
*/

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace App\Framework\ErrorHandling\View;
use App\Framework\ErrorHandling\ErrorContext;
use App\Framework\ErrorHandling\ExceptionConverter;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Http\Headers;
use App\Framework\Http\HttpResponse;
@@ -70,32 +68,6 @@ final readonly class ErrorResponseFactory
);
}
/**
* Erstellt eine HTTP-Response basierend auf dem ErrorContext (Legacy)
*/
public function createResponse(ErrorContext $context, bool $isDebug = false, bool $isApiRequest = false): HttpResponse
{
$status = ExceptionConverter::getStatusFromException($context->exception);
$headers = new Headers();
if ($isApiRequest) {
$headers = $headers->with('Content-Type', 'application/json; charset=utf-8');
$body = $this->apiRenderer->render($context, $isDebug);
} else {
$headers = $headers->with('Content-Type', 'text/html; charset=utf-8');
$body = $this->htmlRenderer->render($context, $isDebug);
}
// Cache-Control hinzufügen (Fehlerseiten sollten nicht gecacht werden)
$headers = $headers->with('Cache-Control', 'no-store, no-cache, must-revalidate');
return new HttpResponse(
status: Status::from($status),
headers: $headers,
body: $body
);
}
/**
* Bestimmt, ob es sich um eine API-Anfrage handelt
*/

View File

@@ -6,7 +6,6 @@ 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\Exception\ErrorHandlerContext;
use App\Framework\Meta\MetaData;
@@ -244,141 +243,6 @@ final readonly class ErrorTemplateRenderer implements ErrorViewRendererInterface
}
}
/**
* Rendert eine Fehlerseite basierend auf dem ErrorContext (Legacy)
*/
public function render(ErrorContext $context, bool $isDebug = false): string
{
$template = $isDebug ? $this->debugTemplate : $this->productionTemplate;
try {
// Sichere Datenaufbereitung ohne Closures oder komplexe Objekte
$errorLine = $context->exception->getLine();
$codeHtml = new FileHighlighter()($context->exception->getFile(), $errorLine - 10, 10, $errorLine);
$safeData = [
'code' => RawHtml::from($codeHtml),
'errorClass' => $context->exception::class,
'errorMessage' => $context->exception->getMessage(),
'error' => [
'message' => $context->exception->getMessage(),
'file' => $context->exception->getFile(),
'line' => $context->exception->getLine(),
'trace' => $context->exception->getTraceAsString(),
'class' => get_class($context->exception),
],
'requestId' => $context->requestId->toString(),
'timestamp' => $context->additionalData['timestamp'] ?? date('c'),
'level' => $context->level->name,
'memory' => $context->additionalData['memory_usage'] ?? 0,
'trace' => new StackTrace($context->exception)->getItems(),
];
$renderContext = new RenderContext(
template: $template,
metaData: new MetaData('', '', new OpenGraphTypeWebsite()),
data: $safeData
);
$renderedContent = $this->renderer->render($renderContext);
if ($renderedContent === "") {
throw new \Exception("Template Renderer returned empty string");
}
return $renderedContent;
} catch (Throwable $e) {
// Fallback falls das Template-Rendering fehlschlägt
return $this->createFallbackErrorPage($context, $isDebug, $e);
}
}
/**
* Erstellt eine einfache Fehlerseite als Fallback
*/
private function createFallbackErrorPage(ErrorContext $context, bool $isDebug, ?Throwable $renderException = null): string
{
$exception = $context->exception;
if ($isDebug) {
$content = sprintf(
'<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Anwendungsfehler</title>
<style>
body { font-family: sans-serif; line-height: 1.6; color: #333; max-width: 1200px; margin: 0 auto; padding: 20px; }
.error-container { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; padding: 20px; margin: 20px 0; }
.error-title { color: #dc3545; margin-top: 0; }
.error-meta { background: #e9ecef; padding: 10px; border-radius: 4px; font-size: 0.9em; margin: 10px 0; }
.error-trace { background: #f1f3f5; padding: 15px; border-radius: 4px; overflow-x: auto; font-family: monospace; font-size: 0.9em; }
.error-info { margin-bottom: 20px; }
.request-id { font-family: monospace; color: #6c757d; }
.render-error { margin-top: 30px; padding-top: 20px; border-top: 1px dashed #dee2e6; }
</style>
</head>
<body>
<div class="error-container">
<h1 class="error-title">Anwendungsfehler: %s</h1>
<div class="error-meta">
<strong>Request-ID:</strong> <span class="request-id">%s</span><br>
<strong>Zeit:</strong> %s<br>
<strong>Fehler-Level:</strong> %s
</div>
<div class="error-info">
<p><strong>Datei:</strong> %s</p>
<p><strong>Zeile:</strong> %d</p>
</div>
<h3>Stack Trace:</h3>
<div class="error-trace">%s</div>
%s
</div>
</body>
</html>',
htmlspecialchars($exception->getMessage()),
htmlspecialchars($context->requestId?->toString() ?? 'unknown'),
htmlspecialchars($context->additionalData['timestamp'] ?? date('c')),
htmlspecialchars($context->level->name),
htmlspecialchars($exception->getFile()),
$exception->getLine(),
nl2br(htmlspecialchars($exception->getTraceAsString())),
$renderException ? '<div class="render-error"><h3>Fehler beim Rendern der Fehlerseite:</h3><p>' .
htmlspecialchars($renderException->getMessage()) . '</p></div>' : ''
);
} else {
// Produktions-Fallback mit minimalen Informationen
$content = sprintf(
'<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Fehler</title>
<style>
body { font-family: sans-serif; line-height: 1.6; color: #333; max-width: 800px; margin: 0 auto; padding: 40px 20px; text-align: center; }
.error-container { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; padding: 40px 20px; }
.error-title { color: #dc3545; margin-top: 0; }
.request-id { font-family: monospace; color: #6c757d; font-size: 0.8em; margin-top: 30px; }
</style>
</head>
<body>
<div class="error-container">
<h1 class="error-title">Es ist ein Fehler aufgetreten</h1>
<p>Bitte versuchen Sie es später erneut oder kontaktieren Sie den Support.</p>
<p class="request-id">Request-ID: %s</p>
</div>
</body>
</html>',
htmlspecialchars($context->requestId?->toString() ?? 'unknown')
);
}
return $content;
}
/**
* Extrahiert Fehlermeldung aus ErrorHandlerContext
*/

View File

@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\Framework\ErrorHandling\View;
use App\Framework\ErrorHandling\ErrorContext;
use App\Framework\Exception\ErrorHandlerContext;
/**
@@ -16,9 +15,4 @@ interface ErrorViewRendererInterface
* Rendert eine Fehlerseite basierend auf dem ErrorHandlerContext
*/
public function renderFromHandlerContext(ErrorHandlerContext $context, bool $isDebug = false): string;
/**
* Rendert eine Fehlerseite basierend auf dem ErrorContext (Legacy)
*/
public function render(ErrorContext $context, bool $isDebug = false): string;
}