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.