Transports
For a framework-less PHP producer, the core ships optional reference Transport
implementations covering all seven brokers — Redis, RabbitMQ, Amazon SQS, Apache
Kafka, Apache Pulsar and Apache ActiveMQ/Artemis. (Azure Service Bus is the one broker
without a PHP transport — ADR-0021.)
They keep the core dependency-free — install only the broker client you use.
The Transport seam
BabelQueue\Contracts\Transport is a single method:
interface Transport
{
public function publish(string $payload, ?string $queue = null): ?string;
}
$payload is the EnvelopeCodec::encode(...) output. Any broker client can
implement it — phpredis (ext-redis) users, for example, can do it in one line
(rpush).
RedisTransport
composer require predis/predis
use BabelQueue\Codec\EnvelopeCodec;
use BabelQueue\Transport\RedisTransport;
$transport = new RedisTransport(new Predis\Client('redis://localhost:6379'));
$envelope = EnvelopeCodec::fromJob($job, 'orders');
$transport->publish(EnvelopeCodec::encode($envelope), 'orders');
// a Go / Python / Node consumer reads the identical envelope off "orders"
RedisTransport produces with RPUSH, matching the reliable-queue pattern the other
SDKs use.
AmqpTransport
composer require php-amqplib/php-amqplib
use BabelQueue\Transport\AmqpTransport;
$transport = new AmqpTransport(/* php-amqplib connection */);
$transport->publish(EnvelopeCodec::encode($envelope), 'orders');
AmqpTransport declares a durable queue, publishes persistent messages, and sets the
contract AMQP properties (type = URN, correlation_id = trace_id,
message_id = meta.id, plus x-schema-version / x-source-lang / x-attempts),
so consumers can route on properties without parsing the body.
SqsTransport
composer require aws/aws-sdk-php
A produce-side Amazon SQS transport: it sends the canonical envelope as the MessageBody
with the §3 MessageAttributes. It is
decoupled from the AWS SDK behind a one-method BabelQueue\Transport\SqsClient seam, so
wrap a real Aws\Sqs\SqsClient in a one-line adapter:
use BabelQueue\Transport\SqsClient;
use BabelQueue\Transport\SqsTransport;
$adapter = new class (new Aws\Sqs\SqsClient([/* region, credentials */])) implements SqsClient {
public function __construct(private Aws\Sqs\SqsClient $c) {}
public function sendMessage(array $args): mixed { return $this->c->sendMessage($args); }
};
$transport = new SqsTransport($adapter, 'https://sqs.eu-central-1.amazonaws.com/123456789012/orders');
$transport->publish(EnvelopeCodec::encode($envelope));
// a Go / Python / Node / Java / .NET consumer reads the identical envelope + attributes
The projected attributes (bq-job = URN, bq-trace-id, bq-message-id, plus
bq-schema-version / bq-source-lang / bq-created-at) let a consumer route without
decoding the body; FIFO sets MessageGroupId / MessageDeduplicationId.
KafkaTransport
Apache Kafka (§6) is PHP’s one opt-in transport: its only viable client is the
ext-rdkafka C extension, so it deliberately relaxes the zero-extension rule
(ADR-0019). The transport stays decoupled
behind the one-method BabelQueue\Transport\KafkaProducer seam, so you wrap your real
RdKafka\Producer in a one-line adapter:
pecl install rdkafka # the ext-rdkafka extension
use BabelQueue\Transport\KafkaProducer;
use BabelQueue\Transport\KafkaTransport;
$producer = new class (/* RdKafka\Producer */) implements KafkaProducer {
public function produce(string $topic, string $payload, array $headers, ?int $timestampMs = null): void {
// produce to $topic with the bq-* headers + record timestamp
}
};
$transport = new KafkaTransport($producer, 'orders');
$transport->publish(EnvelopeCodec::encode($envelope), 'orders');
// a Java / Go / Node / Python / .NET consumer reads the identical envelope off the topic
The record value is the canonical envelope; the contract fields ride bq- record
headers (bq-job = URN, bq-trace-id, bq-message-id, bq-schema-version,
bq-source-lang, bq-attempts — hyphens, per §6), and the record timestamp mirrors
meta.created_at.
PulsarTransport
Apache Pulsar (§5) has no mature native PHP client, so PHP produces over Pulsar’s native
WebSocket API with a pure-PHP WebSocket client — GR-7 stays intact (no C extension,
ADR-0020). It is decoupled behind the
one-method BabelQueue\Transport\PulsarWebSocketClient seam:
composer require textalk/websocket
use BabelQueue\Transport\PulsarTransport;
use BabelQueue\Transport\PulsarWebSocketClient;
$client = new class (/* textalk/websocket client */) implements PulsarWebSocketClient {
public function publish(string $topic, string $payload, array $properties): void {
// base64 the payload into a WS producer frame with the bq-* properties; check the ack
}
};
$transport = new PulsarTransport($client, 'orders'); // tenant/namespace default to public/default
$transport->publish(EnvelopeCodec::encode($envelope), 'orders');
// the BabelQueue "queue" maps to persistent://public/default/orders
The message value is the canonical envelope; bq- native properties (string→string,
hyphens) carry the contract fields. The body’s meta.created_at stays authoritative — the
adapter must not set the frame’s eventTime.
StompTransport (Artemis)
Apache ActiveMQ Artemis (§7) speaks AMQP 1.0, which has no strong PHP client, so PHP
reaches it over STOMP (the mature pure-PHP stomp-php client; Artemis ships a STOMP
acceptor) — GR-7 intact (ADR-0018).
Artemis bridges STOMP ↔ AMQP 1.0 ↔ JMS on the same address, so a STOMP-produced message is
consumed natively by the Java (JMS) and .NET / Node / Python / Go (AMQP 1.0) SDKs:
composer require stomp-php/stomp-php
use BabelQueue\Transport\StompClient;
use BabelQueue\Transport\StompTransport;
$client = new class (/* Stomp\Client */) implements StompClient {
public function send(string $destination, string $body, array $headers): void {
// SEND the envelope JSON to the anycast address with the bq_ headers
}
};
$transport = new StompTransport($client, 'orders');
$transport->publish(EnvelopeCodec::encode($envelope), 'orders');
Routing is body-authoritative (a STOMP header can’t set the x-opt-jms-type
annotation, so consumers fall back to the body’s job URN, which is always present). The
§7 fields ride correlation-id (= trace_id) and the bq_ application properties —
note the underscores (JMS forbids hyphens in property names —
ADR-0017).
Consuming with the core. These transports are producers. PHP consumes Kafka and Pulsar with the framework-less
KafkaConsumer/PulsarConsumer, and Artemis with the Laravelbabelqueue-artemisdrop-in driver.
On Laravel or Symfony you don’t need these — the Laravel driver and the Symfony serializer move the bytes through the framework’s own transport.