Reliability & governance helpers

The Laravel driver is a thin adapter on the framework-agnostic PHP core, so the core’s optional reliability and governance helpers are available to a Laravel app directly — they live in babelqueue/php-sdk, which the driver already pulls in. None touch the frozen envelope (schema_version: 1); each is a tooling layer that composes with your handlers and the standard queue:work worker. They are the Laravel face of the cross-SDK spec — follow the linked spec page for the full contract, and the php-sdk reference for the complete API.

Idempotency

A handler that fires an external effect (a charge, an email) should be idempotent under at-least-once delivery. Wrap the work in Idempotent::wrap so a redelivered meta.id is skipped, not re-run:

use BabelQueue\Idempotency\Idempotent;
use BabelQueue\Idempotency\PdoStore;

final class OrderCreatedHandler
{
    public function handle(array $data, array $meta, string $traceId): void
    {
        $store = new PdoStore(\DB::connection()->getPdo()); // shared across the fleet
        Idempotent::wrap($store, fn () => $this->chargeOnce($data))($data, $meta);
    }
}

For a fleet, use the shipped persistent PdoStore / RedisStore (both implement the ClaimingStore claim/release contract) instead of InMemoryStore; PdoStore::ddl() emits the portable CREATE TABLE for a migration. See Idempotency and the store deep-dive.

Transactional outbox

To remove the dual write between your Eloquent write and the publish, persist the encoded envelope in the same DB transaction, then drain it with a relay (a scheduled command or queued job):

use BabelQueue\Outbox\Outbox;

\DB::transaction(function () use ($order, $outbox) {
    $order->save();                 // business row
    $outbox->write($envelope);      // the message, same transaction — no commit of its own
});

Outbox, OutboxRelay and the four-method OutboxStore (save / fetchUnpublished / markPublished / markFailed) live in BabelQueue\Outbox; back the store with a PDO/ORM adapter whose save() runs on the open transaction. See Transactional Outbox.

DLQ redrive & replay-bypass

BabelQueue\Redrive\Redrive::run($io, $dlq, $options) moves dead-lettered messages back onto a queue (reset, dry-run, sandbox toQueue, select, bypass) — typically from an Artisan command. The ReplayBypass guard lets a deliberate replay re-run the idempotent core while skipping effects that already fired:

use BabelQueue\Redrive\ReplayBypass;

public function handle(array $data, array $meta, string $traceId): void
{
    $this->saveOrder($data);                                   // idempotent core
    ReplayBypass::bypassExternalEffects($message, fn () => $this->sendEmail($data));
}

See DLQ redrive & replay-bypass.

GDPR field encryption

BabelQueue\Gdpr\Gdpr::protect() / ::unprotect() encrypt only the data leaves a schema marks x-gdpr-sensitive, leaving data pure JSON and the envelope frozen. OpenSslCipher (over ext-openssl) is the reference; bind your own Cipher to a KMS in production. Protect before publishing, unprotect at the top of the handler — and validate cleartext on both sides. See GDPR field encryption.

OpenTelemetry (traceparent)

BabelQueue\Otel\Tracing emits publish <urn> / process <urn> spans and, on a header-carrying transport (Redis/AMQP/SQS), injects/extracts the W3C traceparent so a consumer span is a true child of the producer span — degrading to v0.1 trace_id correlation otherwise. The trace_id already threaded through your handler logs is the v0.1 floor. See Observability.

Per-URN schema validation

BabelQueue\Schema\SchemaValidated::assert() (producer guard) / ::wrap() (consumer safety net) validate a message’s data against the JSON Schema registered for its URN, bridged from a babelqueue-registry manifest via DirProvider — run bqschema (including the gdpr --require PII audit) in CI. See Per-URN schema validation.