Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phpcent Laravel Package

centrifugal/phpcent

PHP client library for Centrifugo v5 HTTP API. Publish, broadcast, subscribe, manage presence and history, plus generate JWT connection and subscription tokens. Install via Composer with composer require centrifugal/phpcent:~6.0.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and pin to your Centrifugo major. phpcent 6 ↔ Centrifugo 5, phpcent 5.x ↔ Centrifugo 4, phpcent 4.x ↔ Centrifugo 3. The HTTP wire format changed between server majors, so this is not a "latest is fine" dependency.

composer require centrifugal/phpcent:~6.0

2. Put both credentials in config, not in code. The library uses two different secrets and conflating them is the #1 setup failure:

  • API key — authenticates your PHP app → Centrifugo HTTP calls (publish, presence, …). Must match the server's api_key.
  • Secret — HMAC key used to sign JWTs (generateConnectionToken, generateSubscriptionToken). Must match the server's token_hmac_secret_key.
// config/services.php
'centrifugo' => [
    'url'    => env('CENTRIFUGO_URL', 'http://localhost:8000/api'), // note the /api suffix
    'api_key'=> env('CENTRIFUGO_API_KEY'),
    'secret' => env('CENTRIFUGO_SECRET'),
],

3. Bind it once as a singleton. There's no service provider or facade — you write five lines:

// app/Providers/AppServiceProvider.php
$this->app->singleton(\phpcent\Client::class, fn () => new \phpcent\Client(
    config('services.centrifugo.url'),
    config('services.centrifugo.api_key'),
    config('services.centrifugo.secret'),
));

4. Read the source, not just the README. src/Client.php is small (a cURL wrapper plus a JWT builder). In tinker:

$c = app(\phpcent\Client::class);
dump($c->info());                                   // health check + confirms auth works
dump($c->publish('test', ['message' => 'hello']));  // inspect the actual return shape

Do this before writing callers. The v6 release moved to Centrifugo v5's {"result": ...} envelope, and there has already been a patch release fixing a method's return handling — don't guess the shape, and don't assume errors throw.

5. First real use case: publish from a queued job. Pick one concrete event (order shipped, mention received, job finished), fire it from a listener after commit, and confirm it lands in a browser tab subscribed with centrifuge-js. That single slice exercises install, config, auth, publish, and the frontend — everything else in the library is a variation.

Where to look first: README "Basic Usage" and "All available API methods" for the surface area; centrifugal.dev/docs/server/server_api for what each method actually means; the Docker test recipe in the README for a local server with --api_insecure.

Implementation Patterns

Wrap the client in one thin application service. Don't let \phpcent\Client leak across controllers and jobs. A single Realtime/Centrifugo class gives you a seam for logging, retries, and test fakes — and makes a future transport swap a one-file change.

final class Realtime
{
    public function __construct(private \phpcent\Client $client) {}

    public function publish(string $channel, array $data): void
    {
        $response = $this->client->publish($channel, $data);
        // log the response; decide explicitly what counts as failure
        Log::debug('centrifugo.publish', ['channel' => $channel, 'response' => $response]);
    }
}

Publish after commit, never inside a transaction. This is the classic Laravel-side bug:

DB::afterCommit(fn () => app(Realtime::class)->publish("orders.{$order->id}", $payload));
// or: class OrderShipped implements ShouldDispatchAfterCommit

Otherwise a rolled-back transaction has already told the browser the order exists.

Push from queue jobs, not from the web request. Publishing is a synchronous cURL round trip. Inside a worker it's fine; inside a user-facing request it adds latency for no reason. Standard shape: controller → event → queued listener → Realtime::publish().

Token endpoint pattern. This is the one route you must add; the package only mints strings.

Route::middleware('auth')->get('/centrifugo/token', function (Request $request) {
    $client = app(\phpcent\Client::class);
    return ['token' => $client->generateConnectionToken((string) $request->user()->id, time() + 300)];
});

// private channels — mint per channel, keep TTL short
Route::middleware('auth')->get('/centrifugo/subscription', function (Request $request) {
    abort_unless(Gate::allows('subscribe', $request->channel), 403);
    return ['token' => app(\phpcent\Client::class)
        ->generateSubscriptionToken((string) $request->user()->id, $request->channel, time() + 1800)];
});

Keep connection tokens to ~5 minutes and let centrifuge-js refresh them; the README's own example uses 5 minutes for a reason. Authorize the channel in PHP before minting — the token is the authorization, so Gate::allows belongs here, not in the browser.

Fan-out: broadcast() over a loop, batch() over repeated calls. Looping publish() in PHP means N HTTP round trips. If you're notifying 40 channels, broadcast($channels, $data). If you're sending 40 different messages, batch([...]) — and check each item in the result, since one can fail independently.

Presence and history are cheap wins. presence($channel) powers "who's online" widgets without new infrastructure; presenceStats($channel) gives counts only (cheaper, no user payload). history($channel) lets a reconnecting client catch up on missed events — that's the feature that makes an offline-then-reconnect user experience not feel broken.

Force disconnects on privilege change. On logout, ban, password change, or role change, call disconnect($userId) after revoking the session. Tokens are bearer-only and can't be individually revoked, so disconnecting is your only lever.

Testing. Two tiers:

  • Unit/feature tests: swap your Realtime wrapper (or the Client singleton) with a fake in the container; assert publish was called with the right channel and payload. Http::fake() will not work — the library uses raw cURL, not Laravel's HTTP client.
  • Integration tests: run the official Docker image exactly as the README shows, then exercise real calls.
docker run -d -p 8000:8000 --name centrifugo centrifugo/centrifugo centrifugo --api_insecure
vendor/bin/phpunit

--api_insecure disables API key enforcement — development only.

Health check. $client->info() in a /up-style endpoint is the simplest way to catch "Centrifugo is down" before users do.

Gotchas and Tips

The base URL must end with /api. new Client("http://localhost:8000") gives confusing 404s; new Client("http://localhost:8000/api") is correct.

The client is mutable — treat the singleton as read-only. setApiKey(), setSecret(), setUseAssoc(), setTimeoutOption() all mutate shared state. One call site calling setUseAssoc(true) silently changes decoding for every other consumer for the rest of the request. Configure once in the container binding, then never touch it.

Decide setUseAssoc() once and document it. Default decoding gives you stdClass; setUseAssoc(true) gives arrays. A codebase that mixes both produces Trying to access array offset on object at exactly the wrong time. I'd set it to true in the singleton and standardize on arrays.

Set both timeouts explicitly. Connect timeout 0 means never — a wedged Centrifugo will hang a queue worker indefinitely. For a background publisher, something like connect 1s / request 2s is sane:

$client->setConnectTimeoutOption(1);
$client->setTimeoutOption(2);

Add a retry() or job-level $tries/$backoff around publish calls; transient cURL failures should not silently drop an event.

Silent drops are the failure mode to guard against. The library is a thin cURL wrapper: transport-level failures and API-level failures (unknown channel, permission denied, malformed payload) don't necessarily arrive the same way. Do not assume "no exception" means "delivered." Log the response body on every publish path and, for anything important, alert on non-success rather than discovering it from a user report.

Version lockstep is an ongoing line item. Track Centrifugo as an infrastructure dependency with a matching phpcent major. The v5 HTTP API format change is exactly the kind of upgrade that breaks without a code change if you drift. Pin both.

generateConnectionToken() requires $userId. Since phpcent 5.0.0 it's no longer optional. Pass an explicit empty string '' for anonymous users.

Private channels need the sub claim to match. generateSubscriptionToken($userId, $channel) embeds the user ID; if it doesn't match the connection's user ID, the subscribe is rejected at the server. Private channels are conventionally #-prefixed — follow Centrifugo's convention rather than inventing your own.

SSL: setSafety(false) disables certificate verification — acceptable on a laptop, never in production. For self-signed certs use setCert('/path/to/certificate.pem') (and setCAPath() if needed); the certificate's hostname must match the host in the client URL.

cURL error: Resolving timed out on containers is usually IPv6. $client->forceIpResolveV4() fixes it without touching DNS config.

Payloads must be JSON-serializable and small. Passing an Eloquent model directly serializes everything — attributes, loaded relations, sometimes the whole graph. Use an API Resource or an explicit array. Large payloads also inflate Centrifugo's history storage; keep history size limits in mind if you're relying on history() for replay.

Use camelCase method names. historyRemove(), presenceStats() — the snake_case variants were removed in phpcent 4.0.0.

Extension points are thin, and that's fine. Subclass Client only to override request handling/logging; otherwise wrap it. The wrapper is also your test seam, your retry point, and your single place to add structured logging — build it on day one rather than retrofitting it after the client has spread across fifteen call sites.

Scope check, once: this library is server-to-server only. It does not open WebSockets, reconnect, or touch the browser. The frontend still needs centrifuge-js plus the token endpoints above — budget that workstream separately, or the "real-time layer" estimate will be short by half.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator