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

Task Bundle Laravel Package

insidieux/task-bundle

A Symfony bundle for defining, organizing, and executing application tasks. It integrates task management into Symfony projects, helping you structure jobs and run them consistently within your existing configuration and workflow. Useful for recurring maintenance, background work, or custom operatio...

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and register

composer require insidieux/task-bundle:^0.2.6

Take 0.2.6 or newer — that patch fixes concurrent updates to the task_queue row, which is exactly the failure mode you'll hit as soon as you poll status while a task is being written. Release cadence on this package is slow, so run a --dry-run first and read the require block before Composer mutates your lock file:

composer require insidieux/task-bundle:^0.2.6 --dry-run -v

Register the bundle the usual way. On Symfony 4/5 with Flex:

// config/bundles.php
return [
    // ...
    Insidieux\TaskBundle\InsidieuxTaskBundle::class => ['all' => true],
];

On a legacy Symfony 3 kernel, add it to registerBundles() instead.

2. Look here first

  • bin/console debug:container --tag=kernel.event_subscriber | grep -i task — see what the bundle wires up.

  • bin/console debug:container | grep -i task — find the manager/runner/storage service IDs; these are what you'll inject. The 0.2.6 changelog confirms one of them writes to a task_queue table, so a persistence-backed storage service exists.

  • Check the schema before writing any code. Look in vendor/insidieux/task-bundle/ for a Doctrine entity/mapping or SQL for task_queue. Then diff it against your database:

    bin/console doctrine:schema:update --dump-sql | grep -i task_queue
    

    If the table isn't there, either a migration ships with the bundle or you own creating it. Decide which, in writing, before it bites you in staging.

  • bin/console debug:config insidieux_task (or config:dump-reference insidieux_task) — confirm the actual configuration root name and available keys before writing YAML. The extension alias is derived from the bundle name, so verify rather than assume.

  • The package's own Resources/config/*.xml|yaml and DependencyInjection/Configuration.php in vendor/insidieux/task-bundle/ — with zero stars and zero dependents, vendor source is your real documentation.

3. First use case

The bundle's value is turning an existing bin/console command into a background job you can kick off from a request and poll. Start there:

// src/Controller/ReportController.php
public function export(TaskManagerInterface $tasks): Response
{
    $task = $tasks->create('app:reports:export', ['--since' => '2024-01-01']);
    $tasks->run($task); // async: returns immediately, process keeps running

    return $this->json(['id' => $task->getId()]);
}

Then a status endpoint the browser polls every couple of seconds. Don't build anything more elaborate until that round-trip works end to end — and confirm the status change actually lands in task_queue (query it directly after the first run) rather than trusting the API surface.

Implementation Patterns

Command-as-task is the dominant pattern. Keep the real work inside a Command class that is idempotent and safe to re-run. The bundle should be a thin launcher, not a home for business logic — that way the same code runs interactively (bin/console app:reports:export) during development and in the background in production.

class ExportCommand extends Command
{
    protected static $defaultName = 'app:reports:export';

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // write results to a deterministic path, e.g. var/exports/{id}.csv
        // so the status endpoint can hand back a download URL
        return Command::SUCCESS;
    }
}

Async vs. sync. Prefer async only for work that exceeds a few seconds or must not block a request. For anything under ~2s, just call the command directly — a background process adds a queue table, a cleanup job, and failure modes you don't need.

Treat the task_queue row as shared mutable state. 0.2.6 exists because two writers can update the same row. Keep your own status writes narrow and atomic — a single UPDATE ... WHERE id = ? AND status = ? — instead of read-modify-write in PHP, and route writes through the bundle's storage service rather than flushing the entity yourself. Anything you add that touches that row is now your concurrency problem.

Lifecycle events for cross-cutting concerns. Listen for start/finish/fail events and centralize logging, metrics, and notification there rather than sprinkling it through controllers:

class TaskListener implements EventSubscriberInterface
{
    public function onFinished(TaskFinishedEvent $event): void
    {
        $this->logger->info('task.finished', ['id' => $event->getTask()->getId()]);
        $this->mailer->sendFinishedNotification($event->getTask());
    }
}

Wire it with kernel.event_subscriber (or #[AsEventListener] on newer Symfony). Confirm the exact event class names via bin/console debug:event-dispatcher | grep -i task — the string constants are what you actually subscribe to.

Status polling contract. Expose a small JSON endpoint returning {status, startedAt, finishedAt, exitCode, outputUrl}, read from task_queue rather than from in-process state. Map the bundle's internal statuses onto a narrow vocabulary your frontend understands (queued|running|done|failed) so an upgrade or a swap to Symfony Messenger doesn't break the UI.

Retention. A queue table that gets a row per job grows forever. Add a pruning command on a cron (DELETE FROM task_queue WHERE finished_at < :cutoff) and keep finished rows long enough to debug failures — a week is usually right. Do it before the table is large, not after.

Composition with Messenger. If you're on Symfony 4.4+, consider using Messenger for the async dispatch and keeping this bundle only for process supervision and status tracking. They overlap; pick one as the source of truth for "is it running."

Testing. Assert on your command in isolation with CommandTester. For the launcher path, swap the process/runner service in a test container with a fake so your controller tests don't fork real processes — and point the storage at a test table you truncate between tests. Never let the test suite spawn background jobs.

Gotchas and Tips

  • Upgrade to 0.2.6 for the concurrency fix. The prior behaviour allowed concurrent updates to clobber a task_queue row, which shows up as tasks stuck in running or reverting to queued. If you're pinned below it, upgrade before you debug anything else.
  • Symfony compatibility is the top risk. A 0.x bundle that last saw a feature release around 2020 predates Symfony 6 and 7. Expect constraints like ^4.4|^5.0. On Symfony 6+, either stay on the old framework, fork and widen the constraints, or replace it — a 0-star, 0-dependent package will not get upstream fixes beyond patches like this one. Vendor the fork under a namespace you control if you go that route.
  • PHP-FPM has a different environment than your CLI. PATH, HOME, and env vars differ, so a command that works in bin/console may fail as a spawned process. Set the working directory and env explicitly, and prefer absolute paths to binaries.
  • Never interpolate user input into a shell string. If the API takes an array, pass an array. If it only takes a string, validate against an allowlist of commands and arguments — argument injection into a spawned process is an RCE vector.
  • Timeouts and orphan processes. A default Process timeout will kill long exports; set null deliberately and add a heartbeat or max-runtime guard. On failure paths, make sure a SIGKILL/stop() actually reaps the child, otherwise you accumulate zombie PHP processes — and rows that never leave running.
  • Load balancing is now a DB concern, not an in-memory one. Status lives in task_queue, so polling works across nodes. The remaining traps are read replicas lagging behind a just-written status and any caching layer in front of the status endpoint — both will make a finished task look stuck.
  • Don't hand-roll the row updates. A listener or a controller that loads the entity, mutates status, and flushes reintroduces exactly the race 0.2.6 fixed. Go through the bundle's storage/manager service.
  • Output capture grows unbounded. Process stdout/stderr buffers can blow past memory_limit on chatty commands. Redirect output to a file and store the path, rather than holding the string or writing it into the table.
  • Permissions on var/, logs, and the DB user. The web user and the CLI user often differ; a process spawned from FPM can't write to files your console user created. Same for the Doctrine connection if the worker runs under a different env.
  • Debugging checklist: debug:container for the actual service IDs, debug:config for keys, debug:event-dispatcher for event names, and Monolog at debug for the task channel. Add $process->getErrorOutput() to your logs on non-zero exit, and SELECT * FROM task_queue ORDER BY id DESC LIMIT 5 when the UI says "running" and nothing is happening — silent failures are the standard symptom.
  • Extension points worth knowing: a custom storage implementation (if the shipped one doesn't fit your retention or polling needs), a custom process factory (to inject env, timeouts, working directory), and an event subscriber for logging/metrics. All three are small classes; each beats patching vendor code.
  • Pin the version and audit it. "insidieux/task-bundle": "0.2.*" rather than ^0.2, and run composer outdated in CI. With no maintainers and no dependents, treat it as an internal dependency you own — read every changelog line, since patches like 0.2.6 land without a migration path or deprecation notice. Keep an extraction plan: the surface is small enough that replacing it with a thin symfony/process wrapper plus a status table is a day's work.
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.
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
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums