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

Worker Bundle Laravel Package

treehouselabs/worker-bundle

A Symfony bundle for running background workers as services. It integrates TreeHouse Labs Worker with the container and console, making it simple to configure, start, and manage long-running job-processing processes in Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: it is a Symfony bundle, not a Laravel package. There is no service provider, no config/queue.php driver, and no Illuminate\Contracts\Queue\Job. The 2019 release pins Symfony 3/4-era components and an old pda/pheanstalk; composer requireing it into a modern Laravel app will almost certainly produce a dependency conflict. Treat it as a reference implementation for Beanstalkd-backed workers, and as something you only actually run inside a legacy Symfony service.

Where the interesting code lives (start here, in this order):

  1. src/JobInterface.php — the job contract. Roughly getQueue() (the tube name) plus an execute() method. Verify the exact signatures before copying anything.
  2. src/Worker.php — the producer/consumer wrapper around Pheanstalk: addJob(...), watch($tube), run().
  3. src/Command/WorkerCommand.php — the worker:run loop. This is where timeouts, signal handling and per-job logging are (or aren't) implemented.
  4. src/DependencyInjection/Configuration.php + Resources/config/services.yml — the only real config surface: Beanstalkd host/port and the logger service.

Minimal Symfony usage looks like:

// AppKernel::registerBundles()
new Treehouse\WorkerBundle\TreehouseWorkerBundle(),

// config.yml
treehouse_worker:
    connection: { host: 127.0.0.1, port: 11300 }

// producing
$worker->addJob(new SendInvoiceJob($invoiceId));

// consuming
// bin/console worker:run invoices

First use case for a Laravel developer: don't install the bundle — reproduce it. Laravel already ships a Beanstalkd driver that does the same job with better ergonomics:

// .env
QUEUE_CONNECTION=beanstalkd
BEANSTALK_HOST=127.0.0.1

// config/queue.php
'beanstalkd' => [
    'driver'      => 'beanstalkd',
    'host'        => env('BEANSTALK_HOST', '127.0.0.1'),
    'queue'       => 'default',
    'retry_after' => 90,   // must exceed your longest job
    'block_for'   => null,
],
dispatch(new SendInvoice($invoiceId))->onQueue('invoices'); // -> tube "invoices"
php artisan queue:work beanstalkd --queue=invoices --tries=3 --timeout=60

Then verify the tube actually receives bytes instead of assuming — telnet 127.0.0.1 11300 and stats-tube invoices, or run beanstalkd -vv. Half of all "worker not running" incidents are a tube-name mismatch that a single stats-tube call would have exposed.

Implementation Patterns

One tube per job type, one worker process per tube. This is the pattern the bundle nudges you toward (getQueue() on every job) and it's the one that stays sane at scale: independent concurrency, independent retry policy, independent deploy. In Laravel this is --queue=invoices per supervisor program.

Jobs are tiny serializable DTOs, not service containers. The bundle serializes the job object into the Beanstalkd payload, so anything you put in the constructor goes over the wire. Pass scalars and IDs, never entities or the container; resolve real dependencies (repositories, mailers, HTTP clients) inside execute() / handle() from the container in the worker process.

final class SendInvoice implements ShouldQueue
{
    use InteractsWithQueue, Queueable;

    public function __construct(public int $invoiceId) {}

    public function handle(InvoiceMailer $mailer): void
    {
        $mailer->send($this->invoiceId); // fetch fresh inside the worker
    }
}

Idempotency is mandatory. Beanstalkd is at-least-once: a worker killed mid-job, or one that exceeds its TTR, gets the job re-reserved and re-run. Guard writes with an "already processed" check (unique constraint, Cache::add("invoice:{$id}:sent", true, 3600), or an update ... where status = 'pending').

Cross-stack interop is the one legitimate reason to care about this bundle. If you must have a Laravel producer and a legacy Symfony consumer on the same Beanstalkd instance, don't use either framework's envelope on the shared tube. Write a thin raw producer and have both sides decode a plain JSON body:

$pheanstalk->useTube('invoices')->put(
    json_encode(['id' => $invoiceId, 'type' => 'send_invoice']),
    Pheanstalk::DEFAULT_PRIORITY, 0, 120 /* ttr */
);

Keep framework-native envelopes (illuminate.queue.callback, serialized PHP objects) on private tubes only.

Deployment = restart workers. Supervisord program per tube, stopwaitsecs slightly greater than your max job runtime so SIGTERM lets the current job finish. On Laravel use php artisan queue:restart in the deploy hook; the Symfony equivalent is supervisorctl restart worker:* with a graceful SIGTERM.

Observability. The bundle wires a PSR-3 logger into Worker; mirror that with a dedicated channel (Log::channel('worker')) on the Laravel side and log job start/finish/fail with the job class and ID. Track stats-tube current-jobs-ready / current-jobs-buried — a rising buried count is your silent-failure alarm.

Gotchas and Tips

  • Archived, 11 stars, last release 2019. No security fixes, no PHP 8.2+ support, no Symfony 6. This is a read it, don't depend on it package. Score 0.45 is about right: fine as a design reference, risky as an install.
  • Payload format mismatch is the #1 interop failure. Laravel's Beanstalkd driver JSON-encodes an envelope (displayName, job, data, attempts, …), while the bundle almost certainly serialize()s the job object. Consuming one with the other will either throw on unserialize or — worse — succeed silently with garbage. Never point two different consumers at the same tube.
  • Tube naming is exact-match, case-sensitive, no prefixing. Laravel's ->onQueue('invoices') and the bundle's getQueue() both map 1:1 to a tube. A typo means the job sits in invoices forever while your worker idles on invoices. stats-tube first, always.
  • TTR vs retry_after mismatch causes double execution. Beanstalkd releases a reserved job back to ready when TTR expires; Laravel independently retries after retry_after. If retry_after ≤ your longest job runtime, or TTR ≤ it, the same job runs twice concurrently. Set retry_after comfortably above max runtime and pass --timeout below it.
  • ignore('default'). Every new Beanstalkd connection watches default in addition to whatever you watch(). Unless you explicitly ignore it, your invoices worker will also be fed generic jobs. Laravel's worker does this for you; raw Pheanstalk clients don't.
  • Buried jobs are invisible. There's no dead-letter concept. Roll your own: catch exceptions, bury with a failed tube or increment an attempts field in the payload, and write a small kick/requeue admin task. Laravel gives you failed_jobs + queue:retry for free — one of several reasons not to reimplement this in Symfony 3.
  • Long-running worker memory. Doctrine's identity map (or Eloquent's) grows unbounded. The bundle's command may or may not expose --max-jobs/--timeout — check WorkerCommand.php; if it doesn't, cap runtime via supervisord and restart. In Laravel: --max-jobs=500 --max-time=3600.
  • No auth, no TLS. Beanstalkd is a trusted-network protocol. Bind to localhost or a private subnet; never expose port 11300.
  • Extension points, mapped. In the bundle: Symfony's event dispatcher wraps worker start and job execution — grep dispatch( in src/ for the actual event names; also a custom PSR-3 logger service and JobInterface implementations for retry policy. Laravel equivalents: Queue::before/after/failing/looping, failed() on the job class, and job middleware (RateLimited, WithoutOverlapping).
  • Testing. Don't spin up a real Beanstalkd in unit tests. Laravel: Queue::fake() + Queue::assertPushedOn('invoices', SendInvoice::class). For the Symfony side, mock Pheanstalk and assert useTube()/put() were called with the expected tube and payload.
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