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

Zmq Laravel Package

react/zmq

View on GitHub
Deep Wiki
Context7

Getting Started

react/zmq is a thin async wrapper around the ext-zmq PHP extension: it takes a ZMQSocket, registers it with a ReactPHP event loop, and emits message / error events so ZeroMQ traffic becomes non-blocking in your event-driven app. It is not a ZeroMQ implementation — everything below assumes you already have libzmq and ext-zmq installed.

# system dependency first, then the PECL extension, then the package
sudo apt-get install libzmq3-dev pkg-config   # alpine: apk add zeromq-dev
pecl install zmq-beta                          # "stable" channel is ancient
composer require react/zmq:^0.4

Verify with php -m | grep zmq before writing code — most "the package is broken" reports are actually a missing extension.

Minimal working pair (bind + connect in one process for a smoke test):

$loop    = React\EventLoop\Factory::create();
$context = new React\ZMQ\Context($loop);

$pull = $context->socket(ZMQ::SOCKET_PULL);
$pull->bind('tcp://127.0.0.1:5555');
$pull->on('message', function ($msg) { echo "got: $msg\n"; });

$push = $context->socket(ZMQ::SOCKET_PUSH);
$push->connect('tcp://127.0.0.1:5555');
$push->send('hello');

$loop->run();

Where to look first:

  1. React\ZMQ\Context — the factory (socket($type); getSocket() is the deprecated alias still found in most blog posts).
  2. React\ZMQ\SocketWrapper — the object you actually hold. It implements Evenement\EventEmitterInterface (on, once), plus send() / recv() which proxy to the underlying ZMQSocket.
  3. examples/ in the repo — the push/pull, pub/sub and req/rep scripts are the fastest way to confirm your environment works.

First realistic use case: a long-running ReactPHP process (queue worker, WebSocket gateway, metrics collector) that needs to talk to a Python/Go/C++ service over ZeroMQ. That is where this package earns its keep — it lets one process own both sides of the loop instead of shelling out to a worker.

Implementation Patterns

Pattern 1 — Worker pool with PUSH/PULL. The classic fit: a PULL socket bound in the producer, N worker processes each connecting a PUSH, ZeroMQ handles fair-queue load balancing.

$pull = $context->socket(ZMQ::SOCKET_PULL);
$pull->bind('tcp://0.0.0.0:5555');
$pull->on('message', function ($payload) use ($loop) {
    $job = json_decode($payload, true);
    $loop->addTimer(0.001, fn() => dispatch($job)); // keep handler short
});

Remember connect() is asynchronous and retries forever in ZeroMQ — you can start workers before the producer exists and messages queue up to the high-water mark. bind() on the stable side, connect() on the ephemeral side.

Pattern 2 — PUB/SUB fan-out to the web layer. PUB sockets broadcast to every SUB; use this to push events into react/http or WebSocket clients.

$sub = $context->socket(ZMQ::SOCKET_SUB);
$sub->connect('tcp://127.0.0.1:5556');
$sub->setSockOpt(ZMQ::SOCKOPT_SUBSCRIBE, 'orders.');   // prefix filter
$sub->on('message', function ($frame) use ($clients) {
    foreach ($clients as $c) { $c->send($frame); }
});

$pub = $context->socket(ZMQ::SOCKET_PUB);
$pub->bind('tcp://127.0.0.1:5556');
$pub->send('orders.created ' . json_encode($order));

Keep the subscription prefix in the message itself — a SUB with no SUBSCRIBE option receives nothing, and that is the #1 "why is my handler silent" bug.

Pattern 3 — Async request/response with DEALER/ROUTER. Strict REQ/REP lockstep will deadlock your loop if a reply never arrives. Use DEALER/ROUTER and carry your own correlation ID:

$dealer = $context->socket(ZMQ::SOCKET_DEALER);
$dealer->connect('tcp://127.0.0.1:5557');
$pending = [];

$dealer->on('message', function ($reply) use (&$pending) {
    [$id, $body] = explode(' ', $reply, 2);
    $pending[$id]($body);
    unset($pending[$id]);
});

$id = bin2hex(random_bytes(8));
$pending[$id] = fn($body) => handleReply($body);
$dealer->send("$id " . json_encode($request));

Pattern 4 — Bridging to existing infrastructure. The wrapper exposes the underlying ZMQSocket, so you can mix high-level event handlers with low-level calls (setSockOpt, sendmulti, recvmulti, getLastEndpoint, unbind) without giving up the loop integration. This is the escape hatch for SOCKOPT_LINGER, heartbeats (ZMQ_HEARTBEAT_IVL on libzmq 4.2+), and identity/SOCKOPT_IDENTITY on ROUTER sockets.

Pattern 5 — Mixing with timers and other streams. Everything is one loop. Pair ZMQ handlers with addPeriodicTimer() for heartbeats, reconnects at the application level, metric flushes, and graceful shutdown:

$loop->addPeriodicTimer(10, fn() => $pub->send('heartbeat ' . time()));
$loop->addSignal(SIGTERM, function () use ($loop, $context) {
    $loop->stop();   // then let sockets destruct with LINGER=0
});

Testing pattern. Use inproc:// transport and two sockets in the same process to smoke-test wiring without ports or a broker; for unit tests, mock the wrapper around send()/recv() and emit('message', [...]) directly — you rarely need real ZeroMQ to test your handler logic.

Gotchas and Tips

Installation is the real cost. ext-zmq is effectively unmaintained: the PECL stable channel is stale, zmq-beta is what you want, and 1.1.3 does not build against PHP 8.x without patches or a fork. Combined with this package's last release (May 2018), treat "PHP 8 + react/zmq" as a research spike, not a given. On new projects, budget a day for the toolchain, or use ZMQPoll manually with addPeriodicTimer().

send() can block. The wrapper's send() passes straight through to ZMQSocket::send() with no internal queueing or backpressure. If the peer is slow and the send HWM is reached, you block the entire event loop. Mitigations: keep HWM high-ish but finite, use ZMQ::MODE_DONTWAIT and buffer on EAGAIN yourself, or use PUSH (which drops/blocks more predictably) instead of ROUTER for fire-and-forget.

REQ/REP is a deadlock trap. A REQ socket must alternate send → recv → send. Any path that skips the recv (timeout, exception, early return) permanently wedges that socket. In a loop, prefer DEALER/ROUTER or wrap every request in a timer that rebuilds the socket on timeout.

Multipart messages are not reassembled. The wrapper emits one message event per frame, so a 3-frame message fires three events with no grouping hint. Either avoid multipart (single JSON blob per message) or drop to sendmulti/recvmulti on the wrapped socket and handle framing yourself.

Attach listeners before run(), and always attach an error listener. Unhandled error events and exceptions thrown from the internal read handler surface as uncaught exceptions in the loop, which kills the process. Note that ZMQSocketException from a synchronous send() is thrown, not emitted as error.

Drain semantics and starvation. The read handler keeps recv'ing with DONTWAIT until EAGAIN, so a flooding publisher can monopolize the tick. Keep handlers sub-millisecond: parse, hand off, return.

PUB/SUB has no delivery guarantees. Slow-joiner problem, HWM-based dropping on the SUB side, no persistence, no retry. If you need at-least-once, add application-level acks or use PUSH/PULL with a durable store in front.

Shutdown hangs. Default LINGER waits for queued messages to flush, which in tests looks like a hang. Set SOCKOPT_LINGER to 0 in test/CLI setups, or you will be kill -9'ing processes.

Debugging. ZeroMQ swallows a lot silently. First move: $socket->getLastEndpoint() after bind() to confirm the address resolved (port 0 and tcp://* behave differently than you'd guess). Second: verify the socket type pair is legal (PUSH↔PULL, PUB↔SUB, REQ↔REP/ROUTER) — mismatched pairs connect successfully and quietly deliver nothing.

Extension points for when you outgrow it: wrap SocketWrapper in your own class that adds sendJson()/onJson(), correlation-id tracking, and reconnect-on-timeout — all the ergonomics this package deliberately leaves out. That wrapper is also your migration seam if you later swap ZeroMQ for clue/reactphp-redis pub/sub or a React\Socket-based protocol.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin