meloflavio/cron-bundle
Cron Bundle for Laravel makes it easy to define, schedule and manage recurring PHP tasks. Register cron jobs, work with cron expressions, trigger runs through Artisan or the scheduler, and track executions with lightweight logging—no extra infrastructure needed.
Reality check first: meloflavio/cron-bundle is a Symfony bundle, not a Laravel package. It provides no service provider, no facade, and no Kernel::schedule() integration. If you are working in a Laravel codebase, you cannot composer require this and expect it to schedule your php artisan commands — Laravel already has app/Console/Kernel.php (or routes/console.php / Schedule:: in Laravel 11) for that. This bundle is only relevant if you are maintaining a Symfony application (or a Laravel app with a legacy Symfony component you're trying to schedule Symfony Command classes from).
Minimal setup (Symfony context):
composer require meloflavio/cron-bundle
Register the bundle in config/bundles.php:
return [
// ...
Meloflavio\CronBundle\MeloflavioCronBundle::class => ['all' => true],
];
Then add one system crontab entry — the whole point of bundles like this is to avoid one crontab line per job:
* * * * * cd /path/to/app && php bin/console cron:run >> var/log/cron.log 2>&1
Where to look first:
README.md — the only real documentation; the repo has essentially no community around it (0 stars, 0 dependents), so treat the README as the spec.src/ — grep for AsCommand / Command subclasses. The dispatcher command (something like cron:run or cron:execute) is the entry point you hook cron into.DependencyInjection/ — look for Configuration.php to see the exact YAML tree keys (cron:, jobs:, schedule: — names are version-specific, verify against source).Resources/config/ — service definitions tell you which classes are public and therefore which extension points exist.First use case: schedule an existing Symfony console command (e.g. app:cleanup-sessions) to run every 15 minutes, with output captured to a log, without touching the server's crontab again.
1. Single dispatcher + declarative schedule. The canonical pattern is: cron invokes the bundle's runner every minute; the runner inspects configured jobs, evaluates their cron expressions, and executes the ones that are due. Keep the system crontab to exactly one line — everything else lives in version-controlled config.
# config/packages/cron.yaml (verify key names against Configuration.php)
cron:
jobs:
cleanup_sessions:
command: 'app:cleanup-sessions'
expression: '*/15 * * * *'
nightly_report:
command: 'app:send-nightly-report --force'
expression: '0 2 * * *'
2. Wrap, don't couple. Expose each scheduled responsibility as its own Command class with a __invoke-style service behind it. The cron config then only names the command string, and you can run the same logic manually (bin/console app:cleanup-sessions) for debugging — this is the single biggest day-to-day ergonomic win.
#[AsCommand(name: 'app:cleanup-sessions')]
final class CleanupSessionsCommand extends Command
{
public function __construct(private SessionCleaner $cleaner) { parent::__construct(); }
protected function execute(InputInterface $input, OutputInterface $output): int
{
$count = $this->cleaner->purgeExpired();
$output->writeln(sprintf('Purged %d sessions', $count));
return Command::SUCCESS;
}
}
3. Environment-scoped schedules. Register the bundle only where the scheduler runs (['dev' => false] or gate the crontab by host). Running the dispatcher on every web node will duplicate work; pick one scheduler host or add a lock (see Gotchas).
4. Deployment workflow. Because the schedule is config-as-code, the deploy pipeline is: deploy → bin/console cache:clear → confirm bin/console cron:run --dry-run (if the bundle supports it; otherwise run it manually once with verbose output) → trust the existing crontab line. No crontab edits per feature branch.
5. Integration with your logging. Pipe stdout/stderr into your normal log sink (var/log/cron.log + logrotate, or logger/systemd-cat). For anything multi-step, have the command itself log via Monolog so failures surface in the same dashboards as web errors.
6. Testing. Test the commands, not the scheduler. Unit-test CleanupSessions::purgeExpired() directly; use CommandTester for the command; treat the cron expression config as a static fixture you assert on if the schedule is business-critical.
composer.json for the symfony/* and php constraints — it will likely conflict with a modern Symfony app, and composer require will fail or force a downgrade. Fork and pin if you must use it.flock, a DB advisory lock, or Symfony Lock component) rather than relying on the bundle.date_default_timezone_set). "0 2 * * *" is not necessarily 2am local — pin date.timezone in php.ini or the app config and document it.bin/console cron:run -vvv) and, if available, a dry-run/list flag. Confirm the job is actually registered with bin/console debug:container --tag=... or by inspecting the compiled container — a typo in a YAML key silently produces "no jobs configured," which looks identical to "schedule working."cache:clear (or cache:warmup) before the dispatcher sees them; a stale prod cache is the #1 cause of "I changed the schedule and nothing happened."src/: a job/event dispatcher (Symfony EventDispatcher subscribers on job start/finish), a JobInterface you can implement for non-command work, and the DI Configuration node for adding custom fields. If none of those exist, your only extension point is the commands themselves — which is fine, and arguably the right place to put logic anyway.Schedule::command('app:cleanup-sessions')->everyFifteenMinutes()->withoutOverlapping()->runInBackground();. It covers every pattern above, is first-party, and is maintained. The only reason to consider this bundle is a mixed/legacy Symfony codebase.How can I help you explore Laravel packages today?