workerman/channel
Workerman Channel is a lightweight, high-performance inter-process communication component for PHP. It provides a simple pub/sub and client-server API over TCP or Unix sockets, enabling Workerman processes and external services to exchange messages efficiently.
workerman/channel is not a Laravel package. There is no service provider, no facade, no config file, and no artisan command. It is a two-class component (Channel\Server and Channel\Client) built on top of workerman/workerman, and it only makes sense inside a process that already has a Workerman event loop running. Treat it as infrastructure you run next to Laravel, not inside a request.
Minimal steps:
composer require workerman/channel workerman/workerman. Check it doesn't pin a Workerman version that conflicts with anything else in your tree (notably if you already use gatewayworker/gateway-worker).src/Server.php and src/Client.php are a few hundred lines total and the README is essentially an example. That is the entire documentation surface.use Channel\Server;
new Server('127.0.0.1', 2206);
Worker::runAll();
use Channel\Client;
$worker->onWorkerStart = function () {
Client::connect('127.0.0.1', 2206);
Client::on('app.user.joined', function ($data) {
// runs inside the event loop
});
};
app.room.123; every Gateway process subscribed to that event pushes to its own connections. It is the "kick this user out of all processes" pattern, without Redis.Broker as a dedicated daemon. Write one tiny artisan command (channel:serve) that instantiates Channel\Server, then Worker::runAll(). Run it under Supervisor with numprocs=1 on loopback or a private network. This process is the entire message bus.
Client registration in onWorkerStart. Every Workerman worker that needs to hear events calls Client::connect() once at startup and registers all handlers there. Handlers are plain closures — build them as thin dispatchers that call into your domain code, not as inline business logic.
The bridge pattern (the one you will actually need). Client::publish() requires a running event loop, so calling it from a normal Laravel controller or queue worker will not work. The clean pattern is:
Channel\Client.Client::publish('app.order.paid', $payload) from inside onMessage, where the loop is alive.This keeps Laravel’s synchronous request lifecycle out of the event loop entirely and gives you one place to authenticate/validate events before broadcasting them.
Namespaced event names. There is no wildcard subscription and no channel hierarchy — events are a flat string namespace on one TCP port. Adopt app.<domain>.<action> (app.chat.message, app.auth.kicked) from day one, or two features will collide within a month.
JSON-serializable payloads only. Whatever you pass to publish() goes over a raw TCP socket. Arrays of scalars are fine; closures, Eloquent models, and streams are not. Serialize to an array and let handlers re-hydrate via Model::find($data['id']).
Offload heavy work. Inside a Client::on callback you must not block — no synchronous HTTP, no slow queries, no sleep. dispatch() a job onto your Redis/DB queue and return immediately. The event loop serves every subscription in that worker; one blocking handler stalls all of them.
Fan-out over fan-in. This package is fire-and-forget pub/sub with no persistence, no acknowledgements, and no replay. If a subscriber is not connected at publish time, it never sees the message. Use it for cache invalidation, presence/room signals, kick-user broadcasts — not for job queues or anything requiring at-least-once delivery.
No reconnect logic in the client. If Client::connect() runs before the broker is up, it fails. Ordering matters in Supervisor: start the channel server first, use autostart/startsecs, and restart clients if the broker dies. Consider wrapping connect in your own retry in onWorkerStart and re-subscribing on reconnect — subscriptions live only on the connection.
Never expose the port. There is no authentication and no TLS. Bind to 127.0.0.1 or a private interface. Port 2206 open to the internet is an unauthenticated message bus for anyone who can nc to it.
Single point of failure by design. One Channel\Server process holds all subscriptions. If it crashes, every worker silently loses its listeners until restarted. Supervisor with autorestart=true is mandatory; monitor it like you monitor Redis.
Channel\Server extends Worker. Creating it registers a global Worker; you cannot casually instantiate it inside an existing Laravel process and expect it to run. It only does anything once Worker::runAll() is called, and Worker::runAll() blocks the process forever. Keep it in its own artisan command.
No container, no facades. Callbacks execute in the Workerman loop, not in a Laravel request kernel. If you boot the Laravel app inside onWorkerStart (common), that is fine, but be aware that the DB connection is long-lived and will time out — set DB::reconnect() / DB::purge() or open short connections inside handlers.
Payload size limit. The protocol caps message length (1 MB). Sending large blobs will disconnect the client. If you need to move a large object, publish an ID and let the receiver fetch it.
Debugging. There is no introspection tooling. Add a temporary subscriber that logs every event name your app publishes, and log in the publish bridge before and after Client::publish(). Check Worker::$stdoutFile/Supervisor logs — errors surface there, not in storage/logs/laravel.log, unless your handler explicitly writes there.
Extension points are thin but real. The wire protocol is trivial; if you need auth or TLS, fork Channel\Server/Client or terminate TLS with stunnel. Otherwise plan to migrate to Redis pub/sub once you need durability, clustering, or observability.
Honest trade-off. With ~144 stars and effectively zero dependents, you will find no Stack Overflow answers — the source is the documentation. If your app is pure Laravel with no Workerman runtime, Redis pub/sub + broadcasting solves the same problem with far more tooling; choose this package only when Workerman is already your process model and you want zero external dependencies. Track the 2025-07-08 release cadence: maintained, but low-traffic, so pin the version and read the diff before upgrading.
How can I help you explore Laravel packages today?