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

fm/worker-bundle

fm/worker-bundle adds a structured worker layer to PHP applications, helping you organize, run, and monitor background jobs with less boilerplate. It integrates cleanly with existing frameworks and keeps long-running tasks reliable and maintainable.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the premise check. fm/worker-bundle is a Symfony bundle, not a Laravel package. It hooks into Symfony\Component\HttpKernel\Bundle\Bundle, the Symfony DI container, and the console component's command lifecycle. Laravel's container, service providers, and artisan are a different contract. There is no composer require that "just works" in a Laravel app.

Second, the status check. The package's own description says it is superseded by treehouselabs/worker-bundle. Zero stars, zero dependents, no maintenance signal. For a Laravel team the correct default answer is do not adopt. The only reason to read further is that it is already sitting in a legacy service's composer.lock and you need to understand or remove it.

Where to look first (legacy Symfony case only):

  1. DependencyInjection/Configuration.php — the authoritative config tree. Blog posts and READMEs lie; this file is what the container validates.
  2. Command/ — the console entrypoint Supervisor will call. After registering the bundle in AppKernel/config/bundles.php, run bin/console list and find the worker command.
  3. composer.lock across your repos — confirm whether anyone actually depends on it before planning anything.

The mental model that carries over to Laravel: this bundle family is "a Symfony console command invoked repeatedly or continuously by an external supervisor." It is not a job framework. The Laravel equivalent is exactly a Job class + php artisan queue:work under Supervisor, or Horizon on Redis. Learn the concept here, then implement it natively.

Smallest first use case — one command you already trust, wrapped:

// src/AppBundle/Command/SendMailCommand.php
protected function configure()
{
    $this->setName('app:send-mail')
        ->addOption('limit', null, InputOption::VALUE_OPTIONAL, '', 100);
}

protected function execute(InputInterface $input, OutputInterface $output)
{
    $sent = $this->mailer->drain((int) $input->getOption('limit'));
    $output->writeln(sprintf('sent %d', $sent));

    return 0; // "no work" is success — see Gotchas
}

If the command cannot be run manually, once, and produce a correct result, it is not ready to be a worker.

Laravel-side equivalent to start from instead:

php artisan make:job SendMail
php artisan queue:work --queue=mail --tries=3 --timeout=60

Implementation Patterns

Pattern 1 — Command + external supervisor (the bundle's actual model)

The bundle gives you definitions and lifecycle, not a daemon. You still need Supervisor or systemd to restart it. Every worker you write should be:

  • Bounded: process N items, then exit. Let Supervisor restart it. This is the single best defence against PHP memory leaks in long-running processes.
  • Idempotent: assume at-least-once delivery. Use INSERT ... ON DUPLICATE KEY / firstOrCreate on the Laravel side.
  • Signal-aware: handle SIGTERM to finish the current item and flush before exit.

Pattern 2 — Config-driven worker definitions

The bundle centralises worker config. Treat that config as a manifest you will later translate to queue names and Supervisor programs:

# legacy Symfony config — inventory this, do not extend it
fm_worker:
    workers:
        mail: { command: 'app:send-mail', sleep: 5, limit: 100 }
; the Laravel translation of the same idea
[program:mail-worker]
command=php /var/www/artisan queue:work redis --queue=mail --sleep=5 --tries=3 --max-time=3600
numprocs=2
autostart=true
autorestart=true
stopwaitsecs=90
stdout_logfile=/var/log/worker-mail.log

Pattern 3 — Graceful lifecycle in the Laravel direction

The bundle advertises start/stop/restart hooks. In Laravel these are queue:restart (signal-based, safe mid-job) and Horizon's horizon:terminate. Model your migration on those, not on bespoke PID-file handling.

Pattern 4 — Observability hooks

The extension points for logging/monitoring map cleanly to Laravel primitives:

Bundle concept Laravel equivalent
Worker start/stop event Queue::before / Queue::after, or a JobProcessing listener
Custom processor hook handle() on the Job, or middleware via middleware()
Worker logging Monolog channel + Log::withContext(['job' => ..., 'attempt' => ...])
Monitoring Horizon metrics, or a heartbeat table written each loop

Pattern 5 — Per-queue migration, not a flag day

If you are extracting:

  1. Inventory (grep for the bundle namespace, inspect composer.lock).
  2. Map each worker definition → one Laravel Job class + queue connection.
  3. Stand up native workers in parallel in staging.
  4. Compare throughput, failure rate, and memory over a 24h soak.
  5. Cut over one queue at a time; keep the old command as rollback for a release cycle.
  6. Remove the bundle and its transitive symfony/* constraints.

Integration tip — do not bridge the container

You can write a Laravel service provider that instantiates Symfony commands. Don't. You'd own two container conventions, two config systems, and two debugging paths forever, to run code you could express as a Job in ten minutes. Run any Symfony worker as a separate process, call it over a queue or CLI, and never load it into the Laravel request lifecycle.


Gotchas and Tips

The dependency is dead — budget for a fork. A superseded package receives no PHP-version or security patches. Any fix becomes yours. This is a permanent maintenance line item, not a one-time integration cost.

Exit codes drive Supervisor behaviour — get them right. A worker that returns non-zero for "no work available" will be treated as crashed and restarted in a tight loop, burning CPU and filling logs. Return 0 for "ran fine, nothing to do"; reserve non-zero for real failures, and pair with startretries / stopwaitsecs in Supervisor. The return $sent > 0 ? 0 : 1 idiom you'll find in examples is a restart-storm waiting to happen.

stopwaitsecs must exceed your job timeout. Otherwise Supervisor SIGKILLs the process mid-job → duplicate execution of a non-idempotent side effect. Set stopwaitsecs (90s) > --timeout (60s).

Composer will fight you if you try to co-install. Laravel pulls symfony/console, symfony/process, symfony/http-foundation. A Symfony bundle will pin those via symfony/framework-bundle and force downgrades that ripple into unrelated Laravel internals. Always check before spiking:

composer why-not symfony/framework-bundle <version>
composer why-not fm/worker-bundle

Long-running processes leak. Doctrine's EntityManager goes stale and accrues memory. If you inherit a Symfony worker: $em->clear() every iteration, and cap iterations. The Laravel --max-time / --max-jobs flags exist for exactly this reason — use them from day one.

Signal handling needs pcntl. Graceful shutdown in PHP requires the pcntl extension and pcntl_async_signals(true) (PHP 7.1+). If it's missing from the container image, "graceful stop" silently degrades to a hard kill. Check php -m | grep pcntl before you promise anyone a graceful restart.

Config changes need a cache clear. Symfony caches the compiled container; editing the worker YAML without bin/console cache:clear (or --env=prod rebuild) leaves you debugging a config that isn't loaded. Laravel's equivalent trap is config:cache.

No health endpoint. You will build liveness yourself — write a heartbeat row or touch a file at the top of each loop, and alert on staleness. A supervisor that dies without alerting produces silent backlog growth, which is high-severity and low-detectability.

Orphaned children after a parent crash cause duplicate job execution and resource exhaustion. If you must run forked workers, reap with pcntl_waitpid(-1, $status, WNOHANG) in the loop and never assume the parent's death kills the children.

Extension points, ranked. Prefer, in order: (1) a new console command, (2) an event subscriber on the console events, (3) patching the bundle. Anything touching the forking/lifecycle code should be considered a fork you now own — write a test before you change it, because there is no upstream test suite to lean on.

The actionable output is the successor, not the fork. If a lockfile reference turns up, the migration target is treehouselabs/worker-bundle — but subject it to the same checks (last commit, open issues, dependents). Succession does not confer health, and for a Laravel-first stack the genuinely correct target is the native queue plus Horizon.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings