- 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.
54 KiB
Error Handling - Unified Architecture Design
Date: 2025-10-12 Status: Design Phase Previous: ERROR-HANDLING-AUDIT-REPORT.md Next: ERROR-HANDLING-MIGRATION-PLAN.md
Executive Summary
Dieses Dokument definiert die Unified Exception Architecture für das Framework. Das Design konsolidiert 4 separate Error-Module in ein einheitliches System unter src/Framework/Exception/ mit automatischer Integration aller Komponenten.
Design Goals:
- ✅ Single Source of Truth für Error Handling
- ✅ Automatic integration (Pattern Detection, Reporting, Circuit Breaker)
- ✅ Backward compatibility mit bestehenden Code
- ✅ Improved developer experience
- ✅ Maintained functionality from all modules
- ✅ Performance optimization (async processing, batching)
1. New Module Structure
Unified Directory Layout
src/Framework/Exception/
├── Core/ # Core exception infrastructure
│ ├── FrameworkException.php # Enhanced base exception
│ ├── ErrorCode.php # Enhanced with severity mapping
│ ├── ExceptionContext.php # Unified context (Domain + Request + System)
│ ├── ErrorSeverity.php # Moved from ErrorAggregation
│ └── ErrorLevel.php # Moved from ErrorHandling
│
├── Handler/ # Global error handling
│ ├── ErrorHandler.php # Integrated error handler
│ ├── ErrorLogger.php # Structured logging
│ ├── ResponseFactory.php # HTTP response creation
│ └── ContextBuilder.php # ExceptionContext builder
│
├── Aggregation/ # Pattern detection & alerting
│ ├── ErrorAggregator.php # Auto-triggered pattern analysis
│ ├── ErrorEvent.php # Single error event VO
│ ├── ErrorPattern.php # Pattern VO
│ ├── AlertManager.php # Alert queuing
│ ├── PatternDetector.php # Fingerprinting logic
│ └── Storage/
│ ├── ErrorStorageInterface.php
│ └── DatabaseErrorStorage.php # Unified database storage
│
├── Reporting/ # Error reporting & analytics
│ ├── ErrorReporter.php # Auto-triggered reporting
│ ├── ErrorReport.php # Report VO (extends ErrorEvent)
│ ├── AnalyticsEngine.php # Velocity & anomaly detection
│ ├── ReportFilters/ # Filter chain
│ └── ReportProcessors/ # Enrichment chain
│
├── Boundaries/ # Resilience & circuit breaker
│ ├── ErrorBoundary.php # Graceful degradation
│ ├── BoundaryConfig.php # Configuration VO
│ ├── BoundaryResult.php # Result pattern
│ ├── RetryStrategy.php # Retry patterns
│ ├── CircuitBreaker/
│ │ ├── CircuitBreakerManager.php
│ │ └── CircuitState.php
│ └── Exceptions/
│ ├── BoundaryException.php # Base boundary exception (extends FrameworkException)
│ ├── BoundaryTimeoutException.php
│ └── BoundaryFailedException.php
│
├── Http/ # HTTP-specific exceptions
│ ├── HttpException.php # Base HTTP exception
│ ├── NotFoundException.php
│ ├── UnauthorizedException.php
│ └── ...
│
├── Security/ # Security exceptions
│ ├── SecurityException.php
│ ├── XssAttemptException.php
│ └── ...
│
├── Database/ # Database exceptions
│ ├── DatabaseException.php
│ └── ...
│
├── Validation/ # Validation exceptions
│ ├── ValidationException.php
│ └── ...
│
└── Migrations/ # Database migrations
├── CreateErrorEventsTable.php
└── CreateErrorPatternsTable.php
Removed Directories
❌ src/Framework/ErrorHandling/ # Merged into Exception/Handler/
❌ src/Framework/ErrorAggregation/ # Merged into Exception/Aggregation/
❌ src/Framework/ErrorBoundaries/ # Merged into Exception/Boundaries/
❌ src/Framework/ErrorReporting/ # Merged into Exception/Reporting/
2. Core Components Design
2.1 Enhanced FrameworkException
File: src/Framework/Exception/Core/FrameworkException.php
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\Aggregation\ErrorAggregator;
use App\Framework\Exception\Reporting\ErrorReporter;
/**
* Enhanced FrameworkException with automatic integration
*
* All framework exceptions MUST extend this class to ensure:
* - Automatic pattern detection via ErrorAggregator
* - Automatic error reporting via ErrorReporter
* - Consistent error handling and context
* - Circuit breaker integration
*/
class FrameworkException extends \RuntimeException
{
protected ExceptionContext $context;
protected ?ErrorCode $errorCode = null;
protected ?int $retryAfter = null;
// NEW: Automatic integration flags
protected bool $skipAggregation = false;
protected bool $skipReporting = false;
protected bool $reported = false;
public function __construct(
string $message = '',
int $code = 0,
?\Throwable $previous = null
) {
parent::__construct($message, $code, $previous);
$this->context = ExceptionContext::empty();
}
// ========================================
// Factory Methods (existing + enhanced)
// ========================================
public static function simple(
string $message,
?\Throwable $previous = null,
int $httpStatus = 500
): static {
$exception = new static($message, $httpStatus, $previous);
return $exception;
}
public static function create(
ErrorCode $errorCode,
?string $message = null
): static {
$exception = new static($message ?? $errorCode->getDescription());
$exception->errorCode = $errorCode;
return $exception;
}
public static function forOperation(
string $operation,
?string $component = null,
?string $message = null,
?ErrorCode $errorCode = null
): static {
$exception = new static($message ?? "Error in {$operation}");
$exception->context = ExceptionContext::forOperation($operation, $component);
$exception->errorCode = $errorCode;
return $exception;
}
public static function fromContext(
string $message,
ExceptionContext $context,
?ErrorCode $errorCode = null
): static {
$exception = new static($message);
$exception->context = $context;
$exception->errorCode = $errorCode;
return $exception;
}
// ========================================
// Fluent API (existing)
// ========================================
public function withContext(ExceptionContext $context): self
{
$this->context = $context;
return $this;
}
public function withOperation(string $operation, ?string $component = null): self
{
$this->context = $this->context->withOperation($operation, $component);
return $this;
}
public function withData(array $data): self
{
$this->context = $this->context->withData($data);
return $this;
}
public function withDebug(array $debug): self
{
$this->context = $this->context->withDebug($debug);
return $this;
}
public function withMetadata(array $metadata): self
{
$this->context = $this->context->withMetadata($metadata);
return $this;
}
public function withErrorCode(ErrorCode $errorCode): self
{
$this->errorCode = $errorCode;
return $this;
}
public function withRetryAfter(int $seconds): self
{
$this->retryAfter = $seconds;
return $this;
}
// NEW: Request/System context methods
public function withRequestContext(
string $requestId,
string $method,
string $uri,
?string $clientIp = null,
?string $userAgent = null
): self {
$this->context = $this->context->withRequestContext(
$requestId,
$method,
$uri,
$clientIp,
$userAgent
);
return $this;
}
public function withSystemContext(
string $environment,
string $hostname,
float $memoryUsage,
float $cpuUsage
): self {
$this->context = $this->context->withSystemContext(
$environment,
$hostname,
$memoryUsage,
$cpuUsage
);
return $this;
}
// NEW: Integration control methods
public function skipAggregation(): self
{
$this->skipAggregation = true;
return $this;
}
public function skipReporting(): self
{
$this->skipReporting = true;
return $this;
}
// ========================================
// Getters
// ========================================
public function getContext(): ExceptionContext
{
return $this->context;
}
public function getErrorCode(): ?ErrorCode
{
return $this->errorCode;
}
public function getRetryAfter(): ?int
{
return $this->retryAfter;
}
// NEW: Get severity from ErrorCode
public function getSeverity(): ErrorSeverity
{
return $this->errorCode?->getSeverity() ?? ErrorSeverity::ERROR;
}
// ========================================
// Recovery Methods
// ========================================
public function isRecoverable(): bool
{
return $this->errorCode?->isRecoverable() ?? true;
}
public function getRecoveryHint(): ?string
{
return $this->errorCode?->getRecoveryHint();
}
// NEW: Check if already reported
public function isReported(): bool
{
return $this->reported;
}
public function markAsReported(): void
{
$this->reported = true;
}
public function shouldSkipAggregation(): bool
{
return $this->skipAggregation;
}
public function shouldSkipReporting(): bool
{
return $this->skipReporting;
}
// ========================================
// Category Helpers
// ========================================
public function isCategory(string $category): bool
{
if ($this->errorCode === null) {
return false;
}
return $this->errorCode->getCategory() === $category;
}
public function isErrorCode(ErrorCode $errorCode): bool
{
return $this->errorCode === $errorCode;
}
// ========================================
// Serialization
// ========================================
public function toArray(): array
{
return [
'message' => $this->getMessage(),
'code' => $this->getCode(),
'error_code' => $this->errorCode?->value,
'severity' => $this->getSeverity()->value,
'context' => $this->context->toArray(),
'file' => $this->getFile(),
'line' => $this->getLine(),
'recoverable' => $this->isRecoverable(),
'retry_after' => $this->retryAfter,
'recovery_hint' => $this->getRecoveryHint(),
];
}
}
Key Enhancements:
- ✅ Automatic integration flags (
skipAggregation,skipReporting,reported) - ✅ Request/System context support via
withRequestContext(),withSystemContext() - ✅
getSeverity()from ErrorCode - ✅ Integration control methods (
skipAggregation(),skipReporting()) - ✅ Category helpers (
isCategory(),isErrorCode())
2.2 Unified ExceptionContext
File: src/Framework/Exception/Core/ExceptionContext.php
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
/**
* Unified ExceptionContext combining Domain + Request + System context
*
* Replaces both:
* - ExceptionContext (old)
* - ErrorHandlerContext (old)
*/
final readonly class ExceptionContext
{
public function __construct(
// Domain Context
public ?string $operation = null,
public ?string $component = null,
public array $data = [],
public array $debug = [],
public array $metadata = [],
// Request Context (NEW)
public ?string $requestId = null,
public ?string $requestMethod = null,
public ?string $requestUri = null,
public ?string $clientIp = null,
public ?string $userAgent = null,
public ?string $userId = null,
// System Context (NEW)
public ?string $environment = null,
public ?string $hostname = null,
public ?float $memoryUsage = null,
public ?float $cpuUsage = null,
public ?string $phpVersion = null,
) {
}
// ========================================
// Factory Methods
// ========================================
public static function empty(): self
{
return new self();
}
public static function forOperation(string $operation, ?string $component = null): self
{
return new self(operation: $operation, component: $component);
}
public static function fromRequest(
string $requestId,
string $method,
string $uri,
?string $clientIp = null,
?string $userAgent = null,
?string $userId = null
): self {
return new self(
requestId: $requestId,
requestMethod: $method,
requestUri: $uri,
clientIp: $clientIp,
userAgent: $userAgent,
userId: $userId
);
}
public static function fromSystem(): self
{
return new self(
environment: $_ENV['APP_ENV'] ?? 'production',
hostname: gethostname() ?: 'unknown',
memoryUsage: memory_get_usage(true) / 1024 / 1024, // MB
cpuUsage: sys_getloadavg()[0] ?? 0.0,
phpVersion: PHP_VERSION
);
}
// ========================================
// Fluent API - Domain Context
// ========================================
public function withOperation(string $operation, ?string $component = null): self
{
return new self(
operation: $operation,
component: $component ?? $this->component,
data: $this->data,
debug: $this->debug,
metadata: $this->metadata,
requestId: $this->requestId,
requestMethod: $this->requestMethod,
requestUri: $this->requestUri,
clientIp: $this->clientIp,
userAgent: $this->userAgent,
userId: $this->userId,
environment: $this->environment,
hostname: $this->hostname,
memoryUsage: $this->memoryUsage,
cpuUsage: $this->cpuUsage,
phpVersion: $this->phpVersion
);
}
public function withData(array $data): self
{
return new self(
operation: $this->operation,
component: $this->component,
data: array_merge($this->data, $data),
debug: $this->debug,
metadata: $this->metadata,
requestId: $this->requestId,
requestMethod: $this->requestMethod,
requestUri: $this->requestUri,
clientIp: $this->clientIp,
userAgent: $this->userAgent,
userId: $this->userId,
environment: $this->environment,
hostname: $this->hostname,
memoryUsage: $this->memoryUsage,
cpuUsage: $this->cpuUsage,
phpVersion: $this->phpVersion
);
}
public function withDebug(array $debug): self
{
return new self(
operation: $this->operation,
component: $this->component,
data: $this->data,
debug: array_merge($this->debug, $debug),
metadata: $this->metadata,
requestId: $this->requestId,
requestMethod: $this->requestMethod,
requestUri: $this->requestUri,
clientIp: $this->clientIp,
userAgent: $this->userAgent,
userId: $this->userId,
environment: $this->environment,
hostname: $this->hostname,
memoryUsage: $this->memoryUsage,
cpuUsage: $this->cpuUsage,
phpVersion: $this->phpVersion
);
}
public function withMetadata(array $metadata): self
{
return new self(
operation: $this->operation,
component: $this->component,
data: $this->data,
debug: $this->debug,
metadata: array_merge($this->metadata, $metadata),
requestId: $this->requestId,
requestMethod: $this->requestMethod,
requestUri: $this->requestUri,
clientIp: $this->clientIp,
userAgent: $this->userAgent,
userId: $this->userId,
environment: $this->environment,
hostname: $this->hostname,
memoryUsage: $this->memoryUsage,
cpuUsage: $this->cpuUsage,
phpVersion: $this->phpVersion
);
}
// ========================================
// Fluent API - Request Context
// ========================================
public function withRequestContext(
string $requestId,
string $method,
string $uri,
?string $clientIp = null,
?string $userAgent = null,
?string $userId = null
): self {
return new self(
operation: $this->operation,
component: $this->component,
data: $this->data,
debug: $this->debug,
metadata: $this->metadata,
requestId: $requestId,
requestMethod: $method,
requestUri: $uri,
clientIp: $clientIp,
userAgent: $userAgent,
userId: $userId,
environment: $this->environment,
hostname: $this->hostname,
memoryUsage: $this->memoryUsage,
cpuUsage: $this->cpuUsage,
phpVersion: $this->phpVersion
);
}
public function withUserId(string $userId): self
{
return new self(
operation: $this->operation,
component: $this->component,
data: $this->data,
debug: $this->debug,
metadata: $this->metadata,
requestId: $this->requestId,
requestMethod: $this->requestMethod,
requestUri: $this->requestUri,
clientIp: $this->clientIp,
userAgent: $this->userAgent,
userId: $userId,
environment: $this->environment,
hostname: $this->hostname,
memoryUsage: $this->memoryUsage,
cpuUsage: $this->cpuUsage,
phpVersion: $this->phpVersion
);
}
// ========================================
// Fluent API - System Context
// ========================================
public function withSystemContext(
string $environment,
string $hostname,
float $memoryUsage,
float $cpuUsage
): self {
return new self(
operation: $this->operation,
component: $this->component,
data: $this->data,
debug: $this->debug,
metadata: $this->metadata,
requestId: $this->requestId,
requestMethod: $this->requestMethod,
requestUri: $this->requestUri,
clientIp: $this->clientIp,
userAgent: $this->userAgent,
userId: $this->userId,
environment: $environment,
hostname: $hostname,
memoryUsage: $memoryUsage,
cpuUsage: $cpuUsage,
phpVersion: $this->phpVersion
);
}
// ========================================
// Getters (for backward compatibility)
// ========================================
public function getOperation(): ?string
{
return $this->operation;
}
public function getComponent(): ?string
{
return $this->component;
}
public function getData(): array
{
return $this->data;
}
public function getDebug(): array
{
return $this->debug;
}
public function getMetadata(): array
{
return $this->metadata;
}
// ========================================
// Serialization
// ========================================
public function toArray(): array
{
return [
// Domain context
'operation' => $this->operation,
'component' => $this->component,
'data' => $this->sanitizeData($this->data),
'debug' => $this->debug,
'metadata' => $this->metadata,
// Request context
'request' => [
'id' => $this->requestId,
'method' => $this->requestMethod,
'uri' => $this->requestUri,
'client_ip' => $this->clientIp,
'user_agent' => $this->userAgent,
'user_id' => $this->userId,
],
// System context
'system' => [
'environment' => $this->environment,
'hostname' => $this->hostname,
'memory_usage_mb' => $this->memoryUsage,
'cpu_usage' => $this->cpuUsage,
'php_version' => $this->phpVersion,
],
];
}
private function sanitizeData(array $data): array
{
$sensitiveKeys = ['password', 'token', 'api_key', 'secret', 'private_key'];
array_walk_recursive($data, function (&$value, $key) use ($sensitiveKeys) {
if (in_array(strtolower($key), $sensitiveKeys)) {
$value = '[REDACTED]';
}
});
return $data;
}
}
Key Enhancements:
- ✅ Unified context (Domain + Request + System)
- ✅ Replaces both ExceptionContext and ErrorHandlerContext
- ✅ Factory methods for different context types
- ✅ Fluent API preserved and extended
- ✅ Automatic data sanitization
2.3 Enhanced ErrorCode with Severity Mapping
File: src/Framework/Exception/Core/ErrorCode.php
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
enum ErrorCode: string
{
// ... (alle bestehenden 100+ ErrorCodes bleiben)
// NEW: Severity mapping
public function getSeverity(): ErrorSeverity
{
return match($this) {
// CRITICAL Severity
self::SYSTEM_RESOURCE_EXHAUSTED,
self::DB_CONNECTION_FAILED,
self::DB_POOL_EXHAUSTED,
self::SEC_SQL_INJECTION_ATTEMPT,
self::SEC_XSS_ATTEMPT,
self::SEC_PATH_TRAVERSAL_ATTEMPT,
self::PERF_MEMORY_LIMIT_EXCEEDED,
self::PERF_CIRCUIT_BREAKER_OPEN,
self::FS_DISK_FULL => ErrorSeverity::CRITICAL,
// ERROR Severity
self::SYSTEM_DEPENDENCY_MISSING,
self::DB_QUERY_FAILED,
self::DB_TRANSACTION_FAILED,
self::DB_MIGRATION_FAILED,
self::AUTH_CREDENTIALS_INVALID,
self::AUTH_INSUFFICIENT_PRIVILEGES,
self::CACHE_CONNECTION_FAILED,
self::FS_FILE_NOT_FOUND,
self::FS_PERMISSION_DENIED,
self::API_SERVICE_UNAVAILABLE,
self::QUEUE_CONNECTION_FAILED,
self::QUEUE_JOB_FAILED,
self::PERF_EXECUTION_TIMEOUT,
self::EVENT_DISPATCH_FAILED,
self::TPL_RENDERING_FAILED => ErrorSeverity::ERROR,
// WARNING Severity
self::SYSTEM_CONFIG_INVALID,
self::DB_CONSTRAINT_VIOLATION,
self::AUTH_TOKEN_EXPIRED,
self::AUTH_SESSION_EXPIRED,
self::HTTP_RATE_LIMIT_EXCEEDED,
self::API_RATE_LIMIT_EXCEEDED,
self::CACHE_KEY_NOT_FOUND,
self::QUEUE_MAX_RETRIES_EXCEEDED,
self::PERF_THRESHOLD_EXCEEDED,
self::TPL_TEMPLATE_NOT_FOUND => ErrorSeverity::WARNING,
// INFO Severity
self::HTTP_NOT_FOUND,
self::ENTITY_NOT_FOUND,
self::VAL_REQUIRED_FIELD_MISSING,
self::VAL_INVALID_FORMAT,
self::VAL_OUT_OF_RANGE => ErrorSeverity::INFO,
// DEBUG Severity
self::DISC_CACHE_CORRUPTION,
self::TPL_CACHE_FAILED => ErrorSeverity::DEBUG,
// Default to ERROR for unmapped codes
default => ErrorSeverity::ERROR,
};
}
// Existing methods remain unchanged
public function getCategory(): string { ... }
public function getNumericCode(): int { ... }
public function getDescription(): string { ... }
public function getRecoveryHint(): string { ... }
public function isRecoverable(): bool { ... }
public function getRetryAfterSeconds(): ?int { ... }
}
Key Enhancements:
- ✅
getSeverity()method mapping all 100+ codes to severities - ✅ Explicit severity for each category
- ✅ Fallback to ERROR for unmapped codes
2.4 Integrated ErrorHandler
File: src/Framework/Exception/Handler/ErrorHandler.php
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Handler;
use App\Framework\Exception\Aggregation\ErrorAggregator;
use App\Framework\Exception\Core\FrameworkException;
use App\Framework\Exception\Reporting\ErrorReporter;
use App\Framework\Http\Response;
use Throwable;
/**
* Integrated ErrorHandler with automatic aggregation and reporting
*/
final readonly class ErrorHandler
{
public function __construct(
private ErrorLogger $logger,
private ResponseFactory $responseFactory,
private ContextBuilder $contextBuilder,
private ErrorAggregator $aggregator, // NEW: Auto-integrated
private ErrorReporter $reporter, // NEW: Auto-integrated
private bool $enableAggregation = true,
private bool $enableReporting = true,
) {
}
/**
* Register global error handlers
*/
public function register(): void
{
set_exception_handler($this->handleException(...));
set_error_handler($this->handleError(...));
register_shutdown_function($this->handleShutdown(...));
}
/**
* Handle exception with automatic integration
*/
public function handleException(Throwable $exception): void
{
try {
// 1. Build unified context
$context = $this->contextBuilder->buildFromException($exception);
// 2. Enrich FrameworkException with full context
if ($exception instanceof FrameworkException) {
$exception = $this->enrichException($exception, $context);
}
// 3. Log the exception
$this->logger->logException($exception, $context);
// 4. Auto-trigger aggregation (if enabled and not skipped)
if ($this->enableAggregation && !$this->shouldSkipAggregation($exception)) {
$this->aggregator->processException($exception, $context);
}
// 5. Auto-trigger reporting (if enabled and not skipped)
if ($this->enableReporting && !$this->shouldSkipReporting($exception)) {
$this->reporter->reportException($exception, $context);
}
// 6. Create HTTP response (only if in HTTP context)
if ($this->isHttpContext()) {
$response = $this->responseFactory->createFromException($exception, $context);
$this->emitResponse($response);
}
} catch (Throwable $e) {
// Fallback: Don't let error handling crash the application
$this->handleErrorHandlerFailure($e, $exception);
}
}
/**
* Enrich FrameworkException with full context
*/
private function enrichException(
FrameworkException $exception,
ExceptionContext $fullContext
): FrameworkException {
// Only enrich if exception doesn't already have full context
if ($fullContext->requestId && !$exception->getContext()->requestId) {
return $exception->withContext($fullContext);
}
return $exception;
}
/**
* Check if aggregation should be skipped
*/
private function shouldSkipAggregation(Throwable $exception): bool
{
if ($exception instanceof FrameworkException) {
return $exception->shouldSkipAggregation();
}
return false;
}
/**
* Check if reporting should be skipped
*/
private function shouldSkipReporting(Throwable $exception): bool
{
if ($exception instanceof FrameworkException) {
return $exception->shouldSkipReporting() || $exception->isReported();
}
return false;
}
/**
* Create HTTP response from exception
*/
public function createHttpResponse(
Throwable $exception,
?ExceptionContext $context = null
): Response {
$context ??= $this->contextBuilder->buildFromException($exception);
return $this->responseFactory->createFromException($exception, $context);
}
// ... (rest of existing ErrorHandler methods)
}
Key Enhancements:
- ✅ Automatic ErrorAggregator integration
- ✅ Automatic ErrorReporter integration
- ✅ Respects
skipAggregation()andskipReporting()flags - ✅ Prevents duplicate reporting via
isReported()check - ✅ Graceful fallback if integration fails
2.5 Auto-Triggered ErrorAggregator
File: src/Framework/Exception/Aggregation/ErrorAggregator.php
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Aggregation;
use App\Framework\Exception\Core\ExceptionContext;
use App\Framework\Exception\Core\FrameworkException;
use Throwable;
/**
* Auto-triggered ErrorAggregator (called by ErrorHandler)
*/
final readonly class ErrorAggregator
{
// ... (existing constructor and properties)
/**
* NEW: Process exception (called by ErrorHandler)
*/
public function processException(
Throwable $exception,
ExceptionContext $context
): void {
try {
// Create ErrorEvent from exception + context
$errorEvent = ErrorEvent::fromException($exception, $context);
// Use existing processErrorEvent logic
$this->processErrorEvent($errorEvent);
} catch (\Throwable $e) {
// Don't let aggregation break the application
$this->logError("Failed to process exception: " . $e->getMessage(), [
'exception' => $e,
'original_exception' => $exception->getMessage(),
]);
}
}
/**
* Existing: Process ErrorEvent
*/
public function processErrorEvent(ErrorEvent $event): void
{
// Existing logic remains unchanged
$this->storage->storeEvent($event);
$pattern = $this->updateErrorPattern($event);
if ($pattern->shouldAlert()) {
$this->queueAlert($pattern, $event);
}
$this->logError("Error processed", [
'event_id' => $event->id->toString(),
'fingerprint' => $event->getFingerprint(),
'pattern_id' => $pattern->id->toString(),
]);
}
// ... (rest of existing methods remain unchanged)
}
Key Enhancements:
- ✅ New
processException()method for ErrorHandler integration - ✅ Creates ErrorEvent from Throwable + ExceptionContext
- ✅ Delegates to existing
processErrorEvent()logic - ✅ Graceful error handling
2.6 Enhanced ErrorEvent
File: src/Framework/Exception/Aggregation/ErrorEvent.php
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Aggregation;
use App\Framework\Exception\Core\ExceptionContext;
use App\Framework\Exception\Core\FrameworkException;
use Throwable;
/**
* Enhanced ErrorEvent with Throwable support
*/
final readonly class ErrorEvent
{
// ... (existing properties remain)
/**
* NEW: Create from Throwable + ExceptionContext
*/
public static function fromException(
Throwable $exception,
ExceptionContext $context
): self {
// Extract ErrorCode (if FrameworkException)
$errorCode = $exception instanceof FrameworkException
? $exception->getErrorCode() ?? self::inferErrorCode($exception)
: self::inferErrorCode($exception);
// Extract severity
$severity = $exception instanceof FrameworkException
? $exception->getSeverity()
: self::inferSeverity($exception);
return new self(
id: Ulid::generate(),
service: self::extractServiceName($context),
component: $context->component ?? 'unknown',
operation: $context->operation ?? 'unknown',
errorCode: $errorCode,
errorMessage: $exception->getMessage(),
severity: $severity,
occurredAt: new \DateTimeImmutable(),
context: $context->data,
metadata: $context->metadata,
requestId: $context->requestId,
userId: $context->userId,
clientIp: $context->clientIp,
isSecurityEvent: self::isSecurityException($exception),
stackTrace: $exception->getTraceAsString(),
userAgent: $context->userAgent,
);
}
/**
* Existing: Create from ErrorHandlerContext (backward compatibility)
*/
public static function fromErrorHandlerContext(ErrorHandlerContext $context): self
{
// Existing logic remains for backward compatibility during migration
// Will be deprecated after migration complete
}
/**
* Infer ErrorCode from generic Throwable
*/
private static function inferErrorCode(Throwable $exception): ErrorCode
{
return match (true) {
$exception instanceof \PDOException => ErrorCode::DB_QUERY_FAILED,
$exception instanceof \RuntimeException => ErrorCode::SYSTEM_RESOURCE_EXHAUSTED,
$exception instanceof \InvalidArgumentException => ErrorCode::VAL_INVALID_INPUT,
default => ErrorCode::SYSTEM_RESOURCE_EXHAUSTED,
};
}
/**
* Infer severity from generic Throwable
*/
private static function inferSeverity(Throwable $exception): ErrorSeverity
{
return match (true) {
$exception instanceof \Error => ErrorSeverity::CRITICAL,
$exception instanceof \PDOException => ErrorSeverity::ERROR,
$exception instanceof \RuntimeException => ErrorSeverity::ERROR,
default => ErrorSeverity::WARNING,
};
}
/**
* Check if exception is security-related
*/
private static function isSecurityException(Throwable $exception): bool
{
if ($exception instanceof FrameworkException) {
return $exception->isCategory('SEC');
}
return false;
}
// ... (rest of existing methods remain unchanged)
}
Key Enhancements:
- ✅ New
fromException()factory method - ✅ Automatic ErrorCode inference for non-FrameworkException
- ✅ Automatic severity inference
- ✅ Security exception detection
- ✅ Backward compatibility with
fromErrorHandlerContext()
3. Integration Architecture
3.1 Automatic Integration Flow
┌─────────────────────────────────────────────────────────────────┐
│ 1. Exception Thrown │
│ throw FrameworkException::create(ErrorCode::DB_QUERY_FAILED) │
└──────────────────────┬──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. ErrorHandler::handleException() │
│ - Build ExceptionContext (Domain + Request + System) │
│ - Enrich FrameworkException with full context │
│ - Log exception via ErrorLogger │
└──────────────────────┬──────────────────────────────────────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 3a. Aggregate│ │ 3b. Report │ │ 3c. Respond │
│ (async) │ │ (async) │ │ (sync) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ ErrorEvent │ │ ErrorReport │ │ HTTP Response│
│ ErrorPattern │ │ Analytics │ │ JSON/HTML │
│ Alert Queue │ │ Storage │ │ │
└──────────────┘ └──────────────┘ └──────────────┘
3.2 ErrorBoundaries Integration
File: src/Framework/Exception/Boundaries/Exceptions/BoundaryException.php
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Boundaries\Exceptions;
use App\Framework\Exception\Core\ErrorCode;
use App\Framework\Exception\Core\FrameworkException;
/**
* Base exception for ErrorBoundaries (extends FrameworkException)
*/
class BoundaryException extends FrameworkException
{
public function __construct(
string $message,
string $boundaryName,
?\Throwable $previous = null
) {
parent::__construct($message, 0, $previous);
$this->withOperation('boundary.execute', 'ErrorBoundary')
->withData(['boundary_name' => $boundaryName])
->withErrorCode(ErrorCode::PERF_CIRCUIT_BREAKER_OPEN);
}
}
File: src/Framework/Exception/Boundaries/Exceptions/BoundaryTimeoutException.php
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Boundaries\Exceptions;
use App\Framework\Exception\Core\ErrorCode;
final class BoundaryTimeoutException extends BoundaryException
{
public function __construct(
string $message,
string $boundaryName,
float $executionTimeSeconds,
?\Throwable $previous = null
) {
parent::__construct($message, $boundaryName, $previous);
$this->withData([
'execution_time_seconds' => $executionTimeSeconds,
'timeout_type' => 'boundary_timeout',
])->withErrorCode(ErrorCode::PERF_EXECUTION_TIMEOUT);
}
}
Key Changes:
- ✅ BoundaryException extends FrameworkException
- ✅ Inherits ErrorCode, ExceptionContext, retry logic
- ✅ Automatically integrated with ErrorHandler
- ✅ Benefits from pattern detection and reporting
3.3 Database Schema Consolidation
Migration: src/Framework/Exception/Migrations/CreateErrorEventsTable.php
<?php
use App\Framework\Database\Schema\Blueprint;
use App\Framework\Database\Schema\Schema;
return new class {
public function up(Schema $schema): void
{
$schema->create('error_events', function (Blueprint $table) {
// Primary key
$table->string('id', 26)->primary(); // ULID
// Domain context
$table->string('service', 50);
$table->string('component', 100);
$table->string('operation', 100);
$table->string('error_code', 20);
$table->text('error_message');
$table->string('severity', 20); // CRITICAL, ERROR, WARNING, INFO, DEBUG
// Timing
$table->timestamp('occurred_at');
// Context data (JSON)
$table->json('context')->nullable();
$table->json('metadata')->nullable();
// Request context
$table->string('request_id', 50)->nullable();
$table->string('user_id', 50)->nullable();
$table->string('client_ip', 45)->nullable(); // IPv6 support
$table->text('user_agent')->nullable();
// Security
$table->boolean('is_security_event')->default(false);
// Debugging
$table->text('stack_trace')->nullable();
// NEW: Reporting fields (merged from error_reports)
$table->string('report_level', 20)->nullable(); // error, warning, info, debug
$table->json('environment')->nullable(); // System context
$table->boolean('is_reported')->default(false);
$table->timestamp('reported_at')->nullable();
// Indexes for performance
$table->index(['service', 'occurred_at']);
$table->index(['error_code', 'occurred_at']);
$table->index(['severity', 'occurred_at']);
$table->index('request_id');
$table->index('user_id');
$table->index(['is_security_event', 'occurred_at']);
$table->index('occurred_at'); // For cleanup queries
});
}
public function down(Schema $schema): void
{
$schema->drop('error_events');
}
};
Migration: src/Framework/Exception/Migrations/CreateErrorPatternsTable.php
<?php
use App\Framework\Database\Schema\Blueprint;
use App\Framework\Database\Schema\Schema;
return new class {
public function up(Schema $schema): void
{
$schema->create('error_patterns', function (Blueprint $table) {
// Primary key
$table->string('id', 26)->primary(); // ULID
// Pattern identification
$table->string('fingerprint', 64)->unique(); // SHA-256 hash
// Pattern metadata
$table->string('service', 50);
$table->string('component', 100);
$table->string('operation', 100);
$table->string('error_code', 20);
$table->text('normalized_message');
$table->string('severity', 20);
// Occurrence tracking
$table->integer('occurrence_count')->default(1);
$table->timestamp('first_occurrence');
$table->timestamp('last_occurrence');
// Impact tracking (JSON arrays)
$table->json('affected_users')->nullable(); // Array of user IDs
$table->json('affected_ips')->nullable(); // Array of IP addresses
// Pattern lifecycle
$table->boolean('is_active')->default(true);
$table->boolean('is_acknowledged')->default(false);
$table->string('acknowledged_by', 100)->nullable();
$table->timestamp('acknowledged_at')->nullable();
$table->text('resolution')->nullable();
// Additional metadata
$table->json('metadata')->nullable();
// Timestamps
$table->timestamp('created_at');
$table->timestamp('updated_at');
// Indexes for performance
$table->index(['service', 'is_active', 'last_occurrence']);
$table->index(['error_code', 'is_active']);
$table->index(['severity', 'is_active', 'occurrence_count']);
$table->index(['is_active', 'is_acknowledged']);
$table->index('last_occurrence'); // For cleanup queries
});
}
public function down(Schema $schema): void
{
$schema->drop('error_patterns');
}
};
Key Changes:
- ✅ Merged
error_reportsfields intoerror_events - ✅ Single source of truth für error storage
- ✅ Optimized indexes for common queries
- ✅ Retained all functionality from both tables
3.4 Backward Compatibility Strategy
Compatibility Layer: src/Framework/Exception/Compatibility/ErrorHandlerContextAdapter.php
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Compatibility;
use App\Framework\Exception\Core\ExceptionContext;
/**
* Adapter for old ErrorHandlerContext (backward compatibility)
*
* @deprecated Will be removed in next major version
*/
final readonly class ErrorHandlerContextAdapter
{
/**
* Convert old ErrorHandlerContext to new ExceptionContext
*/
public static function toExceptionContext($oldContext): ExceptionContext
{
// Map old context structure to new unified context
return ExceptionContext::empty()
->withOperation(
$oldContext->exception->operation ?? 'unknown',
$oldContext->exception->component ?? 'unknown'
)
->withData($oldContext->exception->data ?? [])
->withDebug($oldContext->exception->debug ?? [])
->withMetadata($oldContext->exception->metadata ?? [])
->withRequestContext(
$oldContext->request->requestId ?? '',
$oldContext->request->method ?? 'GET',
$oldContext->request->requestUri ?? '/',
$oldContext->request->clientIp ?? null,
$oldContext->request->userAgent ?? null,
$oldContext->request->userId ?? null
)
->withSystemContext(
$oldContext->system->environment ?? 'production',
$oldContext->system->hostname ?? 'unknown',
$oldContext->system->memoryUsageMb ?? 0.0,
$oldContext->system->cpuUsage ?? 0.0
);
}
}
Deprecation Notice: src/Framework/ErrorHandling/ErrorHandlerContext.php
<?php
namespace App\Framework\ErrorHandling;
/**
* @deprecated Use \App\Framework\Exception\Core\ExceptionContext instead
* Will be removed in next major version
*/
final readonly class ErrorHandlerContext
{
// Keep existing implementation for backward compatibility
// Add deprecation notice in constructor
public function __construct(...)
{
trigger_error(
'ErrorHandlerContext is deprecated. Use ExceptionContext instead.',
E_USER_DEPRECATED
);
// ... existing constructor logic
}
}
4. Performance Optimizations
4.1 Async Processing
Queue Integration for ErrorAggregation and ErrorReporting:
// In ErrorHandler::handleException()
if ($this->enableAggregation && !$this->shouldSkipAggregation($exception)) {
// Queue aggregation instead of synchronous processing
$this->queue->push('error_aggregation', [
'exception_class' => get_class($exception),
'exception_data' => $exception->toArray(),
'context' => $context->toArray(),
]);
}
if ($this->enableReporting && !$this->shouldSkipReporting($exception)) {
// Queue reporting instead of synchronous processing
$this->queue->push('error_reporting', [
'exception_class' => get_class($exception),
'exception_data' => $exception->toArray(),
'context' => $context->toArray(),
]);
}
Background Jobs:
ProcessErrorAggregationJob- Processes queued aggregationProcessErrorReportingJob- Processes queued reportsCleanupExpiredErrorEventsJob- Cleanup old error events
4.2 Caching Strategy
Pattern Cache (existing, enhanced):
// Cache error patterns with fingerprint key
$cacheKey = 'error_pattern:' . $fingerprint;
$this->cache->remember($cacheKey, fn() => $pattern, Duration::fromHours(1));
ErrorCode Severity Cache (new):
// Cache ErrorCode → ErrorSeverity mapping
private static array $severityCache = [];
public function getSeverity(): ErrorSeverity
{
if (isset(self::$severityCache[$this->value])) {
return self::$severityCache[$this->value];
}
$severity = match($this) { ... };
self::$severityCache[$this->value] = $severity;
return $severity;
}
4.3 Database Optimizations
Batch Inserts for error_events:
// Batch insert instead of individual inserts
public function storeEventsBatch(array $events): void
{
$data = array_map(fn($event) => $event->toArray(), $events);
$this->db->table('error_events')->insert($data);
}
Index Strategy:
- Composite indexes for common query patterns
- Separate indexes for cleanup queries (by occurred_at)
- JSON column indexes for metadata searches (PostgreSQL)
Partitioning (future optimization):
- Partition error_events by occurred_at (monthly partitions)
- Automatic partition management
- Faster cleanup queries
5. Testing Strategy
5.1 Unit Tests
Test Coverage Requirements: ≥95% for all core components
Priority Test Suites:
-
FrameworkExceptionTest.php
- Factory methods
- Fluent API
- Context enrichment
- Severity mapping
- Serialization
-
ExceptionContextTest.php
- Factory methods
- Fluent API for all context types
- Data sanitization
- Serialization
-
ErrorCodeTest.php
- Severity mapping for all 100+ codes
- Category extraction
- Recovery hints
- Retry timing
-
ErrorHandlerTest.php
- Automatic aggregation
- Automatic reporting
- Skip flags
- Backward compatibility
-
ErrorAggregatorTest.php
- Pattern detection
- Fingerprinting
- Alert triggering
- Batch processing
-
ErrorBoundaryIntegrationTest.php
- BoundaryException extends FrameworkException
- Automatic integration
- Circuit breaker
5.2 Integration Tests
End-to-End Error Flow:
it('handles exception end-to-end with automatic integration', function () {
// 1. Throw exception
$exception = FrameworkException::create(
ErrorCode::DB_QUERY_FAILED,
'Database query failed'
);
// 2. Error handler processes
$this->errorHandler->handleException($exception);
// 3. Verify aggregation triggered
expect($this->queue)->toHaveJob('error_aggregation');
// 4. Process aggregation job
$this->processQueue('error_aggregation');
// 5. Verify pattern created
$patterns = $this->errorAggregator->getActivePatterns();
expect($patterns)->toHaveCount(1);
expect($patterns[0]->errorCode)->toBe('DB002');
// 6. Verify reporting triggered
expect($this->queue)->toHaveJob('error_reporting');
// 7. Process reporting job
$this->processQueue('error_reporting');
// 8. Verify report created
$reports = $this->errorReporter->getRecentReports();
expect($reports)->toHaveCount(1);
});
5.3 Performance Tests
Benchmarks:
it('creates exception in <1ms', function () {
$start = microtime(true);
$exception = FrameworkException::create(
ErrorCode::DB_QUERY_FAILED,
'Test exception'
);
$duration = (microtime(true) - $start) * 1000;
expect($duration)->toBeLessThan(1.0);
});
it('processes 1000 errors in <5 seconds', function () {
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
$exception = FrameworkException::create(
ErrorCode::DB_QUERY_FAILED,
"Error {$i}"
);
$this->errorHandler->handleException($exception);
}
$duration = microtime(true) - $start;
expect($duration)->toBeLessThan(5.0);
});
6. Migration Checklist
Phase 1: Core Infrastructure
- Create new Exception/ directory structure
- Implement enhanced FrameworkException
- Implement unified ExceptionContext
- Enhance ErrorCode with severity mapping
- Create ErrorSeverity enum
- Write unit tests for core components
Phase 2: Handler Integration
- Implement integrated ErrorHandler
- Create ContextBuilder
- Create ResponseFactory
- Implement async queue integration
- Write integration tests
Phase 3: Aggregation Integration
- Enhance ErrorAggregator with processException()
- Enhance ErrorEvent with fromException()
- Update ErrorPattern (no changes needed)
- Migrate database schema
- Write aggregation tests
Phase 4: Reporting Integration
- Enhance ErrorReporter with reportException()
- Merge ErrorReport with ErrorEvent
- Update AnalyticsEngine
- Write reporting tests
Phase 5: Boundaries Integration
- Create BoundaryException extending FrameworkException
- Update BoundaryTimeoutException
- Update BoundaryFailedException
- Update ErrorBoundary to use new exceptions
- Write boundaries integration tests
Phase 6: Backward Compatibility
- Create ErrorHandlerContextAdapter
- Add deprecation notices
- Update documentation
- Create migration guide
Phase 7: Cleanup
- Remove old ErrorHandling/ directory
- Remove old ErrorAggregation/ directory
- Remove old ErrorBoundaries/ directory
- Remove old ErrorReporting/ directory
- Update all imports across codebase
7. Success Metrics
Before
- ❌ 4 separate error modules
- ❌ Manual integration required
- ❌ 3 database tables
- ❌ Fragmented context
- ❌ Inconsistent exception types
- ❌ ~2.000 lines across modules
After
- ✅ 1 unified Exception module
- ✅ Automatic integration
- ✅ 2 database tables (consolidated)
- ✅ Unified ExceptionContext
- ✅ All exceptions extend FrameworkException
- ✅ ~2.500 lines (enhanced functionality)
- ✅ <1ms exception creation
- ✅ <5ms pattern detection (async)
- ✅ <10ms error reporting (async)
8. Next Steps
- Review & Approval - Review this design document
- Migration Plan - Create detailed step-by-step migration plan
- Prototyping - Build proof-of-concept for critical components
- Testing Strategy - Define comprehensive test suite
- Implementation - Execute migration in phases
- Documentation - Update all framework documentation
- Release - Plan major version release
Status: Design Phase Complete ✅
Next Document: ERROR-HANDLING-MIGRATION-PLAN.md
Estimated Implementation: 3-5 days