Goal
Rewrite contributte/scheduler to be a real Nette integration for symfony/scheduler, following the same overall concept that contributte/messenger uses for symfony/messenger.
Why
The current package is still a custom cron/callback runner with its own runtime model (IJob, IScheduler, scheduler:run, lock files, callback jobs).
That works, but it is not aligned with Symfony Scheduler's architecture and misses the richer ecosystem around schedules, recurring messages, Messenger workers, runtime events, stateful execution, and locks.
contributte/messenger is already a strong example of how Contributte integrates a Symfony component into Nette:
- one DI extension as the public entry point
- internal compiler passes by concern
- native Symfony runtime/services exposed in Nette
- good docs and focused DI/E2E tests
contributte/scheduler should move in the same direction.
Current situation
Today contributte/scheduler is centered around:
IJob and IScheduler
Scheduler / LockingScheduler
- config-driven
scheduler.jobs
- callback jobs and custom job classes
- custom commands:
scheduler:run
scheduler:list
scheduler:force-run
scheduler:help
That model is fundamentally different from Symfony Scheduler, which is based on:
ScheduleProviderInterface
Schedule
RecurringMessage
- Messenger-backed scheduler transports
messenger:consume scheduler_<name>
debug:scheduler
- optional runtime events, stateful cache, and lock integration
Proposed direction
Rebuild the package as a thin Nette bridge over Symfony Scheduler, similar in spirit to contributte/messenger.
Core design
- Keep a single
SchedulerExtension as public DI entry point.
- Split integration into small passes, e.g.:
ProviderPass
TaskPass
TransportPass
EventPass
ConsolePass
DebugPass
- Make Symfony Scheduler concepts the primary API:
ScheduleProviderInterface
Schedule
RecurringMessage
#[AsSchedule]
- Support Symfony task attributes as an upper layer:
#[AsCronTask]
#[AsPeriodicTask]
Runtime integration
- Register a schedule-provider locator.
- Register
SchedulerTransportFactory.
- Create one Messenger transport per schedule:
scheduler_<name> -> schedule://<name>
- Reuse Messenger worker flow:
messenger:consume scheduler_<name>
- Register scheduler event bridge:
DispatchSchedulerEventListener
- Register
ServiceCallMessageHandler for service/method-based task definitions.
- Expose native scheduler debug tooling:
Dependencies
Likely runtime direction:
php >=8.2
nette/di
symfony/scheduler
symfony/messenger
symfony/console
Optional integrations:
dragonmantank/cron-expression
symfony/cache
symfony/lock
symfony/event-dispatcher
Migration strategy
This is not a small refactor; it is a model change.
Recommended compatibility layer
Keep the scheduler extension name, but introduce a transition layer that compiles legacy config into Symfony schedules where possible.
Preserve temporarily:
scheduler.jobs
- callback-based jobs
- service-based jobs
scheduler.path semantics via a lock adapter
Deprecate:
IJob
IScheduler
Scheduler
LockingScheduler
- custom imperative runtime access
- legacy custom commands that do not map cleanly to Symfony Scheduler
Command migration
Keep/review:
debug:scheduler as first-class
Deprecate or replace:
scheduler:run -> use messenger:consume scheduler_<name>
scheduler:list -> use debug:scheduler
scheduler:force-run -> likely no direct equivalent; either drop or reintroduce only as an explicit compatibility tool
scheduler:help -> not needed once docs and native tooling are in place
Iteration plan
Iteration 1 - Symfony-native MVP
Implement the clean core first:
- explicit
ScheduleProviderInterface services
- schedule provider discovery
- scheduler transports
messenger:consume scheduler_<name>
debug:scheduler
- minimal docs and tests
This gives the cleanest architecture and proves the bridge works.
Iteration 2 - Compatibility layer
Add migration support for current users:
- compile legacy
scheduler.jobs into recurring messages
- support callback/service-based task definitions
- map
scheduler.path to Symfony lock support
- emit deprecations for old API/commands
This reduces upgrade pain.
Iteration 3 - High-level ergonomics
Add developer-friendly features:
#[AsSchedule]
#[AsCronTask]
#[AsPeriodicTask]
- config-defined tasks/messages
- optional stateful cache support
- optional event-dispatcher integration
- improved examples and cookbook docs
Testing plan
Add real coverage, not only config parsing:
- DI compile tests
- provider discovery tests
- legacy config migration tests
- attribute-based task tests
- lock integration tests
- stateful schedule tests
- console command tests
- E2E worker/schedule scenarios
Docs plan
Update docs to include:
- installation
- Messenger dependency/integration story
- minimal setup
- schedule providers
- task attributes
- locks and stateful mode
- consuming schedules
- debugging
- migration guide from legacy
scheduler.jobs
Open questions
- Should
contributte/messenger become a hard dependency, or should contributte/scheduler wire symfony/messenger directly?
- How much of the old
scheduler.jobs config should be preserved during transition?
- Should
scheduler:force-run exist in the new world at all?
- Is this rewrite released as the next major version only?
Acceptance criteria
- Package is centered on
symfony/scheduler, not a custom in-process scheduler loop.
- Nette users can define schedules/providers and consume them via Messenger workers.
debug:scheduler works in Nette apps.
- There is a documented upgrade path from current
contributte/scheduler.
- Tests cover DI, runtime wiring, and realistic schedule execution scenarios.
Goal
Rewrite
contributte/schedulerto be a real Nette integration forsymfony/scheduler, following the same overall concept thatcontributte/messengeruses forsymfony/messenger.Why
The current package is still a custom cron/callback runner with its own runtime model (
IJob,IScheduler,scheduler:run, lock files, callback jobs).That works, but it is not aligned with Symfony Scheduler's architecture and misses the richer ecosystem around schedules, recurring messages, Messenger workers, runtime events, stateful execution, and locks.
contributte/messengeris already a strong example of how Contributte integrates a Symfony component into Nette:contributte/schedulershould move in the same direction.Current situation
Today
contributte/scheduleris centered around:IJobandISchedulerScheduler/LockingSchedulerscheduler.jobsscheduler:runscheduler:listscheduler:force-runscheduler:helpThat model is fundamentally different from Symfony Scheduler, which is based on:
ScheduleProviderInterfaceScheduleRecurringMessagemessenger:consume scheduler_<name>debug:schedulerProposed direction
Rebuild the package as a thin Nette bridge over Symfony Scheduler, similar in spirit to
contributte/messenger.Core design
SchedulerExtensionas public DI entry point.ProviderPassTaskPassTransportPassEventPassConsolePassDebugPassScheduleProviderInterfaceScheduleRecurringMessage#[AsSchedule]#[AsCronTask]#[AsPeriodicTask]Runtime integration
SchedulerTransportFactory.scheduler_<name>->schedule://<name>messenger:consume scheduler_<name>DispatchSchedulerEventListenerServiceCallMessageHandlerfor service/method-based task definitions.debug:schedulerDependencies
Likely runtime direction:
php >=8.2nette/disymfony/schedulersymfony/messengersymfony/consoleOptional integrations:
dragonmantank/cron-expressionsymfony/cachesymfony/locksymfony/event-dispatcherMigration strategy
This is not a small refactor; it is a model change.
Recommended compatibility layer
Keep the
schedulerextension name, but introduce a transition layer that compiles legacy config into Symfony schedules where possible.Preserve temporarily:
scheduler.jobsscheduler.pathsemantics via a lock adapterDeprecate:
IJobISchedulerSchedulerLockingSchedulerCommand migration
Keep/review:
debug:scheduleras first-classDeprecate or replace:
scheduler:run-> usemessenger:consume scheduler_<name>scheduler:list-> usedebug:schedulerscheduler:force-run-> likely no direct equivalent; either drop or reintroduce only as an explicit compatibility toolscheduler:help-> not needed once docs and native tooling are in placeIteration plan
Iteration 1 - Symfony-native MVP
Implement the clean core first:
ScheduleProviderInterfaceservicesmessenger:consume scheduler_<name>debug:schedulerThis gives the cleanest architecture and proves the bridge works.
Iteration 2 - Compatibility layer
Add migration support for current users:
scheduler.jobsinto recurring messagesscheduler.pathto Symfony lock supportThis reduces upgrade pain.
Iteration 3 - High-level ergonomics
Add developer-friendly features:
#[AsSchedule]#[AsCronTask]#[AsPeriodicTask]Testing plan
Add real coverage, not only config parsing:
Docs plan
Update docs to include:
scheduler.jobsOpen questions
contributte/messengerbecome a hard dependency, or shouldcontributte/schedulerwiresymfony/messengerdirectly?scheduler.jobsconfig should be preserved during transition?scheduler:force-runexist in the new world at all?Acceptance criteria
symfony/scheduler, not a custom in-process scheduler loop.debug:schedulerworks in Nette apps.contributte/scheduler.