🐘 🛡️ Advanced Error Handling with TuskLang & PHP
🛡️ Advanced Error Handling with TuskLang & PHP
Introduction
Error handling is the safety net that keeps applications running smoothly. TuskLang and PHP let you implement sophisticated error handling with config-driven exception management, error recovery, graceful degradation, and intelligent error reporting that ensures applications remain resilient and user-friendly.Key Features
- Comprehensive exception handling - Error recovery strategies - Graceful degradation - Error monitoring and reporting - Circuit breaker patterns - Retry mechanismsExample: Error Handling Configuration
[error_handling]
exceptions: @go("error.ConfigureExceptions")
recovery: @go("error.ConfigureRecovery")
monitoring: @go("error.ConfigureMonitoring")
degradation: @go("error.ConfigureDegradation")
PHP: Error Handler Implementation
<?phpnamespace App\ErrorHandling;
use TuskLang\Config;
use TuskLang\Operators\Env;
use TuskLang\Operators\Metrics;
use TuskLang\Operators\Go;
class ErrorHandler
{
private $config;
private $monitor;
private $recovery;
private $circuitBreaker;
public function __construct()
{
$this->config = new Config();
$this->monitor = new ErrorMonitor();
$this->recovery = new ErrorRecovery();
$this->circuitBreaker = new CircuitBreaker();
$this->setupErrorHandlers();
}
public function handleException(\Throwable $exception, $context = [])
{
$startTime = microtime(true);
try {
// Log the exception
$this->logException($exception, $context);
// Record metrics
$this->recordErrorMetrics($exception);
// Check if recovery is possible
if ($this->canRecover($exception)) {
return $this->recovery->attemptRecovery($exception, $context);
}
// Check circuit breaker
if ($this->circuitBreaker->isOpen($exception)) {
return $this->handleCircuitBreakerOpen($exception);
}
// Attempt graceful degradation
if ($this->shouldDegrade($exception)) {
return $this->degradeGracefully($exception, $context);
}
// Generate user-friendly error
return $this->generateUserError($exception);
} catch (\Exception $e) {
// Fallback error handling
return $this->handleFallbackError($e);
} finally {
$duration = (microtime(true) - $startTime) * 1000;
Metrics::record("error_handling_duration", $duration, [
"exception_type" => get_class($exception)
]);
}
}
public function handleError($error, $context = [])
{
$exception = new \ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']);
return $this->handleException($exception, $context);
}
public function handleFatalError($error)
{
$exception = new \ErrorException($error['message'], 0, E_ERROR, $error['file'], $error['line']);
// Fatal errors require immediate attention
$this->sendCriticalAlert($exception);
return $this->handleException($exception, ['fatal' => true]);
}
private function setupErrorHandlers()
{
// Set up exception handler
set_exception_handler([$this, 'handleException']);
// Set up error handler
set_error_handler([$this, 'handleError']);
// Set up fatal error handler
register_shutdown_function([$this, 'handleFatalError']);
// Set up shutdown function to catch fatal errors
register_shutdown_function(function() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
$this->handleFatalError($error);
}
});
}
private function logException(\Throwable $exception, $context = [])
{
$logData = [
'exception' => get_class($exception),
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTraceAsString(),
'context' => $context,
'timestamp' => date('c'),
'request_id' => $this->getRequestId(),
'user_id' => $this->getCurrentUserId()
];
$this->monitor->logError($logData);
}
private function recordErrorMetrics(\Throwable $exception)
{
$exceptionType = get_class($exception);
$severity = $this->getExceptionSeverity($exception);
Metrics::record("exceptions_total", 1, [
"type" => $exceptionType,
"severity" => $severity
]);
// Record error rate
Metrics::record("error_rate", 1, [
"type" => $exceptionType
]);
}
private function canRecover(\Throwable $exception)
{
$recoverableExceptions = $this->config->get('error_handling.recoverable_exceptions', []);
foreach ($recoverableExceptions as $recoverable) {
if ($exception instanceof $recoverable['class']) {
return $recoverable['recoverable'] ?? false;
}
}
return false;
}
private function shouldDegrade(\Throwable $exception)
{
$degradationRules = $this->config->get('error_handling.degradation_rules', []);
foreach ($degradationRules as $rule) {
if ($exception instanceof $rule['exception_class']) {
return $rule['should_degrade'] ?? false;
}
}
return false;
}
private function degradeGracefully(\Throwable $exception, $context)
{
$degradationStrategy = $this->config->get('error_handling.degradation_strategy', 'fallback');
switch ($degradationStrategy) {
case 'fallback':
return $this->useFallbackData($context);
case 'cached':
return $this->useCachedData($context);
case 'default':
return $this->useDefaultResponse($context);
case 'partial':
return $this->usePartialResponse($context);
default:
return $this->useFallbackData($context);
}
}
private function generateUserError(\Throwable $exception)
{
$errorConfig = $this->config->get('error_handling.user_errors', []);
$exceptionType = get_class($exception);
if (isset($errorConfig[$exceptionType])) {
$config = $errorConfig[$exceptionType];
return [
'error' => $config['message'],
'code' => $config['code'],
'user_friendly' => true
];
}
// Default error response
return [
'error' => 'An unexpected error occurred. Please try again later.',
'code' => 'INTERNAL_ERROR',
'user_friendly' => true
];
}
private function handleCircuitBreakerOpen(\Throwable $exception)
{
$fallbackResponse = $this->config->get('error_handling.circuit_breaker.fallback_response', []);
return [
'error' => $fallbackResponse['message'] ?? 'Service temporarily unavailable',
'code' => 'SERVICE_UNAVAILABLE',
'retry_after' => $this->circuitBreaker->getRetryAfter($exception)
];
}
private function handleFallbackError(\Exception $exception)
{
// Ultimate fallback - log and return generic error
$this->monitor->logError([
'exception' => get_class($exception),
'message' => 'Error in error handler: ' . $exception->getMessage(),
'timestamp' => date('c')
]);
return [
'error' => 'System error. Please contact support.',
'code' => 'SYSTEM_ERROR',
'user_friendly' => true
];
}
private function sendCriticalAlert(\Throwable $exception)
{
$alertManager = new AlertManager();
$alertManager->sendCritical([
'type' => 'fatal_error',
'exception' => get_class($exception),
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine()
]);
}
private function getExceptionSeverity(\Throwable $exception)
{
$severityMap = $this->config->get('error_handling.severity_map', []);
$exceptionType = get_class($exception);
return $severityMap[$exceptionType] ?? 'medium';
}
private function getRequestId()
{
return $_SERVER['HTTP_X_REQUEST_ID'] ?? uniqid();
}
private function getCurrentUserId()
{
// Get current user ID from session or context
return $_SESSION['user_id'] ?? null;
}
}
class ErrorRecovery
{
private $config;
private $strategies = [];
public function __construct()
{
$this->config = new Config();
$this->loadStrategies();
}
public function attemptRecovery(\Throwable $exception, $context = [])
{
$recoveryStrategy = $this->getRecoveryStrategy($exception);
if (!$recoveryStrategy) {
return null;
}
try {
return $recoveryStrategy->recover($exception, $context);
} catch (\Exception $e) {
// Recovery failed
$this->logRecoveryFailure($exception, $e);
return null;
}
}
private function getRecoveryStrategy(\Throwable $exception)
{
$exceptionType = get_class($exception);
foreach ($this->strategies as $strategy) {
if ($strategy->canHandle($exceptionType)) {
return $strategy;
}
}
return null;
}
private function loadStrategies()
{
$strategies = $this->config->get('error_handling.recovery_strategies', []);
foreach ($strategies as $strategy) {
$this->strategies[] = new $strategy'class';
}
}
private function logRecoveryFailure(\Throwable $originalException, \Exception $recoveryException)
{
$logger = new Logger();
$logger->error('Recovery failed', [
'original_exception' => get_class($originalException),
'original_message' => $originalException->getMessage(),
'recovery_exception' => get_class($recoveryException),
'recovery_message' => $recoveryException->getMessage()
]);
}
}
class RetryRecoveryStrategy
{
private $config;
private $maxRetries;
private $backoffMultiplier;
public function __construct($config)
{
$this->config = $config;
$this->maxRetries = $config['max_retries'] ?? 3;
$this->backoffMultiplier = $config['backoff_multiplier'] ?? 2;
}
public function canHandle($exceptionType)
{
$retryableExceptions = $this->config['retryable_exceptions'] ?? [];
return in_array($exceptionType, $retryableExceptions);
}
public function recover(\Throwable $exception, $context = [])
{
$operation = $context['operation'] ?? null;
if (!$operation) {
return null;
}
for ($attempt = 1; $attempt <= $this->maxRetries; $attempt++) {
try {
$result = $operation();
if ($result !== null) {
return $result;
}
} catch (\Exception $e) {
if ($attempt === $this->maxRetries) {
throw $e;
}
// Wait before retry
$delay = $this->calculateDelay($attempt);
usleep($delay * 1000000); // Convert to microseconds
}
}
return null;
}
private function calculateDelay($attempt)
{
return pow($this->backoffMultiplier, $attempt - 1);
}
}
class FallbackRecoveryStrategy
{
private $config;
public function __construct($config)
{
$this->config = $config;
}
public function canHandle($exceptionType)
{
$fallbackExceptions = $this->config['fallback_exceptions'] ?? [];
return in_array($exceptionType, $fallbackExceptions);
}
public function recover(\Throwable $exception, $context = [])
{
$fallbackData = $this->config['fallback_data'] ?? [];
$contextKey = $context['context'] ?? 'default';
return $fallbackData[$contextKey] ?? $fallbackData['default'] ?? null;
}
}
Circuit Breaker Pattern
<?phpnamespace App\ErrorHandling\CircuitBreaker;
use TuskLang\Config;
class CircuitBreaker
{
private $config;
private $state = 'closed';
private $failureCount = 0;
private $lastFailureTime = null;
private $threshold;
private $timeout;
public function __construct()
{
$this->config = new Config();
$this->threshold = $this->config->get('error_handling.circuit_breaker.threshold', 5);
$this->timeout = $this->config->get('error_handling.circuit_breaker.timeout', 60);
}
public function isOpen(\Throwable $exception)
{
$this->recordFailure($exception);
if ($this->state === 'open') {
if ($this->shouldAttemptReset()) {
$this->setHalfOpen();
}
return true;
}
if ($this->failureCount >= $this->threshold) {
$this->setOpen();
return true;
}
return false;
}
public function recordSuccess()
{
$this->failureCount = 0;
$this->state = 'closed';
}
public function getRetryAfter(\Throwable $exception)
{
if ($this->state === 'open') {
$elapsed = time() - $this->lastFailureTime;
return max(0, $this->timeout - $elapsed);
}
return 0;
}
private function recordFailure(\Throwable $exception)
{
$this->failureCount++;
$this->lastFailureTime = time();
// Record metrics
Metrics::record("circuit_breaker_failures", 1, [
"exception_type" => get_class($exception)
]);
}
private function setOpen()
{
$this->state = 'open';
$this->lastFailureTime = time();
// Record metrics
Metrics::record("circuit_breaker_opened", 1);
// Send alert
$this->sendCircuitBreakerAlert();
}
private function setHalfOpen()
{
$this->state = 'half-open';
// Record metrics
Metrics::record("circuit_breaker_half_open", 1);
}
private function shouldAttemptReset()
{
return (time() - $this->lastFailureTime) >= $this->timeout;
}
private function sendCircuitBreakerAlert()
{
$alertManager = new AlertManager();
$alertManager->send([
'type' => 'circuit_breaker_opened',
'failure_count' => $this->failureCount,
'threshold' => $this->threshold,
'timeout' => $this->timeout
]);
}
}
class CircuitBreakerDecorator
{
private $circuitBreaker;
private $operation;
public function __construct(CircuitBreaker $circuitBreaker, callable $operation)
{
$this->circuitBreaker = $circuitBreaker;
$this->operation = $operation;
}
public function execute()
{
if ($this->circuitBreaker->isOpen(new \Exception('Circuit breaker check'))) {
throw new CircuitBreakerOpenException('Circuit breaker is open');
}
try {
$result = call_user_func($this->operation);
$this->circuitBreaker->recordSuccess();
return $result;
} catch (\Exception $e) {
$this->circuitBreaker->isOpen($e);
throw $e;
}
}
}
Error Monitoring
<?phpnamespace App\ErrorHandling\Monitoring;
use TuskLang\Config;
class ErrorMonitor
{
private $config;
private $storage;
private $alerts = [];
public function __construct()
{
$this->config = new Config();
$this->storage = $this->getStorage();
$this->loadAlerts();
}
public function logError($errorData)
{
// Store error
$this->storage->store($errorData);
// Check for alerts
$this->checkAlerts($errorData);
// Update metrics
$this->updateMetrics($errorData);
}
public function getErrorStats($timeRange = '1h')
{
return $this->storage->getStats($timeRange);
}
public function getRecentErrors($limit = 100)
{
return $this->storage->getRecent($limit);
}
public function searchErrors($query, $filters = [])
{
return $this->storage->search($query, $filters);
}
private function getStorage()
{
$type = $this->config->get('error_handling.monitoring.storage', 'elasticsearch');
switch ($type) {
case 'elasticsearch':
return new ElasticsearchStorage($this->config);
case 'database':
return new DatabaseStorage($this->config);
case 'file':
return new FileStorage($this->config);
default:
throw new \Exception("Unknown error storage: {$type}");
}
}
private function loadAlerts()
{
$alerts = $this->config->get('error_handling.monitoring.alerts', []);
foreach ($alerts as $alert) {
$this->alerts[] = new ErrorAlert($alert);
}
}
private function checkAlerts($errorData)
{
foreach ($this->alerts as $alert) {
if ($alert->shouldTrigger($errorData)) {
$alert->trigger($errorData);
}
}
}
private function updateMetrics($errorData)
{
$exceptionType = $errorData['exception'];
$severity = $this->getErrorSeverity($errorData);
Metrics::record("errors_total", 1, [
"type" => $exceptionType,
"severity" => $severity
]);
// Update error rate
Metrics::record("error_rate", 1, [
"type" => $exceptionType
]);
}
private function getErrorSeverity($errorData)
{
$severityMap = $this->config->get('error_handling.severity_map', []);
$exceptionType = $errorData['exception'];
return $severityMap[$exceptionType] ?? 'medium';
}
}
class ErrorAlert
{
private $config;
private $triggered = false;
private $lastTriggered = null;
public function __construct($config)
{
$this->config = $config;
}
public function shouldTrigger($errorData)
{
// Check if alert is already triggered
if ($this->triggered && $this->isWithinCooldown()) {
return false;
}
// Check conditions
foreach ($this->config['conditions'] as $condition) {
if (!$this->evaluateCondition($condition, $errorData)) {
return false;
}
}
return true;
}
public function trigger($errorData)
{
$this->triggered = true;
$this->lastTriggered = time();
$alertManager = new AlertManager();
$alertManager->send($this->config, $errorData);
}
private function evaluateCondition($condition, $errorData)
{
switch ($condition['type']) {
case 'exception_type':
return $errorData['exception'] === $condition['value'];
case 'message_contains':
return strpos($errorData['message'], $condition['value']) !== false;
case 'error_count':
return $this->getErrorCount($condition['time_window']) >= $condition['value'];
case 'severity':
return $errorData['severity'] === $condition['value'];
default:
return false;
}
}
private function isWithinCooldown()
{
$cooldown = $this->config['cooldown'] ?? 300; // 5 minutes
return (time() - $this->lastTriggered) < $cooldown;
}
private function getErrorCount($timeWindow)
{
// Implementation to get error count in time window
return 0; // Placeholder
}
}
Graceful Degradation
<?phpnamespace App\ErrorHandling\Degradation;
use TuskLang\Config;
class GracefulDegradation
{
private $config;
private $strategies = [];
public function __construct()
{
$this->config = new Config();
$this->loadStrategies();
}
public function degrade($service, $context = [])
{
$strategy = $this->getStrategy($service);
if (!$strategy) {
return $this->getDefaultResponse($service);
}
return $strategy->degrade($context);
}
public function useFallbackData($context)
{
$fallbackData = $this->config->get('error_handling.degradation.fallback_data', []);
$contextKey = $context['context'] ?? 'default';
return $fallbackData[$contextKey] ?? $fallbackData['default'] ?? null;
}
public function useCachedData($context)
{
$cache = new CacheManager();
$cacheKey = $context['cache_key'] ?? 'default';
return $cache->get($cacheKey);
}
public function useDefaultResponse($context)
{
$defaultResponses = $this->config->get('error_handling.degradation.default_responses', []);
$contextKey = $context['context'] ?? 'default';
return $defaultResponses[$contextKey] ?? $defaultResponses['default'] ?? null;
}
public function usePartialResponse($context)
{
$partialData = $context['partial_data'] ?? [];
// Return partial data with degradation notice
return [
'data' => $partialData,
'degraded' => true,
'message' => 'Some data may be incomplete due to service issues'
];
}
private function loadStrategies()
{
$strategies = $this->config->get('error_handling.degradation.strategies', []);
foreach ($strategies as $service => $strategy) {
$this->strategies[$service] = new $strategy'class';
}
}
private function getStrategy($service)
{
return $this->strategies[$service] ?? null;
}
private function getDefaultResponse($service)
{
return [
'error' => 'Service temporarily unavailable',
'code' => 'SERVICE_UNAVAILABLE',
'service' => $service
];
}
}
class DatabaseDegradationStrategy
{
private $config;
public function __construct($config)
{
$this->config = $config;
}
public function degrade($context)
{
$query = $context['query'] ?? null;
if (!$query) {
return $this->getDefaultResponse();
}
// Try to use read replica
if ($this->hasReadReplica()) {
return $this->useReadReplica($query);
}
// Use cached data
if ($this->hasCachedData($query)) {
return $this->useCachedData($query);
}
// Return partial data
return $this->getPartialData($query);
}
private function hasReadReplica()
{
// Check if read replica is available
return false; // Placeholder
}
private function useReadReplica($query)
{
// Execute query on read replica
return null; // Placeholder
}
private function hasCachedData($query)
{
$cache = new CacheManager();
return $cache->has($this->getCacheKey($query));
}
private function useCachedData($query)
{
$cache = new CacheManager();
$data = $cache->get($this->getCacheKey($query));
return [
'data' => $data,
'cached' => true,
'message' => 'Data from cache (database unavailable)'
];
}
private function getPartialData($query)
{
// Return partial data based on query type
return [
'data' => [],
'partial' => true,
'message' => 'Limited data available'
];
}
private function getCacheKey($query)
{
return 'db_cache:' . md5($query);
}
private function getDefaultResponse()
{
return [
'error' => 'Database service unavailable',
'code' => 'DB_UNAVAILABLE'
];
}
}