clue/connection-manager-extra
Extra connector decorators for ReactPHP Socket. Wrap ConnectorInterface to add retries, timeouts, delays, rejection rules, swapping, consecutive/random selection, concurrency limits and selective routing—without changing your async connect() code.
composer require clue/connection-manager-extra
Socket):
use React\Socket\Connector;
use React\EventLoop\Factory;
$loop = Factory::create();
$connector = new Connector($loop);
ConnectionManagerTimeout):
use ConnectionManager\Extra\ConnectionManagerTimeout;
$timeoutConnector = new ConnectionManagerTimeout($connector, 3.0);
$timeoutConnector->connect('example.com:80')->then(
function ($stream) {
$stream->write("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n");
$stream->end();
},
function ($exception) {
Log::error("Connection failed: " . $exception->getMessage());
}
);
use ConnectionManager\Extra\ConnectionManagerRepeat;
$retryConnector = new ConnectionManagerRepeat($connector, 3); // 3 total tries
$retryConnector->connect('unreliable-service:80')->then(...);
Wrap base connectors with decorators to handle edge cases:
// Timeout + Retry
$resilientConnector = new ConnectionManagerRepeat(
new ConnectionManagerTimeout($connector, 2.0), // 2s timeout
3 // 3 total tries
);
// Delay + Timeout
$delayedConnector = new ConnectionManagerTimeout(
new ConnectionManagerDelay($connector, 1.0), // 1s delay
5.0 // 5s total timeout
);
SelectiveRoute connections based on URI patterns (e.g., block ads, delay HTTP):
$rejectAds = new ConnectionManagerReject('Ads blocked');
$delayHttp = new ConnectionManagerDelay($connector, 2.0);
$selective = new ConnectionManagerSelective([
'ads.example.com' => $rejectAds,
'*.example.com:80' => $delayHttp, // Delay HTTP only
'*' => $connector // Default fallback
]);
Use Concurrent to try multiple connectors simultaneously:
$primary = new ConnectionManagerTimeout($connector, 1.0);
$fallback = new ConnectionManagerTimeout($fallbackConnector, 3.0);
$concurrent = new ConnectionManagerConcurrent([$primary, $fallback]);
Wrap connectors in a Laravel job for async processing:
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class AsyncConnectionJob implements ShouldQueue
{
use Queueable;
public function handle()
{
$this->connectWithRetry();
}
protected function connectWithRetry()
{
$retryConnector = new ConnectionManagerRepeat($this->connector, 3);
$retryConnector->connect('api.example.com:443')->then(
fn($stream) => $this->processStream($stream),
fn($e) => Log::error($e->getMessage())
);
}
}
Use Swappable to change connectors at runtime (e.g., for A/B testing):
$swappable = new ConnectionManagerSwappable($connector);
$swappable->setConnectionManager($newConnector); // Swap dynamically
URI Matching in Selective:
*) match exact hostnames (e.g., *.youtube.com won’t match www.youtube.com).$selective = new ConnectionManagerSelective([
'youtube.com' => $connector,
'*.youtube.com' => $connector,
'*' => $fallback
]);
https://example.com) are stripped before matching. Use raw host:port (e.g., example.com:443).Timeout vs. Delay:
ConnectionManagerTimeout sets a hard limit (e.g., fail after 3s).ConnectionManagerDelay adds a pre-connection wait (e.g., delay by 1s).Delay when you meant Timeout can cause silent hangs.Loop Dependency:
Timeout/Delay require a ReactPHP loop if not using the default.$loop explicitly or rely on the default:
// Old (explicit loop)
new ConnectionManagerTimeout($connector, 3.0, $loop);
// New (default loop)
new ConnectionManagerTimeout($connector, 3.0);
Retry Logic:
ConnectionManagerRepeat counts total tries (not retries). A value of 3 means:
Concurrent Connections:
ConnectionManagerConcurrent fires all connectors at once and resolves on the first success.$concurrent = new ConnectionManagerConcurrent([$conn1, $conn2], 2); // Max 2 parallel
Log Connection Attempts: Wrap decorators to log activity:
$debugConnector = new class($connector) implements ConnectorInterface {
public function connect($uri) {
Log::debug("Attempting to connect to: {$uri}");
return $this->connector->connect($uri);
}
};
Simulate Failures:
Use ConnectionManagerReject for testing:
$testConnector = new ConnectionManagerReject('Simulated failure');
Check for Hanging Connections:
ConnectionManagerTimeout with a short duration (e.g., 0.1) to catch hangs early.strace or tcpdump to verify if connections are attempted.Validate URI Patterns:
Selective rules with edge cases:
$selective = new ConnectionManagerSelective(['example.com:80-81' => $connector]);
$selective->connect('example.com:80'); // Should match
$selective->connect('example.com:82'); // Should fail
Custom Rejection Logic:
Extend ConnectionManagerReject to add domain-specific rules:
$customReject = new ConnectionManagerReject(function ($uri) {
if (str_contains($uri, 'malicious.com')) {
throw new \RuntimeException("Malicious domain detected");
}
});
Dynamic Decorator Chaining: Build a DSL for connector composition:
class ConnectorBuilder {
public function withTimeout($seconds) { ... }
public function withRetry($tries) { ... }
public function build() { ... }
}
Monitor Connection Metrics: Decorate connectors to track success/failure rates:
$metricsConnector = new class($connector) implements ConnectorInterface {
private $success = 0;
private $failures = 0;
public function connect($uri) {
return $this->connector->connect($uri)
->then(fn() => $this->success++, fn() => $this->failures++);
}
};
Integrate with Laravel’s HTTP Client:
Use decorators in custom HttpClient transports:
$client = new \GuzzleHttp\Client([
'handler' => new class extends \GuzzleHttp\HandlerStack {
public function __construct() {
$this->push(new \GuzzleHttp\Handler\RetryHandler());
$this->push(new CustomConnectorHandler($retryConnector));
}
}
]);
Default Loop Behavior:
Timeout/Delay auto-detect the loop from the wrapped connector.PHP 8+ Compatibility:
3
How can I help you explore Laravel packages today?