roadrunner-php/centrifugo
PHP bridge for the RoadRunner Centrifugo plugin that proxies Centrifugo client events like connect, refresh, subscribe, and RPC calls to your Laravel/PHP app for custom authentication, session renewal, and real-time request handling.
Install the package via Composer. As of 2.5.0 the package was renamed — the canonical install is now:
composer require roadrunner/centrifugo
⚠️ Upgrading from
roadrunner-php/centrifugo? The rename is a package-name change, not a namespace change. Youruse RoadRunner\Centrifugo\...imports and theCentrifugo\Client-style API stay the same, but you must swap the requirement incomposer.json, remove the old entry fromcomposer.lock, and update any CI/Dockerfiles or internal packages that pinroadrunner-php/centrifugo. Expect the old name to stop receiving releases.
The core class is Centrifugo\Client. You’ll instantiate it with your Centrifugo HTTP API credentials and base URL (e.g., http://localhost:8000/api):
use Centrifugo\Client;
$client = new Client(
url: 'http://localhost:8000/api',
apiKey: 'your-centrifugo-api-key',
secret: 'your-centrifugo-secret' // required for signing requests
);
💡 First use case: Push a real-time update to all users subscribed to a channel:
$client->publish('news', ['message' => 'New update!']);
Check the Centrifugo docs to confirm your server is running and the HTTP API is enabled (http_api section in config). Ensure centrifugo is started with proper API access enabled (port 8000 by default).
2.5.0 adds RoadRunner v3 support. If you run the bridge inside a RoadRunner worker, align your runtime with the library:
# .rr.yaml — v3 config schema
version: '3'
Make sure the rr binary, the spiral/roadrunner-* packages, and this package are upgraded together — mixing a v2 binary with v3-aware worker code is the most common cause of "worker silently exits" reports after this release. Check rr --version in your container image before deploying.
Use publish() for single-channel updates, ideal for broadcasting events like chat messages, notifications, or live scores:
$client->publish('chat:room-123', [
'user_id' => 42,
'text' => 'Hello world!',
'timestamp' => time()
]);
Use broadcast() to push the same payload to multiple channels — efficient for system-wide announcements:
$client->broadcast([
['channel' => 'alerts:general', 'data' => ['type' => 'maintenance']],
['channel' => 'alerts:support', 'data' => ['type' => 'ticket-created']],
]);
Leverage server-side management methods for debugging or operational workflows:
presence(string $channel) → get channel subscribershistory(string $channel) → get recent messages (if history enabled)unsubscribe(string $channel, string $client) → force client disconnectdisconnect(string $client) → kill a specific client connectionExample: kick a misbehaving client:
$client->disconnect('client-abc123');
Use in Laravel: bind the client as a singleton in a service provider:
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->singleton(Client::class, function () {
return new Client(
url: config('centrifugo.url'),
apiKey: config('centrifugo.api_key'),
secret: config('centrifugo.secret')
);
});
}
Then inject into jobs, commands, or controllers:
public function store(Request $request, Client $centrifugo)
{
// ... save model
$centrifugo->publish('notifications:user-'.$request->user()->id, [
'message' => 'Your profile was updated',
]);
}
Since this is a RoadRunner bridge, pair it with RoadRunner’s worker processes (e.g., for long-running broadcast services). Avoid publishing inside request cycles for high-volume events — use queues (e.g., spiral/roadrunner-queue) to decouple.
A typical v3 worker bootstrap keeps the client alive across the worker loop, so build it once outside the loop and reuse it:
$client = new Client(url: $url, apiKey: $apiKey, secret: $secret);
while ($payload = $worker->waitPayload()) {
$client->publish($payload->channel, json_decode($payload->body, true));
$worker->respond(new Payload('OK'));
}
The rename to roadrunner/centrifugo is the single biggest operational change in this release:
requires roadrunner-php/centrifugo must be updated at the same time, or Composer will happily install two copies of the same library under different vendor paths and you'll debug phantom version skew.replace/provide the old name in a shared library, do it deliberately ("replace": {"roadrunner-php/centrifugo": "self.version"}), otherwise consumers get conflicting installs.composer why roadrunner-php/centrifugo after upgrading; an empty result means the migration is complete.Centrifugo’s HTTP API is sensitive. Always:
apiKey and secret in .env + secure vaultThe client supports HMAC signing — ensure $secret is set. Omitting it will cause 401s on administrative endpoints.
The client uses Guzzle under the hood. Set custom timeouts:
$client = new Client(
url: 'http://centrifugo:8000/api',
apiKey: '...',
secret: '...',
options: ['timeout' => 2.5, 'connect_timeout' => 1.0]
);
Set low timeouts — slow Centrifugo responses shouldn’t block your PHP app. This matters doubly inside a RoadRunner v3 worker: a blocking HTTP call stalls the whole worker, not just one request.
Centrifugo restricts message size (default: 64 KiB). Avoid sending large binary data. For file updates, send only a signed URL:
$client->publish('files:updated', [
'file_id' => $file->id,
'download_url' => SignedUrl::for($file)->toString()
]);
The client does not auto-retry. Wrap calls in retry logic for critical events (e.g., notification delivery):
$retry = new RetryStrategy(maxAttempts: 3, delayMs: 100);
$retry->execute(fn() => $client->publish('urgent', $payload));
Consider using oneway() for fire-and-forget scenarios where delivery confirmation isn’t needed (avoiding HTTP round-trip overhead).
broadcast() and publish() scale with network I/O, not PHP memory — good for high-load systems/stats) to detect bottlenecksEnable Guzzle debug logs in Laravel by extending the client:
$client = new Client(
url: '...',
apiKey: '...',
secret: '...',
options: [
'handler' => HandlerStack::create(new MiddlewareInspector()),
'debug' => env('APP_DEBUG', false)
]
);
Check Centrifugo logs for http_api errors — most failures show up there with clear messages (e.g., "invalid token" or "channel not allowed").
After the RoadRunner v3 upgrade, also capture worker stderr: version mismatches between the rr binary and the PHP worker packages usually surface there as protobuf/decoding errors rather than clean exceptions.
How can I help you explore Laravel packages today?