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

Queue Bundle Laravel Package

rybakdigital/queue-bundle

rybakdigital/queue-bundle offers a lightweight foundation for configuring and managing queued jobs in PHP applications, with a simple setup for integrating background task processing into your project.

View on GitHub
Deep Wiki
Context7

Getting Started

Read the source before you composer require. This is a 2019 package with 0 stars and 0 dependents, and the description says "bundle" — that's Symfony/Doctrine vocabulary, not Laravel. There is almost certainly no Illuminate\Support\ServiceProvider, no config/queue.php driver, and no php artisan queue:work integration. Treat it as a transport library you will wrap, not a drop-in queue backend.

Where to look first, in this order:

  1. composer.json — check the php constraint and whether it pulls symfony/* packages transitively. Laravel 10/11 runs PHP 8.1+, so a ^7.1-era constraint may still resolve; a ^5.5|^7.0 one will not.
  2. src/ — find the transport interface (the thing you'll bind) and the concrete adapters. The package's public surface is essentially push($job, $priority, $delay) and a consume/work loop.
  3. tests/ — usually the only accurate documentation for a package this size. Copy the test's fake/in-memory transport for your own integration tests.
  4. README.md — last; for an unmaintained package it is frequently wrong about the current API.

Minimal wiring:

composer require rybakdigital/queue-bundle
// app/Providers/QueueBundleServiceProvider.php
public function register(): void
{
    // Bind the package's transport interface (verify the real FQCN in src/)
    $this->app->singleton(\RybakDigital\Queue\TransportInterface::class, function ($app) {
        return new \RybakDigital\Queue\Transport\YourBackendTransport(
            host: config('queue.connections.bundle.host'),
            port: config('queue.connections.bundle.port'),
            queue: config('queue.connections.bundle.queue', 'default'),
        );
    });
}

First use case: offloading one slow, self-contained job (a report render, an export) where you can prove the round-trip end-to-end. Do not make your first use case "migrate the whole app off Laravel's queue" — the gap between this package and Illuminate\Queue is large (see Gotchas).

First consumer, wrapped as an Artisan command so you keep supervisor/deploy ergonomics:

class BundleWorkCommand extends Command
{
    protected $signature = 'bundle:work {--once} {--max-jobs=500} {--max-time=3600}';

    public function handle(TransportInterface $transport): int
    {
        $start = time();
        $processed = 0;

        $transport->consume(function ($job) {
            app()->call([new $job->className, 'handle']); // decode + dispatch yourself
        }, shouldStop: function () use ($start, &$processed) {
            return $this->option('once')
                || ++$processed >= (int) $this->option('max-jobs')
                || (time() - $start) >= (int) $this->option('max-time');
        });

        return self::SUCCESS;
    }
}

Implementation Patterns

Adapter-first: hide it behind your own interface. Every call site should talk to your JobDispatcher, not the package. This is the single decision that makes the package replaceable (and, realistically, removable) later.

interface JobDispatcher
{
    public function dispatch(object $job, ?int $delaySeconds = null, int $priority = 0): string;
}

Implement it once against the bundle, once against Illuminate\Bus\Dispatcher — and toggle via config. You get an instant escape hatch and a way to A/B the two in staging.

Binding pattern. Use singleton, not bind, for transports — they hold connections and (often) internal channel state. Register the adapter in a deferred provider so you don't open a socket on every web request that never queues anything.

Payload discipline: send IDs, not models. Laravel's SerializesModels, Queueable, InteractsWithQueue, ShouldQueue, and ShouldBeUnique are all Illuminate traits — the bundle knows nothing about them. Keep payloads to scalars and version them:

['v' => 1, 'type' => 'export', 'order_id' => $order->id, 'attempt' => 1]

Re-fetch the model in the handler. This also means a payload written today survives a schema change next month.

Delay and priority normalization. Laravel's ->delay() accepts a DateTimeInterface/DateInterval; most framework-agnostic transports want integer seconds or a timestamp. Normalize in exactly one place:

$delaySeconds = $job->delay instanceof \DateTimeInterface
    ? max(0, $job->delay->getTimestamp() - time())
    : (int) $job->delay;

Priority conventions also differ (higher-is-better vs lower-is-better). Write one test asserting the mapping; don't discover it in production.

Retries: own them yourself. Don't assume the bundle's retry config maps to Laravel's $tries/$backoff/failed() callback. Carry an attempt counter in the payload, implement backoff in the wrapper, and push a fresh job on retry so the counter increments deterministically:

try {
    $handler->handle($payload);
} catch (RetryableException $e) {
    if ($payload['attempt'] >= 3) {
        $this->dispatchToDeadLetter($payload, $e);
        return;
    }
    $dispatcher->dispatch($payload, delaySeconds: 2 ** $payload['attempt']);
}

Bridge the lifecycle into Laravel events. Emit Illuminate\Queue\Events\JobProcessing / JobProcessed / JobFailed from your consumer, or your own equivalents, so existing listeners, logging, and metrics keep working. It costs ten lines and saves every observability tool you already own.

Testing. Bus::fake() and Queue::fake() will not intercept this transport, and assertPushed() won't see anything. Fake your JobDispatcher interface instead, and keep one integration test that runs against the real transport (a local backend or the package's in-memory fake from tests/).

Pilot with shadow traffic. Run the bundle in parallel with your current queue for a week: dispatch to both, consume only from the old one, and diff results. This catches serialization and idempotency bugs before they cost you a job.

Gotchas and Tips

  • "Bundle" means Symfony. Expect no service provider auto-discovery, no queue.php connection driver, and no artisan queue:work. Everything in the consumer command above is glue you write and maintain. Budget for that honestly.
  • Abandonment risk is the headline. Last release April 2019, zero dependents, zero stars, MIT. There will be no CVE fixes. Pin the exact version in composer.json, commit the lockfile, and if it's load-bearing, git clone it into packages/ as a path repository so you can patch it. Run composer outdated and PHP 8.2+ deprecation checks (dynamic properties, ${var} interpolation) before assuming it installs cleanly.
  • You are giving up the entire Laravel queue surface: queue:work/queue:listen, --tries, --backoff, --timeout, --memory, the failed_jobs table, queue:retry, queue:failed, queue:flush, Horizon, job middleware, WithoutOverlapping, RateLimited, job batching/chaining, and Bus::fake(). Enumerate which of these you actually need before adopting; for most apps the answer is "all of them" and the package is the wrong tool.
  • Idempotency is not optional. Without clear visibility-timeout semantics from the transport, assume every job can run twice: at-least-once delivery, worker killed mid-job, retry after a slow ACK. Guard with a DB uniqueness constraint or a processed-jobs table keyed by job UUID.
  • Graceful shutdown is on you. Supervisor sends SIGTERM and kills after stopwaitsecs. Laravel's worker traps signals and finishes the current job; a naive while (true) loop will not. At minimum, check a flag set by pcntl_signal between jobs and call pcntl_async_signals(true).
  • Long-running consumers leak. DB::disconnect() and gc_collect_cycles() every N jobs, and cap the loop with --max-jobs/--max-time so supervisor restarts it. Laravel's queue:work does this for you; queue:listen doesn't, which is why it's the slow-but-safe option.
  • Don't let the bundle read your credentials. If its config format is Symfony YAML/XML, keep secrets in .env and inject them at construction from config(). Never commit its config file; never let it read $_ENV directly.
  • Logging and debugging. Point the package's logger at Laravel's Log via a small adapter — otherwise worker errors vanish into stderr and you're grepping supervisor logs at 2am. Log the transport name, queue name, job class, attempt number, and job ID on every failure. Job ID is what makes a stuck queue debuggable.
  • Extension point worth keeping. The transport abstraction itself is the only genuinely portable idea here. If you need to publish from a shared library that cannot depend on Illuminate\Bus, define your own push() interface in that library and write adapters for both the bundle and Laravel's dispatcher. You get the decoupling without the dependency.
  • Bottom line for a Laravel developer: greenfield, use QUEUE_CONNECTION=database|redis|sqs plus Horizon and move on. Reach for this package only when you must bridge pre-existing non-Laravel queue infrastructure, or when a shared package cannot depend on Illuminate. In that case, adopt it behind your own interface, pinned, forked, and with an exit plan written down first.
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.
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
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core