babelqueue/php-sdk

Framework-agnostic core for BabelQueue: the canonical polyglot queue envelope codec, contracts and dead-letter helpers. Framework adapters (Laravel, Symfony, ...) are built on top.

Maintainers

Package info

github.com/BabelQueue/php-sdk

Homepage

pkg:composer/babelqueue/php-sdk

Fund package maintenance!

muhammetsafak

Statistics

Installs: 236

Dependents: 3

Suggesters: 0

Stars: 1

Open Issues: 0

v1.13.0 2026-06-20 16:53 UTC

This package is auto-updated.

Last update: 2026-06-20 16:56:03 UTC


README

License: MIT

The framework-agnostic core of BabelQueue for PHP — the canonical polyglot queue envelope codec, contracts and dead-letter helpers. Framework adapters (babelqueue/laravel, babelqueue/symfony, …) are built on top of this.

You usually don't install this directly — you install an adapter:

composer require babelqueue/laravel    # Laravel
composer require babelqueue/symfony    # Symfony (Messenger)

…and the adapter pulls this core in. Install it directly only for a framework-less PHP app, or to build a new adapter.

composer require babelqueue/php-sdk

What's in here

Area Class Role
Codec BabelQueue\Codec\EnvelopeCodec Build / encode / decode the canonical {job, trace_id, data, meta, attempts} envelope (schema_version 1). The single PHP implementation of the wire format — every adapter reuses it, so Laravel and Symfony can't drift.
Contracts BabelQueue\Contracts\PolyglotJob Producible message: getBabelUrn() + toPayload().
BabelQueue\Contracts\HasBabelUrn / HasTraceId URN identity / optional trace-id propagation.
BabelQueue\Contracts\InboundMessage Read-only decoded view of a consumed envelope.
BabelQueue\Contracts\Transport Minimal publish seam (framework-less / adapter use).
Validation BabelQueue\Validation\EnvelopeValidator Consumer-side validation with a reason — quarantine an unsupported meta.schema_version instead of dropping it.
Transports BabelQueue\Transport\RedisTransport / AmqpTransport Optional framework-less reference Transport impls (Redis RPUSH; RabbitMQ durable + contract AMQP properties).
Dead-letter BabelQueue\DeadLetter\DeadLetter Annotate an envelope with the additive dead_letter block (ADR-0009).
Outbox BabelQueue\Outbox\Outbox / OutboxRelay / OutboxStore Transactional outbox (ADR-0029): persist the message atomically with the business write, relay it later. Dependency-free — OutboxStore is an interface you bind to your DB.
Routing BabelQueue\Routing\UnknownUrnStrategy fail / delete / release / dead_letter constants.
Support BabelQueue\Support\Uuid Dependency-free UUIDv4 (no ramsey/symfony-uid needed).
Errors BabelQueue\Exceptions\BabelQueueException / UnknownUrnException / InvalidEnvelopeException Exception hierarchy; InvalidEnvelopeException carries the rejection reason + envelope.

The contract this core implements — the canonical envelope, URN scheme, broker bindings and versioning policy — is documented at babelqueue.com. The golden conformance fixtures live in tests/fixtures/ — every PHP package must round-trip them.

Framework-less use

Produce the canonical envelope from a plain PHP app and let any other SDK consume it. The reference transports keep the core dependency-free — install only the broker client you use:

composer require predis/predis              # for RedisTransport
composer require php-amqplib/php-amqplib    # for AmqpTransport
use BabelQueue\Codec\EnvelopeCodec;
use BabelQueue\Transport\RedisTransport;
use BabelQueue\Validation\EnvelopeValidator;

// Produce — a Go/Python/Node consumer reads the identical envelope off "orders".
$transport = new RedisTransport(new Predis\Client('redis://localhost:6379'));
$transport->publish(EnvelopeCodec::encode(EnvelopeCodec::fromJob($job, 'orders')), 'orders');

// Consume (your own loop) — validate before dispatch, quarantine the unknown.
$envelope = EnvelopeCodec::decode($rawBody);
if ($reason = EnvelopeValidator::check($envelope)) {
    // $reason === 'unsupported_schema_version' → dead-letter, don't drop.
    return;
}

phpredis (ext-redis) users can implement the one-method Transport directly — it is just an rpush.

Transactional outbox (ADR-0029)

A plain producer makes a dual write — commit the business row and publish to the broker — two systems that can disagree on a crash. The outbox removes it: the message is written into the same database, in the same transaction as the business data (so they commit or roll back atomically), and a separate relay publishes it afterwards. No distributed transaction; exactly-once handoff into the broker (then at-least-once on the wire, deduped on meta.id by the consumer-side Idempotent::wrap, ADR-0022).

The helper is dependency-free (GR-7): the core defines OutboxStore and you bind it to your DB. The transaction boundary is yoursOutboxStore::save() runs inside the transaction you opened around your business write. The envelope is stored verbatim (GR-1) and relayed byte-for-byte, so trace_id is preserved end-to-end (GR-4).

use BabelQueue\Codec\EnvelopeCodec;
use BabelQueue\Outbox\Outbox;
use BabelQueue\Outbox\OutboxRelay;

// WRITE side — one transaction for the business row AND the message (your tx boundary).
$outbox = new Outbox($store);                 // $store implements OutboxStore (your DB)
$db->transaction(function () use ($db, $outbox, $order): void {
    $db->insertOrder($order);                                          // business write
    $outbox->write(EnvelopeCodec::make('urn:babel:orders:created', $order, 'orders'));
});                                            // both commit, or neither

// READ side — a worker/cron drains the durable rows onto the broker.
$relay = new OutboxRelay($transport, $store); // $transport is any BabelQueue Transport
$relay->drain();                              // publishes verbatim, marks published/failed

OutboxStore is four methods — save(), fetchUnpublished(), markPublished(), markFailed(). A reference InMemoryOutboxStore ships for tests; a runnable InitORM-backed adapter + the outbox-table DDL live in babelqueue-examples/outbox-initorm/, keeping this core DB-free.

Design

This core is the contract runtime, not a worker. It does not own a broker loop or retry — adapters bind to each framework's native queue (Laravel's drop-in driver, Symfony Messenger) and reuse that framework's worker/retry.

Testing

composer install
vendor/bin/phpunit

License

MIT © Muhammet Şafak.