Recurring Background Tasks with #[Scheduled] in Winter Boot
The #[Scheduled] attribute runs a bean method on a recurring timer, with no external cron job. Scheduled tasks run in a dedicated worker process, separate from the HTTP workers, so a slow task never blocks request handling. Intervals are fixed (in seconds); cron expressions are not supported.
Prerequisites
Section titled “Prerequisites”Scheduling needs the swoole extension and the WinterWebSwooleApplication runner; it is not available under PHP-FPM. See Installation.
Enable Scheduling on Your Application Class
Section titled “Enable Scheduling on Your Application Class”Add #[EnableScheduling] to your #[WinterBootApplication] class. Without it, #[Scheduled] attributes are ignored. If the swoole extension is missing, startup fails with an AnnotationException.
use dev\winterframework\stereotype\WinterBootApplication;use dev\winterframework\stereotype\task\EnableScheduling;use dev\winterframework\core\app\WinterWebSwooleApplication;
#[WinterBootApplication( configDirectory: [__DIR__ . '/config'], scanNamespaces: [['com\\example', __DIR__ . '/src']])]#[EnableScheduling]class MyApplication{ public static function main(): void { (new WinterWebSwooleApplication())->run(self::class); }}The #[Scheduled] Attribute
Section titled “The #[Scheduled] Attribute”Place #[Scheduled] on a public, non-final, non-abstract void method with no required arguments, on a #[Service] or #[Component] bean. Set exactly one interval — fixedDelay, fixedRate, or one of their String variants; setting none or more than one throws an AnnotationException at startup.
Parameters
Section titled “Parameters”-
fixedDelayintSeconds to wait after the previous execution completes before starting the next. Use this when you want to avoid overlapping runs and the task duration may vary. -
fixedDelayStringstringProperty placeholder (e.g.${my.delay}) that resolves to thefixedDelayvalue from your application configuration. -
fixedRateintSeconds between the start of successive executions, regardless of how long each run takes. Use this when you need a consistent heartbeat. -
fixedRateStringstringProperty placeholder resolving to thefixedRatevalue from your application configuration. -
initialDelayintSeconds to wait after application startup before the first execution fires. -
initialDelayStringstringProperty placeholder resolving to theinitialDelayvalue from your application configuration.
Fixed Delay vs. Fixed Rate
Section titled “Fixed Delay vs. Fixed Rate”The two core scheduling modes behave differently when a task takes longer than its interval. Choose the one that matches your task’s requirements.
fixedDelay introduces a gap between the end of one execution and the start of the next. Use this when you want to avoid overlapping runs and the task duration may vary.
use dev\winterframework\stereotype\Component;use dev\winterframework\task\scheduling\stereotype\Scheduled;
#[Component]class CacheWarmer{ #[Scheduled(fixedDelay: 60, initialDelay: 10)] public function refreshCache(): void { // Runs 10 seconds after startup, then again 60 seconds after // each completion. echo 'Cache refreshed at ' . date('H:i:s') . PHP_EOL; }}Timeline: [startup] → 10s delay → run → 60s delay → run → 60s delay → run → …
fixedRate fires the method every N seconds counted from the start of the previous invocation. Use this when you need a consistent heartbeat regardless of individual run time.
use dev\winterframework\stereotype\Component;use dev\winterframework\task\scheduling\stereotype\Scheduled;
#[Component]class HealthReporter{ #[Scheduled(fixedRate: 30)] public function reportHealthMetrics(): void { // Fires every 30 seconds from the previous start time. echo 'Health check at ' . date('H:i:s') . ' — PID: ' . getmypid() . PHP_EOL; }}Timeline: [startup] → run starts → 30s → run starts → 30s → run starts → …
Configure Timing with Property Placeholders
Section titled “Configure Timing with Property Placeholders”Use the String variants of each parameter to read timing values from application.yml, so you can change schedules per environment without changing code.
app: scheduler: reportDelay: 45#[Component]class SomeScheduler{ #[Scheduled(fixedDelayString: '${app.scheduler.reportDelay}', initialDelay: 5)] public function someScheduledMethodName(): void { echo 'Report generated: ' . uniqid() . PHP_EOL; }}Configuration
Section titled “Configuration”Configure the scheduling worker pool in application.yml under winter.task.scheduling:
winter: task: scheduling: poolSize: 1 # Number of scheduling worker processes (default 1) queueCapacity: 50 # Maximum number of scheduled tasks (default 50)poolSizeint(default1) Number of worker processes that run scheduled tasks.queueCapacityint(default50) Maximum number of#[Scheduled]methods the scheduler can track.
Complete Example
Section titled “Complete Example”The following files show a full scheduling setup: the application entry point and a scheduler component with two differently-timed tasks.
use dev\winterframework\stereotype\WinterBootApplication;use dev\winterframework\stereotype\task\EnableScheduling;use dev\winterframework\core\app\WinterWebSwooleApplication;
#[WinterBootApplication( configDirectory: [__DIR__ . '/config'], scanNamespaces: [['com\\example', __DIR__ . '/src']])]#[EnableScheduling]class MyApplication{ public static function main(): void { (new WinterWebSwooleApplication())->run(self::class); }}use dev\winterframework\stereotype\Component;use dev\winterframework\stereotype\Autowired;use dev\winterframework\task\scheduling\stereotype\Scheduled;
#[Component]class SomeScheduler{ #[Autowired] private ReportRepository $reportRepository;
/** * Generates a unique ID 10 seconds after startup, * then every 20 seconds after each completion. */ #[Scheduled(fixedDelay: 20, initialDelay: 10)] public function someScheduledMethodName(): void { echo 'I generate a unique ID every 20 seconds: ' . uniqid(); }
/** * Persists a daily summary on a 24-hour fixed rate. */ #[Scheduled(fixedRate: 86400, initialDelay: 60)] public function persistDailySummary(): void { $this->reportRepository->saveDailySummary(date('Y-m-d')); }}