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

Zend Queue Laravel Package

zendframework/zend-queue

Zend Queue provides a unified PHP interface for message queue systems, with adapters for arrays, databases, Memcache, MongoDB, and other backends. It supports sending, receiving, and processing messages through a consistent API, simplifying asynchronous job handling in Zend Framework applications.

View on GitHub
Deep Wiki
Context7

Getting Started

zendframework/zend-queue is an abstraction over queue backends, not a queue worker. It gives you one interface (ZendQueue\Queue) over Array, Db (via zend-db), Memcache, MongoDB, and AMQP adapters, plus a small Message value object. There is no retry policy, no failed-job table, no delayed dispatch, and no Laravel integration — you bring all of that. Treat it as a transport, not a framework.

Install and orient:

composer require zendframework/zend-queue
# or, if you want the maintained namespace:
composer require laminas/laminas-queue

Read in this order: src/Queue.php (the adapter contract), src/Adapter/AbstractAdapter.php (options plumbing and getCapabilities()), src/Adapter/* (pick your backend), src/Message/*. The adapter classes are the real documentation — the README is thin.

Bind it like a Laravel queue driver, using a config file that mirrors config/queue.php:

// config/zendqueue.php
return [
    'default' => env('ZENDQUEUE_ADAPTER', 'db'),
    'adapters' => [
        'db'    => ['adapter' => \ZendQueue\Adapter\Db::class,        'options' => ['db' => 'legacy', 'queueName' => 'events']],
        'array' => ['adapter' => \ZendQueue\Adapter\ArrayAdapter::class, 'options' => []],
    ],
];
// AppServiceProvider::register()
$this->app->singleton(\ZendQueue\Queue::class, function ($app) {
    $cfg = config('zendqueue');
    $c   = $cfg['adapters'][$cfg['default']];
    return new $c['adapter']($c['options'] ?? []);
});

First use case: a Laravel artisan command that drains an existing queue (a legacy ZF app's table, an AMQP exchange, or a Mongo collection) and hands work to Laravel. That interop scenario is the honest reason to use this package at all.

Implementation Patterns

Wrap it in a producer/consumer service. Give your app a small class with push(string $body), pop(int $max, int $timeout), ack(Message $m) so the rest of the codebase never imports ZendQueue names directly. This also makes swapping to Illuminate\Queue later trivial.

Worker loop. Adapters do not block indefinitely; receive($max, $timeout) returns after the timeout with a partial (often empty) collection. You own the loop, the sleep, and the error handling:

while (true) {
    $messages = $queue->receive(10, 5);
    foreach ($messages as $msg) {
        try {
            $payload = json_decode($msg->getBody(), true, 512, JSON_THROW_ON_ERROR);
            dispatch_sync(new ProcessLegacyEvent($payload));
            $queue->deleteMessage($msg);              // ack = delete
        } catch (\Throwable $e) {
            Log::error('zendqueue.failed', ['handle' => $msg->getHandle(), 'error' => $e->getMessage()]);
            // no native retry: requeue, park in dead_letters, or delete to avoid poison loops
        }
    }
    if (count($messages) === 0) { sleep(1); }
}

Run it under Supervisor, not queue:work — Laravel's worker knows nothing about this queue.

Bridge outbound to legacy. In a Laravel job, JSON-encode an envelope (['type' => ..., 'payload' => ..., 'attempt' => 0]) and $queue->send($body). Keep the envelope versioned and schema-validated; you'll be debugging both sides.

Bridge inbound to Laravel. Prefer dispatching a Laravel job from the drain command (dispatch(new ProcessLegacyEvent($payload))) rather than doing work in the loop, so you get retries, backoff, and failed_jobs for free.

Native queue connector (advanced). Implement Illuminate\Contracts\Queue\Queue over the adapter and register with Queue::extend('zendqueue', fn () => new ZendQueueConnector($app)). Worth it only if you intend dispatch() to route through this transport long-term; it's ~150 lines of contract methods (size, push, pop, later, pushRaw) with no retry semantics.

Tests. Use the Array adapter — it's in-memory, instant, and good for exercising your envelope encoding and ack logic without touching infrastructure.

Schema. For the Db adapter you must create tables once. Call $queue->create('events') from a one-off command/migration (or copy the adapter's CREATE TABLE SQL into a migration) — the adapter's DDL is not versioned against the repo's schema.

Gotchas and Tips

  • Archived, PHP 8 rough edges. BSD-3-Clause and still installable, but the code predates modern typing; expect deprecation noise on 8.x. Pin your PHP version in CI or prefer laminas/laminas-queue.
  • Namespace drift is real. Early ZF2 used Zend\Queue\*; the published package is ZendQueue\*. Old blog posts and ZF1 Zend_Queue docs describe the same concepts with different names — useful for semantics, useless for copy-paste.
  • No retries, delays, or dead-lettering. send() is fire-and-forget, receive() has no visibility guarantee beyond the adapter's timeout, and there is no backoff. Implement at-least-once yourself: persist attempts in the payload, requeue with a count, and cap it.
  • Forgetting deleteMessage() silently duplicates. Ack explicitly, always in a finally-adjacent path. On the Db adapter, messages become visible again after the timeout column expires, so a crashed worker redelivers.
  • Metadata is adapter-dependent. Body round-trips everywhere; headers/metadata often do not (the Db adapter stores body plus bookkeeping columns). Encode everything you need in the body.
  • Call getCapabilities() before relying on features. Not every adapter supports count(), queue listing, or timeouts, and some fail loudly at runtime instead of at construction.
  • count() can be a full table scan. Never put it on a hot dashboard path.
  • AMQP/Mongo connections are heavy and stateful. Construct once per worker (singleton), and add reconnect logic around receive(). Under Octane, flush the singleton on RequestTerminated — don't leak a stale socket across requests.
  • Constructor shapes differ between adapters. Db wants a Zend\Db\Adapter\Adapter (or DB options), AMQP wants credentials/host, Array takes nothing meaningful. Validate your config mapping in the service provider rather than trusting the adapter to throw something readable.
  • create()/drop() mutate infrastructure. Never call them from application code paths; they issue DDL and require elevated permissions.
  • Debugging tip: log getHandle() and body length at both send and ack. When messages vanish, the difference between "never sent," "sent but not acked," and "acked but handler threw after" is usually visible in that one line.
  • Extension points: subclass ZendQueue\Adapter\AbstractAdapter for a custom backend (e.g., SQS or Redis) and implement send/receive/deleteMessage; subclass ZendQueue\Message\AbstractMessage only if you need typed accessors — but keep the wire format plain JSON strings.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource