*/ private StateManager $stateManager, private ?Logger $logger = null, ) { } /** * Check if circuit is open for given boundary */ public function isCircuitOpen(string $boundaryName, BoundaryConfig $config): bool { $state = $this->getCircuitState($boundaryName); if ($state->isOpen()) { // Check if circuit should transition to half-open if ($this->shouldTransitionToHalfOpen($state, $config)) { $newState = $state->transitionToHalfOpen(); $this->stateManager->setState($boundaryName, $newState); $this->log('info', "Circuit transitioned to HALF_OPEN for boundary: {$boundaryName}"); return false; // Allow operations in half-open state } return true; } return false; } /** * Record a successful operation */ public function recordSuccess(string $boundaryName, BoundaryConfig $config): void { $newState = $this->stateManager->updateState( $boundaryName, function (?BoundaryCircuitBreakerState $currentState) use ($config, $boundaryName): BoundaryCircuitBreakerState { $state = $currentState ?? new BoundaryCircuitBreakerState(); $newState = $state->recordSuccess(); // If in half-open state and enough successes, close the circuit if ($state->isHalfOpen() && $newState->meetsSuccessThreshold($config->successThreshold ?? 3)) { $newState = $newState->transitionToClosed(); $this->log('info', "Circuit transitioned to CLOSED for boundary: {$boundaryName}"); } return $newState; } ); } /** * Record a failed operation */ public function recordFailure(string $boundaryName, BoundaryConfig $config): void { $newState = $this->stateManager->updateState( $boundaryName, function (?BoundaryCircuitBreakerState $currentState) use ($config, $boundaryName): BoundaryCircuitBreakerState { $state = $currentState ?? new BoundaryCircuitBreakerState(); $newState = $state->recordFailure(); // Check if failure threshold is exceeded if ($newState->exceedsFailureThreshold($config->circuitBreakerThreshold)) { $newState = $newState->transitionToOpen(); $this->log('warning', "Circuit transitioned to OPEN for boundary: {$boundaryName} after {$config->circuitBreakerThreshold} failures"); } // If in half-open state, any failure should open the circuit if ($state->isHalfOpen()) { $newState = $newState->transitionToOpen(); $this->log('warning', "Circuit transitioned to OPEN for boundary: {$boundaryName} due to failure in HALF_OPEN state"); } return $newState; } ); } /** * Get current circuit state */ public function getCircuitState(string $boundaryName): BoundaryCircuitBreakerState { return $this->stateManager->getState($boundaryName) ?? new BoundaryCircuitBreakerState(); } /** * Reset circuit breaker state */ public function resetCircuit(string $boundaryName): void { $newState = new BoundaryCircuitBreakerState(); // Default closed state $this->stateManager->setState($boundaryName, $newState); $this->log('info', "Circuit reset for boundary: {$boundaryName}"); } /** * Get all circuit breaker states */ public function getAllCircuitStates(): array { $states = $this->stateManager->getAllStates(); $result = []; foreach ($states as $boundaryName => $state) { $result[$boundaryName] = $this->getCircuitHealth($boundaryName); } return $result; } /** * Check circuit health for monitoring */ public function getCircuitHealth(string $boundaryName): array { $state = $this->getCircuitState($boundaryName); return [ 'boundary_name' => $boundaryName, 'state' => $state->state->value, 'state_description' => $state->state->getDescription(), 'failure_count' => $state->failureCount, 'success_count' => $state->successCount, 'last_failure_time' => $state->lastFailureTime?->toIsoString(), 'opened_at' => $state->openedAt?->toIsoString(), 'half_open_attempts' => $state->halfOpenAttempts, 'is_healthy' => $state->isClosed(), 'severity' => $state->state->getSeverity(), ]; } /** * Get state manager statistics */ public function getStatistics(): array { return $this->stateManager->getStatistics()->toArray(); } /** * Increment half-open attempts */ public function incrementHalfOpenAttempts(string $boundaryName): void { $this->stateManager->updateState( $boundaryName, function (?BoundaryCircuitBreakerState $currentState): BoundaryCircuitBreakerState { $state = $currentState ?? new BoundaryCircuitBreakerState(); return $state->incrementHalfOpenAttempts(); } ); } private function shouldTransitionToHalfOpen(BoundaryCircuitBreakerState $state, BoundaryConfig $config): bool { if (! $state->isOpen() || $state->openedAt === null) { return false; } $timeSinceOpened = Timestamp::now()->diff($state->openedAt); return $timeSinceOpened->isGreaterThan($config->circuitBreakerTimeout); } private function log(string $level, string $message, array $context = []): void { if ($this->logger === null) { return; } $context['component'] = 'BoundaryCircuitBreakerManager'; match ($level) { 'debug' => $this->logger->debug("[ErrorBoundary] {$message}", $context), 'info' => $this->logger->info("[ErrorBoundary] {$message}", $context), 'warning' => $this->logger->warning("[ErrorBoundary] {$message}", $context), 'error' => $this->logger->error("[ErrorBoundary] {$message}", $context), default => $this->logger->info("[ErrorBoundary] {$message}", $context), }; } }