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

Scheduler Bundle Laravel Package

guikingone/scheduler-bundle

A Symfony bundle for scheduling recurring tasks and console commands with cron expressions. Configure tasks via attributes or YAML, run them with a worker, and use transports to control synchronous or background execution.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the honest framing: guikingone/scheduler-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no config/schedule.php, and no Facade. You cannot composer require it into a Laravel app and start scheduling. A Laravel developer "leverages" this package in one of three ways:

  1. As a pattern reference for what a DB-backed, runtime-mutable scheduler looks like (Laravel's Schedule is code-first and immutable at runtime — this bundle is not).
  2. In a Symfony sidecar/worker alongside a Laravel app, communicating over HTTP, Redis, or a shared queue.
  3. In a polyglot team where you own a Symfony service and want a drop-in replacement for crontab.

If you're evaluating it for a pure Laravel codebase, stop at step 1 — the rest of this assessment assumes you'll actually run it inside Symfony (6.4/7.x).

Minimal steps (Symfony)

composer require guikingone/scheduler-bundle
  • Symfony Flex auto-registers the bundle; otherwise add it to config/bundles.php.
  • Pick and configure a transport (config/packages/scheduler.yaml). doctrine is the sane default for anything multi-host — it creates a task table you'll need to migrate.
  • Add the single crontab entry the bundle expects:
* * * * * cd /path/to/app && bin/console scheduler:run -q

The bundle does not run a daemon; scheduler:run is designed to be invoked every minute, and it decides which tasks are due based on their cron expression.

Where to look first

File / symbol Why
SchedulerInterface The public API: schedule(), unschedule(), getTasks(), run()
TaskInterface + Task/*Task.php The task taxonomy — read these before designing anything
TransportInterface How tasks are persisted; the seam between code and runtime state
SchedulePolicy/* Overlap/priority arbitration — the closest analogue to withoutOverlapping()
Middleware/* Cross-cutting hooks around execution
Event/* (Executing/Executed/Failed) Observability; where you'd ship metrics to your Laravel side

First use case

Scheduling a console command from application code, rather than editing a crontab:

use SchedulerBundle\SchedulerInterface;
use SchedulerBundle\Task\CommandTask;

final class ReportScheduler
{
    public function __construct(private SchedulerInterface $scheduler) {}

    public function register(): void
    {
        $this->scheduler->schedule(
            new CommandTask('nightly-report', 'app:report:generate', '0 2 * * *')
        );
    }
}

The task is written to the transport, picked up on the next scheduler:run tick, and survives deploys — which is both the feature and the governance risk.

Implementation Patterns

1. Code-declared vs. runtime-declared tasks

The bundle supports both, and mixing them is the intended workflow:

  • Code-declared (in a service, a kernel.boot listener, or a fixture command): versioned, reviewable, reproducible. Use this for anything whose existence is a business invariant.
  • Runtime-declared (an admin controller calling $scheduler->schedule()): lets ops add/remove recurring work without a deploy. Use sparingly and behind an audit trail.

A workable discipline: code-declared tasks are idempotent-on-boot (check getTasks() before scheduling), runtime tasks are namespaced (ops:) so you can bulk-remove them.

2. Choose the task type that matches the boundary

Task Use when
CommandTask The work is already a Symfony console command you can test standalone
CallbackTask Small glue in an existing service; not serializable across hosts
ShellTask Wrapping an external binary — treat as an injection surface, never interpolate user input
HttpTask / MessageTask This is your Laravel bridge: hit a Laravel endpoint or dispatch to a shared queue rather than reimplementing logic

For a Laravel shop, MessageTask (Symfony Messenger) or HttpTask pointing at a Laravel route are the two integrations worth prototyping. Both keep the scheduler dumb and let Laravel own the actual job, retries, and Horizon visibility.

3. Overlap and concurrency policies

Laravel gives you withoutOverlapping() and onOneServer() for free. Here you opt in via SchedulePolicy (ExecutionTimePolicy, IdlePolicy, numeric ordering) and middleware. Design assumption to internalize: a task taking longer than its interval will be re-evaluated on the next tick, so either bound the runtime or attach a lock middleware. If you're porting a Laravel schedule over, audit every ->withoutOverlapping() you had and make it explicit.

4. Middleware for the concerns Laravel hides

Middleware is the extension point for: distributed locking, structured logging, backoff/retry, tenant scoping, and "skip if a dependency is down." This is where you'd add the equivalent of Laravel's ->before()/->after()/->onFailure() callbacks. Keep them small and stateless — they wrap every task.

5. Observability: events out, dashboards elsewhere

TaskExecutingEvent / TaskExecutedEvent / TaskFailedEvent are the honest seam. A realistic setup: a listener that emits to your existing telemetry (OTLP, StatsD, or an HTTP call into the Laravel app's monitoring endpoint). Don't build a second dashboard — forward the signal.

6. Mapping to Laravel analogues

Laravel SchedulerBundle equivalent
$schedule->command('x')->daily() new CommandTask('x', 'app:x', '0 0 * * *')
->withoutOverlapping() SchedulePolicy / lock middleware
->onOneServer() Doctrine/Redis transport + policy (no free lunch)
Schedule::call(fn() => …) CallbackTask (single-host only)
->runInBackground() Not native; use MessageTask or ShellTask &
schedule:list scheduler:list
->timezone('UTC') Config-level TZ; per-task TZ is not a first-class concept
Schedule::job(new X) MessageTask + Messenger

7. Hybrid architecture tip

If you must run both stacks, make the transport the contract, not the code. The cleanest version: the Symfony scheduler only ever emits messages/HTTP calls; the Laravel app owns idempotency keys. Avoid the temptation to have Laravel write directly into the Doctrine task table — the schema and internal state machine are the bundle's private business.

Gotchas and Tips

Transport choice is the whole ballgame.

  • filesystem transport is fine for a single VM and catastrophic on multiple hosts (split brain, duplicate runs).
  • doctrine transport means schema migrations you don't control the cadence of — pin the bundle version and review migration diffs on upgrade.
  • redis is the right call for ephemeral/one-shot work, worse for anything you need to audit.

No overlap protection by default. This is the #1 difference from Laravel. A 90-second task on a * * * * * expression will stack. Always pair long tasks with a lock middleware or a SchedulePolicy.

The cron entry is load-bearing. If scheduler:run isn't firing every minute, nothing fires, and there's no "missed tick" replay. Add an external heartbeat check on the last successful run timestamp — the bundle won't tell you it stopped.

Timezones. Expressions are evaluated in the PHP/server timezone unless configured. If your Laravel app uses APP_TIMEZONE=UTC and the Symfony box is local time, your "nightly" job drifts by hours and nobody notices for a week.

Runtime tasks are invisible to code review. Anything scheduled at runtime lives only in the transport. Provide a scheduler:list-based export (or a CI job that diffs it) so a task added by hand in production doesn't become folklore.

Deployment ordering. Task class names are serialized into the transport. Rename or namespace-move a CommandTask subclass and existing rows break. Treat task names as a public contract; keep them as plain strings you control.

Debugging workflow. scheduler:list → confirm the task exists and its expression is what you think → check the task's state in the transport (failed tasks can stay failed) → tail the app log filtered on the task name → only then reach for -vvv on scheduler:run. If a task never appears in scheduler:list, the problem is registration, not execution.

Version churn. The package is pre-1.0 in spirit despite recent releases; task/transport APIs have moved between minors. Pin to an exact minor, and budget an hour to read the UPGRADE notes each bump. With 116 stars and ~0 dependents, you are effectively the integration maintainer — there is no Stack Overflow corpus to lean on. The source is readable; read it.

Extension points worth knowing exist before you need them: custom TaskInterface implementations (wrap your own job abstraction), custom TransportInterface (e.g., an existing queue you already operate), middleware (locking, retry, tenancy), SchedulePolicy (priority/fairness), and event listeners (telemetry). Almost any "the bundle doesn't do X" complaint resolves at one of these seams.

When not to use it. If your needs are "these five commands run on a fixed cadence" and you already run Laravel, this bundle adds a transport, a schema, a crontab, and a second framework for no additional capability. The value only appears when tasks must be created and inspected at runtime by non-developers, or when you're already in Symfony. Be honest about which situation you're in before adopting it.

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.
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
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin