crazy-goat / rabbit-stream
A PHP client for the RabbitMQ Streams protocol.
Requires
- php: >=8.1
- ext-mbstring: *
- psr/log: ^3.0
Requires (Dev)
- ext-sockets: *
- phpstan/phpstan: 2.3.1
- phpunit/phpunit: ~10.5.0
- rector/rector: 2.7.0
- slevomat/coding-standard: ^8.15
- squizlabs/php_codesniffer: ^3.9 || ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 1.x-dev
- v1.13.2
- v1.13.1
- v1.13.0
- v1.12.0
- v1.11.0
- v1.10.0
- v1.9.0
- v1.8.0
- v1.7.0
- v1.6.0
- v1.5.0
- v1.4.0
- v1.3.0
- v1.2.1
- v1.2.0
- v1.1.0
- v1.0.0
- v0.5.0
- v0.4.0
- v0.3.0
- v0.2.0
- v0.1.0
- dev-chore/changelog-sync-v1.13.2
- dev-chore/release-v1.13.2
- dev-fix/issue-906-1x-backport
- dev-fix/issue-906-streamconnection-tls-connect-always-fail
- dev-chore/changelog-sync-v1.13.1
- dev-chore/release-v1.13.1
- dev-fix/issue-948-1x-backport
- dev-fix/issue-948-tlsconfig-saslauthenticaterequestv1-the
- dev-fix/issue-947-1x-backport
- dev-fix/issue-947-amqpdecoder-no-total-limit-of-decoded-va
- dev-chore/issue-945-ci-run-ci-on-push-to-the-1-x-maintenance
- dev-fix/issue-468-bug-offsetspec-interval-serializes-offse
- dev-feature/issue-405-amqp-decoder-alloc
- dev-feature/issue-400-tls-support
- dev-feature/issue-404-publish-copy-hotpath
- dev-feature/issue-464-amqp-array-support
- dev-feature/issue-461-consumer-read-push-frames
- dev-feature/issue-470-consumer-update-reply-offset
- dev-feature/issue-474-producer-close-pending-confirms
- dev-feature/issue-403-chunk-crc
- dev-feature/issue-356-negotiated-max-value-tests
- dev-feature/issue-359-osirischunkparser-docs
- dev-feature/issue-360-readme-consumer-autocommit-example
- dev-feature/issue-348-consumer-array-merge-performance
- dev-feature/issue-361-consumer-credit-flow-control-tests
- dev-feature/issue-349-null-socket-sendframe
- dev-feature/issue-152-access-refused-e2e
- dev-feature/issue-347-keyenum-fallback
- dev-feature/speed-up-e2e-tests
- dev-feature/issue-342-heartbeat-test-speedup
- dev-feature/issue-160-metadata-update-e2e
- dev-feature/issue-149-e2e-invalid-vhost
- dev-feature/issue-150-close-throw-e2e
- dev-feature/issue-164-metadata-multiple-streams-e2e
- dev-feature/issue-176-extract-e2e-base-class
- dev-feature/issue-156-multiple-publishers-e2e-test
- dev-feature/issue-191-message-unit-tests
- dev-feature/issue-177-verify-error-codes
- dev-feature/issue-161-heartbeat-e2e-test
- dev-feature/issue-167-empty-special-chars-messages
- dev-oda-183-unit-test-keyenum-fromstreamcode-edge-ca
- dev-oda-241-docs-documentation-index-and-languages-m
- dev-feature/combine-lints
- dev-fix/issue-200-metadata-response-comment
- dev-feature/issue-201-publishing-error-interface
- dev-feature/rector-fqn-rule
- dev-feature/issue-117-writebuffer-utf8-validation
- dev-feature/issue-114-fix-last-publishing-id
- dev-feature/issue-109-rename-assert-response-code
- dev-feature/issue-113-destructor-socket-cleanup
- dev-feature/issue-115-readloop-timeout-fix
- dev-feature/issue-106-connection-response-validation
- dev-feature/issue-107-consumer-backpressure
- dev-feature/implementing-pr-skill
- dev-feature/issue-102-frame-size-limit
- dev-feature/issue-70-release-v1.0.0
- dev-feature/issue-84-qa-commands
- dev-feature/issue-73-rector
- dev-feature/issue-56-cleanup-major-version
- dev-feature/issue-55-examples-docs-deprecation
- dev-feature/issue-54-consumer
- dev-feature/issue-53-producer-rewrite
- dev-fix/message-body-bool-type
- dev-feature/issue-50-osiris-chunk-parser
- dev-feature/issue-16-query-publisher-sequence
- dev-feature/issue-9-delete-publisher
- dev-feature/read-loop
- dev-feature/publish
- dev-feature/declare-publisher
- dev-feature/github-actions
This package is auto-updated.
Last update: 2026-10-30 07:44:49 UTC
README
A PHP library implementing the RabbitMQ Streams Protocol client.
It provides low-level TCP communication with a RabbitMQ broker over the native Stream protocol (port 5552), including binary frame serialization/deserialization.
Requirements
- PHP 8.1+, 64-bit build,
psr/log ^3.0, andext-mbstring(see Requirements) - RabbitMQ with the
rabbitmq_streamplugin enabled
Installation
composer require crazy-goat/rabbit-stream
Quick Start
Publishing
use CrazyGoat\RabbitStream\Client\Connection; $connection = Connection::create(host: 'localhost', port: 5552); $producer = $connection->createProducer('my-stream', name: 'my-producer'); $producer->send('hello world'); $producer->waitForConfirms(timeout: 5); $producer->close(); $connection->close();
Message bodies are plain strings — Producer::send() and sendBatch()
automatically wrap them in an AMQP 1.0 Data section
on the wire, and the consumer returns them unwrapped (see Publishing).
TLS transport (encrypted connections)
By default connections are plaintext tcp:// (port 5552). Pass a
CrazyGoat\RabbitStream\VO\TlsConfig to use the encrypted ssl:// transport
(RabbitMQ stream listener on port 5551):
use CrazyGoat\RabbitStream\Client\Connection; use CrazyGoat\RabbitStream\VO\TlsConfig; $connection = Connection::create( host: 'localhost', port: 5551, // TLS stream port tls: new TlsConfig( cafile: '/etc/ssl/ca.pem', // optional: custom CA bundle // localCert: '/etc/ssl/client.crt', // optional: client certificate // localPk: '/etc/ssl/client.key', // (e.g. for EXTERNAL SASL) ), );
Pass the private-key passphrase with new TlsConfig(passphrase: '...'). Read it
with $tls->getPassphrase(). The $passphrase property is private, so
var_export(), serialize(), json_encode(), the (array) cast and stack traces
do not show it. Code that read $tls->passphrase must call getPassphrase().
connect() completes the TLS handshake, including the client certificate and the key
passphrase, within socketTimeout. The E2E test tests/E2E/TlsConnectionE2ETest.php runs
against a broker with a TLS stream listener and mutual TLS (./run-e2e.sh sets it up).
Peer certificate and hostname verification are on by default
(verify_peer/verify_peer_name). For a self-signed development broker you can
disable them explicitly — do not do this in production:
$connection = Connection::create( host: 'localhost', port: 5551, tls: new TlsConfig(verifyPeer: false, verifyPeerName: false), );
Consuming
use CrazyGoat\RabbitStream\Client\Connection; use CrazyGoat\RabbitStream\VO\OffsetSpec; $connection = Connection::create(host: 'localhost', port: 5552); $consumer = $connection->createConsumer('my-stream', offset: OffsetSpec::first()); // read() returns [] when nothing arrives within the timeout, so this loop // stops at the first quiet 5 seconds. That suits draining a backlog. while ($messages = $consumer->read(timeout: 5)) { foreach ($messages as $msg) { echo $msg->getBody() . "\n"; } } $consumer->close(); $connection->close();
To keep consuming a live stream that can have gaps, do not stop on an empty
batch. Loop on a flag and treat [] as "nothing yet" (see
examples/consumer.php for a full version with signal handling):
$running = true; // Set to false from a signal handler to stop consuming. while ($running) { foreach ($consumer->read(timeout: 5) as $msg) { echo $msg->getBody() . "\n"; } }
Documentation
Full documentation lives in docs/: getting started, guides,
API reference, protocol notes and examples. Translations are listed in
docs/LANGUAGES.md (currently English only).
Usage
High-level API (Recommended)
use CrazyGoat\RabbitStream\Client\Connection; use CrazyGoat\RabbitStream\Client\ConfirmationStatus; // Connect (handshake and authentication handled automatically) $connection = Connection::create( host: '127.0.0.1', user: 'guest', password: 'guest' ); // Create a producer for 'my-stream' $producer = $connection->createProducer( stream: 'my-stream', onConfirm: function (ConfirmationStatus $status): void { if ($status->isConfirmed()) { echo "Message {$status->getPublishingId()} confirmed\n"; } } ); // Send a message $producer->send("Hello, RabbitMQ Stream!"); // Drive the loop to receive confirmations (optional, blocking) $connection->readLoop(maxFrames: 1); // Close producer and connection $producer->close(); $connection->close();
Consuming with Message Decoding
Consumer::read() and Consumer::readOne() already return decoded Message
objects; access fields such as $message->getOffset(), $message->getBody(),
$message->getContentType() and $message->getMessageId() directly. See the
AMQP message decoding guide for
manual decoding of raw chunks with OsirisChunkParser and AmqpMessageDecoder.
Consumer with Auto-Commit
use CrazyGoat\RabbitStream\Client\Connection; use CrazyGoat\RabbitStream\VO\OffsetSpec; $connection = Connection::create(host: 'localhost', port: 5552); // Named consumer with auto-commit every 100 messages // The name is used to persist the offset on the server $consumer = $connection->createConsumer( stream: 'my-stream', offset: OffsetSpec::first(), name: 'my-consumer-group', autoCommit: 100, ); // Stops at the first empty batch (nothing within 5 seconds); see "Consuming". while ($messages = $consumer->read(timeout: 5)) { foreach ($messages as $msg) { echo $msg->getBody() . "\n"; } } $consumer->close(); // stores final offset automatically // On next startup, resume from the stored offset. queryOffset() returns null // when nothing has been stored yet (a normal first run, not an error). $storedOffset = $connection->queryOffset('my-consumer-group', 'my-stream'); $consumer = $connection->createConsumer( stream: 'my-stream', offset: $storedOffset === null ? OffsetSpec::first() : OffsetSpec::offset($storedOffset), name: 'my-consumer-group', autoCommit: 100, ); $connection->close();
Note:
autoCommittriggersstoreOffsetevery N messages. The offset is also stored onclose(). A named consumer is required for offset persistence — unnamed consumers cannot usestoreOffsetorqueryOffset.queryOffset()returnsnull(rather than throwing) when no offset has been stored for the name/stream pair.
See examples/consumer_auto_commit.php for a full working example.
Low-level Connection API
StreamConnection exposes the raw protocol frames (requests and responses) for
cases the high-level Connection does not cover. It is documented in the
StreamConnection API reference;
for most applications use the high-level API above.
Protocol Implementation Status
Protocol reference: https://github.com/rabbitmq/rabbitmq-server/blob/main/deps/rabbitmq_stream/docs/PROTOCOL.adoc
Connection & Authentication
| Command | Key | Request | Response |
|---|---|---|---|
| PeerProperties | 0x0011 | ✅ | ✅ |
| SaslHandshake | 0x0012 | ✅ | ✅ |
| SaslAuthenticate | 0x0013 | ✅ | ✅ |
| Tune | 0x0014 | ✅ | ✅ |
| Open | 0x0015 | ✅ | ✅ |
Publishing
| Command | Key | Request | Response |
|---|---|---|---|
| DeclarePublisher | 0x0001 | ✅ | ✅ |
| Publish | 0x0002 | ✅ | — |
| PublishConfirm | 0x0003 | — | ✅ |
| PublishError | 0x0004 | — | ✅ |
| QueryPublisherSequence | 0x0005 | ✅ | ✅ |
| DeletePublisher | 0x0006 | ✅ | ✅ |
Consuming
| Command | Key | Request | Response |
|---|---|---|---|
| Subscribe | 0x0007 | ✅ | ✅ |
| Deliver | 0x0008 | — | ✅ |
| Credit | 0x0009 | ✅ | ✅ |
| StoreOffset | 0x000a | ✅ | — |
| QueryOffset | 0x000b | ✅ | ✅ |
| Unsubscribe | 0x000c | ✅ | ✅ |
| ConsumerUpdate | 0x001a | ✅ | ✅ |
Stream Management
| Command | Key | Request | Response |
|---|---|---|---|
| Create | 0x000d | ✅ | ✅ |
| Delete | 0x000e | ✅ | ✅ |
| Metadata | 0x000f | ✅ | ✅ |
| MetadataUpdate | 0x0010 | — | ✅ |
| CreateSuperStream | 0x001d | ✅ | ✅ |
| DeleteSuperStream | 0x001e | ✅ | ✅ |
| StreamStats | 0x001c | ✅ | ✅ |
Routing (Super Streams)
| Command | Key | Request | Response |
|---|---|---|---|
| Route | 0x0018 | ✅ | ✅ |
| Partitions | 0x0019 | ✅ | ✅ |
Connection Management
| Command | Key | Request | Response |
|---|---|---|---|
| Close | 0x0016 | ✅ | ✅ |
| Heartbeat | 0x0017 | ✅ | — |
| ExchangeCommandVersions | 0x001b | ✅ | ✅ |
| ResolveOffsetSpec | 0x001f | ✅ | ✅ |
Legend: ✅ implemented, ❌ not implemented, — not applicable (one-direction command)