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

Command Scheduler Bundle Laravel Package

jmose/command-scheduler-bundle

Symfony bundle for scheduling console commands with cron expressions. Define jobs in YAML, run due tasks via a single scheduler command, and track execution status and logs. A lightweight alternative to system cron for Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first: this is a Symfony bundle, not a Laravel package. composer require jmose/command-scheduler-bundle will not boot inside a Laravel app — it relies on Symfony's bundle registration, Doctrine entity mapping, and its own console runner. A Laravel developer gets value from it in two cases only: (a) you maintain a Symfony service alongside your Laravel app, or (b) you want to copy its DB-driven scheduling model into Laravel. Laravel already ships an equivalent execution engine (schedule:run + dragonmantank/cron-expression); what it lacks natively is the admin-editable task table.

If you're on the Symfony side, minimal path (current release is 3.0.1, Symfony 5 / PHP >= 7.2):

composer require jmose/command-scheduler-bundle:"^3.0.1"
  1. Register in config/bundles.php: Jmose\CommandSchedulerBundle\CommandSchedulerBundle::class => ['all' => true].
  2. Add config/packages/command_scheduler.yaml (the bundle exposes entity-manager and log-path options; copy the sample from the README rather than guessing).
  3. Create the scheduled_command table — generate a Doctrine migration or doctrine:schema:update --force in dev.
  4. Import the admin routes in config/routes.yaml (they land under a /command-scheduler-style prefix).
  5. Add the one cron entry that makes everything work:
* * * * * cd /var/www/app && php bin/console scheduler:run >/dev/null 2>&1

Where to look first: read two files and you understand the entire bundle — the ScheduledCommand entity (the schema: command, arguments, cron expression, priority, disabled, locked, last run, last return code, output/log path) and the scheduler:run command (selects due rows via cron-expression, executes, writes result back).

First use case: schedule something low-risk like a nightly cache:clear or a report command, add it through the admin UI (or scheduler:add), then force it once with scheduler:execute <id> to confirm the plumbing before trusting the cron.

Implementation Patterns

The pattern worth stealing: DB-driven schedules. Code-defined schedules require a deploy to change; an ops team often wants to add "run this at 03:15" without one. The bundle's model:

  • one table of tasks with command, arguments, cron_expression, priority, disabled, locked, last_run, last_return_code, log_file
  • one system cron entry, every minute, that runs the dispatcher
  • the dispatcher filters rows with CronExpression::isValidExpression() / isDue(), sorts by priority, takes a lock, runs, records exit code and output

Port to Laravel without leaving the framework:

// app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
    $tasks = Cache::remember('scheduled_tasks', 60, fn () =>
        ScheduledTask::where('enabled', true)->orderBy('priority')->get()
    );

    foreach ($tasks as $task) {
        $schedule->command($task->command, $task->arguments ?? [])
            ->cron($task->expression)
            ->withoutOverlapping($task->lock_minutes ?? 60)
            ->onOneServer()
            ->appendOutputTo(storage_path("logs/scheduled/{$task->id}.log"))
            ->onSuccess(fn () => $task->markSuccess())
            ->onFailure(fn () => $task->markFailure());
    }
}

Key detail: in Laravel you do not add a second cron entry. php artisan schedule:run every minute is the dispatcher; the loop above just re-registers events each minute and Laravel evaluates isDue() itself.

The expression semantics now match. 3.0.0 replaced the abandoned mtdowling/cron-expression with dragonmantank/cron-expression — the exact library Laravel's scheduler uses. That removes a real porting hazard: an expression validated in the Symfony admin UI will now evaluate identically under Schedule::cron(), including the same edge cases (day-of-week/day-of-month OR semantics, @daily-style macros where supported). Don't rewrite expression handling separately for the two sides.

Admin panel analogue. The bundle's selling point is the UI. In Laravel that's a Filament/Nova resource over ScheduledTask — validate with CronExpression::isValidExpression($input) on save (otherwise one bad row throws every minute, forever), and flush scheduled_tasks from cache in the model's saved/deleted events.

Symfony-side day-to-day workflows (if you're actually operating this):

  • Add via UI or bin/console scheduler:add, passing the command name, cron expression, and flags for name/arguments/priority.
  • Inspect with scheduler:list; check lastReturnCode and the per-task log file rather than the runner's own exit status.
  • Debug a task by forcing it with scheduler:execute <id> — it bypasses the expression entirely, which is exactly what you want when the expression is the suspect.
  • Use priority to order tasks that fall due in the same minute (e.g., migrations before cache warmers).
  • Make every scheduled command return meaningful exit codes (Command::SUCCESS / Command::FAILURE) and run non-interactively; the admin panel only knows what the exit code tells it.
  • 3.0.0 standardized command output timestamps on \DateTimeInterface::ATOM. If you scrape the per-task log files into a dashboard, that format is now stable and parseable — but any regex written against the old default DateTime::__toString() output will break.

Mixed-shop integration. If Symfony schedules and Laravel does the work, don't shell out to php artisan from a Symfony task — you inherit two environments' paths, PHP versions, and .env files. Expose a signed, idempotent HTTP endpoint in Laravel and have the Symfony task hit it, or consolidate to one scheduler per application.

Gotchas and Tips

  • Don't try to install it in Laravel. No bundle kernel, no Doctrine mapping registration, different autoloading conventions. Time-box this to zero.
  • Pin ^3.0.1, not ^3.0. v3.0.0 was pulled from Packagist almost immediately — its composer.json wrongly declared PHP >= 7.1 while the code needs 7.2, so a composer require on PHP 7.1 installed a broken combination. 3.0.1 corrects the constraint to PHP >= 7.2. If you have a lockfile or internal mirror that resolved 3.0.0, update deliberately.
  • It's still stale, and Symfony 5 is the ceiling. Latest release is 3.0.1, still 2020 (Symfony 4/5, PHP 7-era). It does not support Symfony 6 or 7 — expect composer constraint conflicts on a modern app, deprecated Doctrine APIs beyond what 3.0.0 already cleaned up (merge() removal), and missing return types. Pin versions or vendor a fork; don't assume master is maintained.
  • No crontab = silent nothing. The most common "bug report" is a missing or misdirected cron entry. Verify with crontab -l under the web/PHP user's account, and check the bundle's log path is writable by that user.
  • Sticky locks. A run killed mid-flight (OOM, deploy, timeout) leaves the locked flag set and the task never runs again. Check the lock state in the admin UI / locked column; ensure your own port uses withoutOverlapping() with a TTL longer than the longest job, backed by a lock-capable cache (redis/database, not array).
  • Overlap by design. The runner executes all due tasks serially in one process. Anything longer than a minute collides with the next cron invocation, and one slow task delays everything behind it. Keep scheduled commands thin; push real work to a queue.
  • Timezone and DST. Cron expressions are evaluated in PHP's default timezone. Set date.timezone explicitly in php.ini and remember a 30 2 * * * task runs twice or not at all on DST boundaries.
  • The admin panel is remote command execution. Unauthenticated, anyone who reaches it can schedule arbitrary console commands. Put it behind a firewall/role, IP-allowlist it, or don't import the routes in production at all.
  • Argument quoting. The arguments field is a shell-ish string; nested quotes and = in values are a recurring source of "works manually, fails scheduled". In a Laravel port, store arguments as JSON and pass an array.
  • Logs grow forever. Per-task log files are never rotated by the bundle. Add logrotate or truncate on each run (> not >>) for chatty commands.
  • Don't monitor the runner's exit code. scheduler:run succeeds if it ran; per-task health lives in last_return_code. Alert on that column, not on cron mail.
  • Validate expressions at write time. One invalid row can throw every minute and take down the whole dispatch loop, not just that task — wrap the due-check per task in try/catch in your port.

Bottom line: 3.0.1 is a maintenance release — Symfony 5 support, the dragonmantank/cron-expression swap, and a corrected PHP floor. Nothing in it changes the verdict for a Laravel developer: still unusable directly in a Laravel app, still a single-maintainer bundle frozen in 2020, still capped at Symfony 5. Its real day-to-day value is the architecture: DB-backed task table, one-minute dispatcher, per-task locks and result columns. That's roughly 200 lines to reproduce on top of Illuminate\Console\Scheduling\Schedule — and since the bundle now validates expressions with the same library Laravel uses, the port is even more of a straight copy than before.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
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