# Error Handling & Debugging This guide covers error handling patterns and debugging strategies in the framework. ## Exception Handling All custom exceptions in the framework must extend `FrameworkException` to ensure consistent error handling, logging, and recovery mechanisms. ### The FrameworkException System The framework provides a sophisticated exception system with: - **ExceptionContext**: Rich context information for debugging - **ErrorCode**: Categorized error codes with recovery hints - **RetryAfter**: Support for recoverable operations - **Fluent Interface**: Easy context building ### Creating Custom Exceptions ```php namespace App\Domain\User\Exceptions; use App\Framework\Exception\FrameworkException; use App\Framework\Exception\Core\DatabaseErrorCode; use App\Framework\Exception\ExceptionContext; final class UserNotFoundException extends FrameworkException { public static function byId(UserId $id): self { return self::create( DatabaseErrorCode::ENTITY_NOT_FOUND, "User with ID '{$id->toString()}' not found" )->withData([ 'user_id' => $id->toString(), 'search_type' => 'by_id' ]); } public static function byEmail(Email $email): self { $context = ExceptionContext::forOperation('user.lookup', 'UserRepository') ->withData(['email' => $email->getMasked()]); return self::fromContext( "User with email not found", $context, DatabaseErrorCode::ENTITY_NOT_FOUND ); } } ``` ### Using ErrorCode Enums The framework provides category-specific error code enums for better organization and type safety: ```php use App\Framework\Exception\Core\DatabaseErrorCode; use App\Framework\Exception\Core\AuthErrorCode; use App\Framework\Exception\Core\HttpErrorCode; use App\Framework\Exception\Core\SecurityErrorCode; use App\Framework\Exception\Core\ValidationErrorCode; // Database errors DatabaseErrorCode::CONNECTION_FAILED DatabaseErrorCode::QUERY_FAILED DatabaseErrorCode::TRANSACTION_FAILED DatabaseErrorCode::CONSTRAINT_VIOLATION // Authentication errors AuthErrorCode::CREDENTIALS_INVALID AuthErrorCode::TOKEN_EXPIRED AuthErrorCode::SESSION_EXPIRED AuthErrorCode::ACCOUNT_LOCKED // HTTP errors HttpErrorCode::BAD_REQUEST HttpErrorCode::NOT_FOUND HttpErrorCode::METHOD_NOT_ALLOWED HttpErrorCode::RATE_LIMIT_EXCEEDED // Security errors SecurityErrorCode::CSRF_TOKEN_INVALID SecurityErrorCode::SQL_INJECTION_DETECTED SecurityErrorCode::XSS_DETECTED SecurityErrorCode::PATH_TRAVERSAL_DETECTED // Validation errors ValidationErrorCode::INVALID_INPUT ValidationErrorCode::REQUIRED_FIELD_MISSING ValidationErrorCode::BUSINESS_RULE_VIOLATION ValidationErrorCode::INVALID_FORMAT // Using error codes in exceptions: throw FrameworkException::create( DatabaseErrorCode::QUERY_FAILED, "Failed to execute user query" )->withContext( ExceptionContext::forOperation('user.find', 'UserRepository') ->withData(['query' => 'SELECT * FROM users WHERE id = ?']) ->withDebug(['bind_params' => [$userId]]) ); ``` ### Exception Context Building ```php // Method 1: Using factory methods $exception = FrameworkException::forOperation( 'payment.process', 'PaymentService', 'Payment processing failed', HttpErrorCode::BAD_GATEWAY )->withData([ 'amount' => $amount->toArray(), 'gateway' => 'stripe', 'customer_id' => $customerId ])->withMetadata([ 'attempt' => 1, 'idempotency_key' => $idempotencyKey ]); // Method 2: Building context separately $context = ExceptionContext::empty() ->withOperation('order.validate', 'OrderService') ->withData([ 'order_id' => $orderId, 'total' => $total->toDecimal() ]) ->withDebug([ 'validation_rules' => ['min_amount', 'max_items'], 'failed_rule' => 'min_amount' ]); throw FrameworkException::fromContext( 'Order validation failed', $context, ValidationErrorCode::BUSINESS_RULE_VIOLATION ); ``` ### Recoverable Exceptions ```php // Creating recoverable exceptions with retry hints final class RateLimitException extends FrameworkException { public static function exceeded(int $retryAfter): self { return self::create( HttpErrorCode::RATE_LIMIT_EXCEEDED, 'API rate limit exceeded' )->withRetryAfter($retryAfter) ->withData(['retry_after_seconds' => $retryAfter]); } } // Using in code try { $response = $apiClient->request($endpoint); } catch (RateLimitException $e) { if ($e->isRecoverable()) { $waitTime = $e->getRetryAfter(); // Schedule retry after $waitTime seconds } throw $e; } ``` ### Exception Categories ```php // Check exception category for handling strategies try { $result = $operation->execute(); } catch (FrameworkException $e) { if ($e->isCategory('AUTH')) { // Handle authentication errors return $this->redirectToLogin(); } if ($e->isCategory('VAL')) { // Handle validation errors return $this->validationErrorResponse($e); } if ($e->isErrorCode(DatabaseErrorCode::CONNECTION_FAILED)) { // Handle specific database connection errors $this->notifyOps($e); } throw $e; } ``` ### Simple Exceptions for Quick Use ```php // When you don't need the full context system throw FrameworkException::simple('Quick error message'); // With previous exception } catch (\PDOException $e) { throw FrameworkException::simple( 'Database operation failed', $e, 500 ); } ``` ### Exception Data Sanitization The framework automatically sanitizes sensitive data in exceptions: ```php // Sensitive keys are automatically redacted $exception->withData([ 'username' => 'john@example.com', 'password' => 'secret123', // Will be logged as '[REDACTED]' 'api_key' => 'sk_live_...' // Will be logged as '[REDACTED]' ]); ``` ### Best Practices 1. **Always extend FrameworkException** for custom exceptions 2. **Use ErrorCode enum** for categorizable errors 3. **Provide rich context** with operation, component, and data 4. **Use factory methods** for consistent exception creation 5. **Sanitize sensitive data** (automatic for common keys) 6. **Make exceptions domain-specific** (UserNotFoundException vs generic NotFoundException) 7. **Include recovery hints** for recoverable errors ## Unified Error Kernel Architecture The framework uses a **context-aware error handling system** centered around the `ErrorKernel` class that automatically detects execution context (CLI vs HTTP) and handles errors accordingly. ### ErrorKernel Overview **Location**: `src/Framework/ExceptionHandling/ErrorKernel.php` **Key Features**: - Automatic context detection (CLI vs HTTP) - Colored console output for CLI errors - HTTP Response objects for web errors - Integration with OWASP Security Event System - Unified error logging via LogReporter ```php use App\Framework\ExceptionHandling\ErrorKernel; final readonly class ErrorKernel { public function __construct( private ErrorRendererFactory $rendererFactory = new ErrorRendererFactory, private ?ExecutionContext $executionContext = null, private ?ConsoleOutput $consoleOutput = null ) {} /** * Context-aware exception handler * - CLI: Colored console output * - HTTP: Logs error (middleware creates response) */ public function handle(Throwable $e, array $context = []): mixed { // Automatic logging $log = new LogReporter(); $log->report($e); // Context-aware handling $executionContext = $this->executionContext ?? ExecutionContext::detect(); if ($executionContext->isCli()) { $this->handleCliException($e); return null; } // HTTP context - middleware will create response return null; } /** * Create HTTP Response from exception (for middleware recovery) */ public function createHttpResponse( Throwable $exception, ?ExceptionContextProvider $contextProvider = null, bool $isDebugMode = false ): Response { $renderer = new ResponseErrorRenderer($isDebugMode); return $renderer->createResponse($exception, $contextProvider); } } ``` ### CLI Error Handling **CliErrorHandler** registers global PHP error handlers for CLI context: ```php use App\Framework\ExceptionHandling\CliErrorHandler; use App\Framework\Console\ConsoleOutput; // Registration in AppBootstrapper $output = new ConsoleOutput(); $cliErrorHandler = new CliErrorHandler($output); $cliErrorHandler->register(); // Automatic colored output for errors: // - Red for uncaught exceptions // - Yellow for warnings // - Cyan for notices // - Full stack traces in CLI ``` ### HTTP Error Handling **ExceptionHandlingMiddleware** catches exceptions in HTTP request pipeline: ```php use App\Framework\Http\Middlewares\ExceptionHandlingMiddleware; #[MiddlewarePriorityAttribute(MiddlewarePriority::ERROR_HANDLING)] final readonly class ExceptionHandlingMiddleware implements HttpMiddleware { public function __invoke( MiddlewareContext $context, Next $next, RequestStateManager $stateManager ): MiddlewareContext { try { return $next($context); } catch (\Throwable $e) { // Log exception $this->logger->error('Unhandled exception in HTTP request', [ 'exception' => get_class($e), 'message' => $e->getMessage(), ]); // Create HTTP Response $errorKernel = new ErrorKernel(); $response = $errorKernel->createHttpResponse( $e, null, isDebugMode: false ); return $context->withResponse($response); } } } ``` ### OWASP Security Event Integration Exceptions can trigger OWASP security events for audit logging: ```php use App\Application\Security\OWASPSecurityEventLogger; use App\Application\Security\OWASPEventIdentifier; // Automatic security logging try { $this->authenticateUser($credentials); } catch (AuthenticationException $e) { // ErrorKernel logs exception $this->errorKernel->handle($e); // OWASP event for security audit trail $this->eventDispatcher->dispatch( new AuthenticationFailedEvent( OWASPEventIdentifier::AUTHN_LOGIN_FAILURE, $credentials->username, $e->getMessage() ) ); throw $e; } ``` ### Legacy ErrorHandling Module Removed **IMPORTANT**: The legacy `ErrorHandling` module (`src/Framework/ErrorHandling/`) has been **completely removed** as of the unified exception architecture migration. **Migration Path**: - All error handling now uses `ErrorKernel` and `FrameworkException` - CLI errors: `CliErrorHandler` → `ErrorKernel` - HTTP errors: `ExceptionHandlingMiddleware` → `ErrorKernel` - Security events: Direct event dispatch via `EventDispatcher` **Old Pattern** (removed): ```php // ❌ Legacy - NO LONGER EXISTS use App\Framework\ErrorHandling\ErrorHandler; use App\Framework\ErrorHandling\SecurityEventLogger; $errorHandler = new ErrorHandler(); $errorHandler->register(); ``` **New Pattern** (current): ```php // ✅ Unified - ErrorKernel use App\Framework\ExceptionHandling\ErrorKernel; $errorKernel = new ErrorKernel(); $errorKernel->handle($exception); ``` ## Logging Best Practices ### Automatic Exception Logging All exceptions handled by `ErrorKernel` are automatically logged via `LogReporter`: ```php // Automatic logging happens in ErrorKernel::handle() $log = new LogReporter(); $log->report($exception); // Logs include: // - Exception class and message // - Stack trace // - File and line number // - Context data ``` ### Manual Logging ```php use App\Framework\Logging\Logger; // Log exceptions with context try { $user = $this->userRepository->find($userId); } catch (UserNotFoundException $e) { Logger::error('User lookup failed', [ 'user_id' => $userId, 'exception' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); throw $e; } // Log levels Logger::debug('Debugging information'); Logger::info('Informational message'); Logger::warning('Warning condition'); Logger::error('Error condition'); Logger::critical('Critical failure'); ``` ## Debug Strategies ### Development vs Production **Development** (`APP_DEBUG=true`): - Full stack traces displayed - Detailed error messages - Debug data in responses - SQL query logging **Production** (`APP_DEBUG=false`): - Generic error messages - Stack traces hidden from users - Errors logged server-side - Security-safe responses ### Debugging Tools ```php // Enable debug mode in ErrorKernel $errorKernel = new ErrorKernel( executionContext: ExecutionContext::cli(), consoleOutput: new ConsoleOutput() ); // HTTP Response with debug mode $response = $errorKernel->createHttpResponse( $exception, $contextProvider, isDebugMode: true // Shows stack trace in response ); ``` ### Error Context Providers ```php use App\Framework\ExceptionHandling\Context\ExceptionContextProvider; // Attach custom context to exceptions $contextProvider = new ExceptionContextProvider(); $contextProvider->attachContext($exception, [ 'request_id' => $requestId, 'user_id' => $userId, 'operation' => 'payment.process' ]); $response = $errorKernel->createHttpResponse( $exception, $contextProvider, isDebugMode: false ); ``` ## Error Recovery Patterns ### Graceful Degradation ```php // Try primary service, fallback to secondary try { return $this->primaryCache->get($key); } catch (CacheException $e) { Logger::warning('Primary cache failed, using fallback', [ 'error' => $e->getMessage() ]); return $this->fallbackCache->get($key); } ``` ### Circuit Breaker Pattern ```php use App\Framework\Resilience\CircuitBreaker; $circuitBreaker = new CircuitBreaker( failureThreshold: 5, timeout: Duration::fromSeconds(60) ); try { return $circuitBreaker->call(function() { return $this->externalApi->request($endpoint); }); } catch (CircuitOpenException $e) { // Circuit is open - use cached response return $this->cachedResponse; } ``` ### Retry with Exponential Backoff ```php use App\Framework\Queue\ValueObjects\RetryStrategy; $retryStrategy = new ExponentialBackoffStrategy( maxAttempts: 3, baseDelaySeconds: 60 ); $attempt = 0; while ($attempt < $retryStrategy->getMaxAttempts()) { try { return $this->performOperation(); } catch (TransientException $e) { $attempt++; if (!$retryStrategy->shouldRetry($attempt)) { throw $e; } $delay = $retryStrategy->getDelay($attempt); sleep($delay->toSeconds()); } } ``` ## Common Error Scenarios ### 1. Database Connection Failure ```php try { $connection = $this->connectionPool->getConnection(); } catch (ConnectionException $e) { // Log error $this->errorKernel->handle($e); // Return cached data or error response return $this->getCachedData() ?? $this->errorResponse(); } ``` ### 2. Validation Errors ```php try { $user = User::create($email, $name); } catch (ValidationException $e) { // Return validation errors to user return new JsonResult([ 'errors' => $e->getErrors() ], status: Status::UNPROCESSABLE_ENTITY); } ``` ### 3. Authentication Failures ```php try { $user = $this->authenticator->authenticate($credentials); } catch (AuthenticationException $e) { // Log security event $this->eventDispatcher->dispatch( new AuthenticationFailedEvent($credentials->username) ); // Return 401 Unauthorized return new JsonResult([ 'error' => 'Invalid credentials' ], status: Status::UNAUTHORIZED); } ``` ### 4. Resource Not Found ```php try { $order = $this->orderRepository->find($orderId); } catch (OrderNotFoundException $e) { // Return 404 Not Found return new JsonResult([ 'error' => 'Order not found' ], status: Status::NOT_FOUND); } ``` ### 5. Rate Limit Exceeded ```php try { $this->rateLimiter->checkLimit($userId); } catch (RateLimitException $e) { // Return 429 Too Many Requests with retry hint return new JsonResult([ 'error' => 'Rate limit exceeded', 'retry_after' => $e->getRetryAfter() ], status: Status::TOO_MANY_REQUESTS); } ```