ejsmont-artur/php-circuit-breaker-bundle
composer require ejsmont-artur/php-circuit-breaker-bundle
config/bundles.php:
return [
// ...
Ejsmont\CircuitBreakerBundle\EjsmontCircuitBreakerBundle::class => ['all' => true],
];
# config/packages/ejsmont_circuit_breaker.yaml
ejsmont_circuit_breaker:
cache: cache.app # Default Symfony cache service
use Ejsmont\CircuitBreaker\CircuitBreaker;
use Ejsmont\CircuitBreaker\CircuitBreakerException;
class PaymentService
{
private $circuitBreaker;
private $paymentGateway;
public function __construct(
CircuitBreaker $circuitBreaker,
PaymentGateway $paymentGateway
) {
$this->circuitBreaker = $circuitBreaker;
$this->paymentGateway = $paymentGateway;
}
public function processPayment(float $amount)
{
$this->circuitBreaker->execute(
'payment_gateway', // Unique identifier for the circuit
function () use ($amount) {
return $this->paymentGateway->charge($amount);
},
3, // Max retries
1000 // Timeout in ms
);
}
}
CircuitBreaker service directly into controllers/services.external_api, database_replica) for clarity.ejsmont_circuit_breaker:
circuits:
payment_gateway:
max_retries: 3
timeout: 1000
fallback: App\Service\PaymentFallbackService
$result = $this->circuitBreaker->execute(
'api_call',
fn() => $externalApi->fetchData(),
2,
500,
fn() => $this->fallbackService->getCachedData()
);
Ejsmont\CircuitBreaker\FallbackInterface.circuit_breaker.state_changed events.
// src/EventListener/CircuitBreakerListener.php
public function onStateChanged(CircuitBreakerEvent $event)
{
if ($event->isOpen()) {
$this->logger->warning('Circuit opened for: '.$event->getName());
}
}
Register in services.yaml:
services:
App\EventListener\CircuitBreakerListener:
tags:
- { name: kernel.event_listener, event: circuit_breaker.state_changed }
CircuitBreakerFactory to create circuits dynamically.
$factory = $container->get('ejsmont_circuit_breaker.factory');
$circuit = $factory->create('dynamic_circuit', [
'max_retries' => $this->getMaxRetriesFromConfig(),
]);
CircuitBreakerMock for unit tests.
$mock = new CircuitBreakerMock('test_circuit');
$mock->setState(CircuitBreaker::STATE_OPEN);
$this->service->setCircuitBreaker($mock);
Cache Dependency:
cache in ejsmont_circuit_breaker.yaml.cache.app or a dedicated cache pool (e.g., cache.doctrine).Timeout Handling:
timeout for slow dependencies.0 for no timeout (not recommended for external calls).State Persistence:
CLOSED after reset_timeout (default: 60s). Override in config:
ejsmont_circuit_breaker:
reset_timeout: 300 # 5 minutes
Fallback Misuse:
Symfony 3+ Compatibility:
service.xml is loaded via config/packages/ejsmont_circuit_breaker.xml.ejsmont_circuit_breaker:
logger: true # Enables logging via Symfony's logger
$state = $this->circuitBreaker->getState('circuit_name');
// $state->isOpen(), $state->getFailureCount(), etc.
$this->circuitBreaker->reset('circuit_name');
Custom States:
Ejsmont\CircuitBreaker\State to add custom logic (e.g., HALF_OPEN with custom retry rules).Metrics Integration:
circuit_breaker.state_changed.Async Support:
$message = new PaymentMessage($amount);
$this->bus->dispatch($message);
// Circuit breaker wraps the message handler.
Rate Limiting:
max_retries to indirectly limit request rates (e.g., max_retries: 1 for strict rate limiting).How can I help you explore Laravel packages today?