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

Cron Bundle Laravel Package

mtamazlicaru/cron-bundle

A Symfony bundle for defining and running scheduled cron jobs in PHP applications. Configure recurring console commands, let the bundle handle due-job execution, and keep background tasks organized alongside your application code.

View on GitHub
Deep Wiki
Context7

Getting Started

Step 0 — verify you can actually install it. mtamazlicaru/cron-bundle (SbkCronBundle) is a Symfony bundle. It registers through a Symfony Bundle class, a DI extension, and symfony/console commands. It ships no ServiceProvider, no facade, no extra.laravel auto-discovery block, and nothing that targets Illuminate\Console\Scheduling\Schedule. In a Laravel app, composer require mtamazlicaru/cron-bundle will either fail on symfony/* constraint conflicts or force you to downgrade shared framework components. Run it in a throwaway directory first:

composer require mtamazlicaru/cron-bundle --dry-run

If it only resolves with --ignore-platform-reqs or version pins on symfony/console/symfony/framework-bundle, stop. That is a disqualifier, not a workaround. Note also that stars, dependents, and score are all zero — there is no upstream to inherit bug fixes from. Anything you adopt here, you own.

Where to look first (30 minutes, as a reference read). Read the bundle's composer.json for PHP/Symfony constraints and last-commit date, then the DependencyInjection/ extension to see the actual config schema, then the console command that evaluates what is due. The interesting part is small: parse a config file → map entries to commands → evaluate cron expressions → dispatch and log. That's the pattern worth extracting, not the code.

The Laravel-native first use case. Everything the bundle's description promises — "configure cron jobs in configuration files," version-controlled alongside your code — Laravel already does, with one crontab entry. Start with the smallest real job you have (nightly cache/table cleanup):

  1. Add the single system cron entry: * * * * * cd /path/to/app && php artisan schedule:run >> /dev/null 2>&1
  2. Declare the job in routes/console.php (Laravel 11+) or a ScheduleServiceProvider:
    Schedule::command('cleanup:stale-rows')->dailyAt('02:00')->withoutOverlapping()->onOneServer();
    
  3. Confirm with php artisan schedule:list, then run php artisan schedule:run -v once and read the output.

If you want the declarative-config flavor, that's the real first deliverable (see below).

Implementation Patterns

Config-as-code schedule registry (the pattern, ported). This is the thing the bundle is actually selling, and it ports cleanly in ~1–2 days:

// config/schedules.php
return [
    'daily-digest' => [
        'command'   => 'mail:daily-digest',
        'cron'      => '0 7 * * *',
        'timezone'  => 'UTC',
        'overlap'   => false,
        'one_server'=> true,
        'timeout'   => 900,
    ],
];
// app/Providers/ScheduleServiceProvider.php
public function boot(): void
{
    foreach (config('schedules', []) as $name => $job) {
        $event = Schedule::command($job['command'])
            ->cron($job['cron'])
            ->timezone($job['timezone'] ?? config('app.timezone'))
            ->name($name);

        if ($job['overlap'] ?? false === false) $event->withoutOverlapping();
        if ($job['one_server'] ?? false)        $event->onOneServer();
        if (isset($job['timeout']))             $event->runInBackground();
    }
}

Benefits you get for free that the bundle doesn't: onOneServer() (cache-backed lock, so autoscaled workers don't double-fire), withoutOverlapping(), runInBackground(), and before()/after()/onSuccess()/onFailure() hooks.

Jobs as classes, not closures. Keep schedule entries pointing at artisan commands or invokable job classes. Closures inside config files can't survive php artisan config:cache, and they're untestable in isolation. A command per job also gives you a manual trigger (php artisan mail:daily-digest) for debugging — the same convenience the bundle's "run/list/inspect" console commands provide.

Per-job logging and failure tracking. The bundle advertises per-job logging; in Laravel do it explicitly rather than relying on the global log:

Schedule::command('sync:invoices')
    ->hourly()
    ->withoutOverlapping()
    ->appendOutputTo(storage_path('logs/invoices.log'))
    ->onFailure(fn () => Log::channel('alerts')->error('sync:invoices failed'));

Send onFailure to a channel that actually pages someone. A scheduled job that fails silently is worse than no scheduled job, because you believe it ran.

Environment and deployment integration. Gate non-production schedules with ->environments(['production']) or by keying your config file per environment (config/schedules/production.php). For containerized deploy, choose one ownership model and document it: either a single long-lived schedule:work worker container, or an external trigger (Kubernetes CronJob, cloud scheduler) that runs php artisan schedule:run on one replica. Don't do both — two dispatchers means duplicate executions.

Testing. Use Carbon::setTestNow() plus $this->artisan('schedule:run') and assert the downstream effect. Add php artisan schedule:list to CI as a smoke test so a broken config file fails the build instead of dying quietly in production.

Gotchas and Tips

  • schedule:run fires only once per minute. It evaluates due tasks and exits. Long or blocking jobs hold the dispatcher and delay everything after them — use runInBackground() (requires schedule:finish handling of exit codes) for anything slow, and treat jobs shorter than the interval as the default assumption.
  • onOneServer() needs a shared cache store. It's backed by an atomic cache lock. With CACHE_STORE=file or array on multiple hosts, every node has its own lock and you get N executions — the exact failure the feature claims to prevent. Verify your production cache driver is Redis/database/memcached.
  • withoutOverlapping() locks for 24 hours by default. If a job crashes hard, the lock can outlive the job. Set the expiry explicitly: ->withoutOverlapping(10). Also note withoutOverlapping and onOneServer solve different problems (overlap vs. multi-node) — you often want both.
  • Timezone and DST. ->timezone() on the event, APP_TIMEZONE in config, and the server's cron daemon timezone are three separate things. A mismatch shifts jobs by an hour twice a year. Pin everything to UTC and convert only for display.
  • Missing cron entry is a silent total failure. Nothing errors; jobs just never run. Add a health check (a heartbeat row written by a everyMinute job, monitored externally) or a staleness alert on your job output table. This is the single most common production incident with any application-layer scheduler.
  • config:cache is load-bearing. Config-driven schedules read cleanly at boot, but anything non-serializable (closures, service instances) in config/schedules.php will break config:cache — or worse, break only in production where the cache is warm.
  • Dependency-erosion risk if you do adopt. A pinned, unmaintained Symfony bundle becomes an invisible blocker six months later when you try to upgrade the framework. If you fork it, add the fork to your dependency-review list with an owner and a review date; unowned forks are permanent debt.
  • Debugging sequence. php artisan schedule:list (is it registered?) → php artisan schedule:test (does it run on demand?) → php artisan schedule:run -v (does the dispatcher see it as due?) → check the lock in your cache store (is a stale lock suppressing it?). Work in that order; it localizes the fault fast.
  • Extension points. Schedule::macro() for repeated configurations (e.g. ->businessHours()), and the ScheduledTaskStarting/ScheduledTaskFinished/ScheduledTaskFailed events for cross-cutting concerns like metrics or audit rows — that's where you'd hook a lightweight "run history" if you need one, rather than adding a dependency for it.
  • If you are evaluating the bundle for a real Symfony app, do not rely on the README alone: confirm what it actually does before you commit. Specifically, determine whether it writes to the system crontab (which makes deploys non-idempotent and rollbacks manual) or runs its own dispatcher loop (which needs leader election to avoid duplication across nodes). That answer determines your entire operational model, and it isn't documented.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager