crazy-goat / rabbit-stream
Requires
- php: >=8.1
- ext-mbstring: *
- psr/log: ^3.0
Requires (Dev)
- phpstan/phpstan: ~2.1.0
- phpunit/phpunit: ~10.5.0
- rector/rector: ~2.4.0
- slevomat/coding-standard: ^8.15
- squizlabs/php_codesniffer: ^3.9
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 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-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-09-08 13:06:59 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 (stream offsets are uint64; 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) ), );
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()); while ($messages = $consumer->read(timeout: 5)) { foreach ($messages as $msg) { echo $msg->getBody() . "\n"; } } $consumer->close(); $connection->close();
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
use CrazyGoat\RabbitStream\Client\AmqpMessageDecoder; use CrazyGoat\RabbitStream\Client\OsirisChunkParser; // ... subscribe to stream and receive Deliver response $chunk = $deliverResponse->getChunk(); $entries = OsirisChunkParser::parse($chunk); // Decode AMQP 1.0 messages into Message objects $messages = AmqpMessageDecoder::decodeAll($entries); foreach ($messages as $message) { echo "Offset: {$message->getOffset()}\n"; echo "Body: {$message->getBody()}\n"; echo "Content-Type: {$message->getContentType()}\n"; echo "Message-ID: {$message->getMessageId()}\n"; }
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, ); 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 $storedOffset = $connection->queryOffset('my-consumer-group', 'my-stream'); $consumer = $connection->createConsumer( stream: 'my-stream', offset: OffsetSpec::offset($storedOffset + 1), 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.
See examples/consumer_auto_commit.php for a full working example.
Low-level Connection API
use CrazyGoat\RabbitStream\StreamConnection; use CrazyGoat\RabbitStream\Request\PeerPropertiesRequestV1; ...
See examples/simple_publisher.php for a full working example.
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)