Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Sdk Laravel Package

babelqueue/php-sdk

Framework-agnostic core of BabelQueue for PHP: the canonical polyglot queue envelope codec, contracts, validation, transports, dead-letter and idempotency helpers that all framework adapters (Laravel, Symfony) build on. Install directly only for framework-less apps or new adapters.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the adapter, not the core. In a Laravel app the entry point is composer require babelqueue/laravel; this core (babelqueue/php-sdk) is pulled in transitively. Only composer require babelqueue/php-sdk directly if you're building a framework-less service or writing your own adapter. Note the Laravel adapter itself is not documented in this core README — read its own docs, and come back here for the wire format and helper behaviour.

Read these four files first, in this order:

  1. BabelQueue\Codec\EnvelopeCodec — the single PHP implementation of the wire envelope ({job, trace_id, data, meta, attempts}, schema_version 1). Everything else is built around it.
  2. BabelQueue\Contracts\PolyglotJob — the two methods you actually implement: getBabelUrn() + toPayload().
  3. BabelQueue\Validation\EnvelopeValidator — consumer-side gate that returns a reason, not a boolean.
  4. BabelQueue\Contracts\Transport — one method; tells you what a broker binding must do.

First useful thing to build: a PHP producer / Go consumer pair. This is the entire point of the package — dropping serialize().

use BabelQueue\Codec\EnvelopeCodec;
use BabelQueue\Contracts\PolyglotJob;

final class OrderCreated implements PolyglotJob
{
    public function __construct(private array $order) {}

    public function getBabelUrn(): string { return 'urn:babel:orders:created'; }
    public function toPayload(): array   { return ['order_id' => $this->order['id'], 'total' => $this->order['total']]; }
}

$body = EnvelopeCodec::encode(EnvelopeCodec::make('urn:babel:orders:created', $job->toPayload(), 'orders'));

Publish that string over whatever transport the adapter uses, then point a non-PHP consumer at the same queue and assert it reads the identical JSON. Do this on day one — it validates your whole reason for adopting the package before you invest in the harder pieces (idempotency, outbox, DLQ).

Implementation Patterns

Model messages as DTOs, not arrays. Implement PolyglotJob per message type and let EnvelopeCodec::fromJob($job, 'queue') build the envelope. The URN becomes your routing key in the adapter and your ClaimingDispatch::on(...) / redrive selector later — treat it as a stable public identifier, since a Go or Python producer will emit the same string.

Validate before dispatch, always. EnvelopeValidator::check() returning a reason string is the design decision that matters operationally: unsupported_schema_version means a newer producer shipped — dead-letter it rather than return-ing silently into the void.

if ($reason = EnvelopeValidator::check($envelope = EnvelopeCodec::decode($raw))) {
    $this->quarantine($envelope, $reason);   // do not ack-and-drop
    return;
}

Pick your idempotency tier deliberately. Idempotent::wrap(new InMemoryStore(), $handler) is fine for a single local worker and tests; for anything with more than one process, jump straight to ClaimingDispatch::wrap(new PdoStore($pdo), $handler). In Laravel that PDO is trivially DB::connection()->getPdo(); run $pdo->exec(PdoStore::ddl()) in a migration and you're done with no new dependency. RedisStore is the alternative if you already have predis. The claim (INSERT unique-key / SET NX PX) is what stops two workers handed the same meta.id from both running.

Use the outbox when the business write and the publish must agree. Bind OutboxStore (four methods) to your DB, then:

DB::transaction(function () use ($outbox, $order) {
    Order::create($order);                                    // business write
    $outbox->write(EnvelopeCodec::make('urn:babel:orders:created', $order->toArray(), 'orders'));
});

Relay in a scheduled command or a dedicated worker via new OutboxRelay($transport, $store) → drain(). The stored envelope is relayed verbatim, so trace_id survives end-to-end.

Separate the idempotent core from external effects, then hook replay-bypass in. This is the pattern worth adopting even before you have a DLQ:

ReplayBypass::bypassExternalEffects($m, fn () => Mail::to($m->email)->send(new Confirmation($m)));

saveOrder($m) always runs; the email is skipped on a redrive. Requires the redrive side to use a HeaderRedriveIO — see gotchas.

GDPR: validate cleartext, then protect. The order matters and is easy to get wrong in a service class. On produce: SchemaValidated::assert(...) → Gdpr::protect($data, $schema, $cipher) → EnvelopeCodec::encode(...). On consume: decode → Gdpr::unprotect($data, $schema, $cipher) → validate/read. Bind a Cipher to your KMS in a container singleton; use OpenSslCipher only for local/test.

Dead-letter annotations and URN strategy are cheap wins. DeadLetter adds the additive dead_letter block; UnknownUrnStrategy gives you the four constants (fail / delete / release / dead_letter) so the unknown-URN policy is config, not a match scattered across handlers.

Tracing is opt-in. Wrap produce/consume in Tracing spans when you want cross-language correlation via trace_id, and rely on the traceparent header seam when the broker carries headers. Don't adopt it in the same PR as the transport migration.

Gotchas and Tips

  • The envelope is frozen. schema_version stays 1; trace_id and the shape don't change for new features. Anything new rides beside the envelope through HeaderPublisher / HasHeaders (replay-bypass, traceparent). If you find yourself wanting to add a key to data for plumbing, you want a header instead.
  • Optional dependencies are really optional. predis (Redis), php-amqplib (AMQP), ext-openssl (reference cipher), open-telemetry/api (tracing) are all suggests. The core is ext-json only. Install precisely what you use; don't drag a Redis client into a Laravel app that already has one unless RedisStore needs it.
  • Encrypt after validate, decrypt before validate. Reversing this order produces confusing schema failures. Also: a protected field becomes a ciphertext string, so a consumer that type-checks data before unprotecting will see a string where it expected an object.
  • Replay-bypass silently no-ops without HeaderRedriveIO. Over a plain RedriveIO the stamp can't be delivered and item->bypassed is false — meaning your "skipped" external effect actually fired. Check bypassed in redrive reporting, and still make external effects (vendor API idempotency keys) independently safe.
  • ClaimParkedException must not ack. It means a concurrent delivery holds the claim; let it redeliver. Swallowing it looks like success and stalls the message.
  • Claim TTL is a crash window, not a guarantee. A worker that dies between claim and commit leaves a TTL after which a redelivery re-runs. This is at-least-once by design — the outbox narrows the produce side only, not the consume side.
  • RedisTransport is one RPUSH. No visibility timeout, no ack, no retry semantics. It's a reference seam, not a queue abstraction — don't let its simplicity set your expectations for what the transport does. phpredis users are expected to implement Transport themselves; it's a single method.
  • Don't assume the Laravel adapter's serialization. If messages behave oddly across languages, dump the raw body first — a PHP object leaking into data is the failure mode this package exists to prevent, and it'll only surface at the boundary.
  • The conformance fixtures are your regression net. tests/fixtures/ holds the golden envelopes every SDK must round-trip. If you write a custom Transport or adapter, round-trip those fixtures in your own test suite; with 1 star and 0 dependents, that suite is your real spec.
  • Pin the version. Small ecosystem, single maintainer surface, last release dated in the future-ish window — treat upgrades as reviewable diffs, and lean on the frozen-schema guarantee so a minor bump can't change your wire format.
  • Debugging checklist for "consumer got nothing": decode succeeded? → EnvelopeValidator::check() reason? → URN registered in the adapter's routing map? → ClaimingDispatch::on() key matching the producer's URN exactly (typos here fail silently as unknown-URN)? → claim already held (parked)?
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator