Circuit Breaker Pattern in Pure PHP
PHP Code Editor
Execution Result
Ready to execute
Click the "Run Script" button to see the output here
Description
A circuit breaker prevents a failing dependency from taking down your whole application. Without it, every request waits for the full timeout before failing, which exhausts connection pools and causes cascading failures. The circuit breaker detects repeated failures and short-circuits subsequent calls immediately, giving the upstream time to recover.
The breaker has three states. In CLOSED, calls go through normally and failures are counted. Once failureThreshold failures occur, it trips to OPEN: all calls are rejected instantly with an exception, no waiting. After recoveryTimeout seconds, it moves to HALF_OPEN and allows exactly one test call through. A success resets to CLOSED and clears the failure counter. A failure in HALF_OPEN trips back to OPEN immediately.
<?php
/**
* Circuit breaker with three states:
* CLOSED: normal operation, failures counted
* OPEN: all calls rejected immediately (fast fail)
* HALF_OPEN: one test call allowed to check if service recovered
*/
class CircuitBreaker
{
private const CLOSED = 'closed';
private const OPEN = 'open';
private const HALF_OPEN = 'half-open';
private string $state = self::CLOSED;
private int $failureCount = 0;
private float $openedAt = 0;
public function __construct(
private readonly int $failureThreshold = 3,
private readonly float $recoveryTimeout = 5.0, // seconds
) {}
public function call(callable $fn): mixed
{
if ($this->state === self::OPEN) {
if (microtime(true) - $this->openedAt >= $this->recoveryTimeout) {
$this->state = self::HALF_OPEN;
echo "[CircuitBreaker] State: HALF_OPEN, testing recovery\n";
} else {
throw new RuntimeException('Circuit is OPEN, request rejected');
}
}
try {
$result = $fn();
$this->onSuccess();
return $result;
} catch (Throwable $e) {
$this->onFailure();
throw $e;
}
}
public function getState(): string
{
return $this->state;
}
private function onSuccess(): void
{
$this->failureCount = 0;
$this->state = self::CLOSED;
echo "[CircuitBreaker] Success. State: CLOSED\n";
}
private function onFailure(): void
{
$this->failureCount++;
echo "[CircuitBreaker] Failure #{$this->failureCount}\n";
if ($this->state === self::HALF_OPEN || $this->failureCount >= $this->failureThreshold) {
$this->state = self::OPEN;
$this->openedAt = microtime(true);
echo "[CircuitBreaker] State: OPEN, tripping circuit\n";
}
}
}
// Demo: service that fails 4 times then recovers
$callCount = 0;
$cb = new CircuitBreaker(failureThreshold: 3, recoveryTimeout: 0.1);
$flakyService = function () use (&$callCount): string {
$callCount++;
if ($callCount <= 4) throw new RuntimeException("Service unavailable");
return "OK from service";
};
for ($i = 1; $i <= 8; $i++) {
echo "\nCall $i: ";
try {
$result = $cb->call($flakyService);
echo $result . "\n";
} catch (RuntimeException $e) {
echo "FAILED: " . $e->getMessage() . "\n";
}
// Wait briefly so recovery timeout triggers
if ($i === 5) usleep(150_000);
}
Comments
No comments yet
Be the first to share your thoughts!