lsr / scheduler
Laser framework scheduler integration.
Requires
- php: >=8.4
- dragonmantank/cron-expression: ^3.6
- nette/di: ^3.2
- psr/container: ^2.0
- symfony/console: ^8|^7
- symfony/scheduler: ^8|^7.4
- symfony/service-contracts: ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.95
- phpstan/extension-installer: ^1.2
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^12|^13
- roave/security-advisories: dev-latest
Suggests
- lsr/console: Loads scheduler console commands into the framework console.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-08 12:21:51 UTC
README
lsr/scheduler provides Nette DI integration for the standalone Symfony Scheduler.
Scheduled jobs are application services, so their definitions and timing ship in the same repository and deployment as the application.
Requirements
- PHP
>=8.4. - Nette DI
^3.2, PSR Container^2.0and cron-expression^3.6. - Symfony Console
^8|^7, Scheduler^8|^7.4and Service Contracts^3.0. - No PHP extensions are explicitly required by this package's manifest; dependencies may add platform requirements.
- An application-owned DI bootstrap and a supervised long-running scheduler process.
lsr/consoleis suggested for loading the scheduler commands. Alternatively, register them in the application's Symfony Console application yourself.- Persistent schedule state and locking require separately installed, compatible Symfony Cache and Lock implementations.
Installation
composer require lsr/scheduler
Configuration and jobs
Register the extension in the application DI configuration:
extensions: scheduler: Lsr\Scheduler\Di\SchedulerExtension
Define a job service and its schedule:
services: - App\Tasks\DeleteExpiredSessionsJob scheduler: sleep: 1000000 processOnlyLastMissedRun: true jobs: delete-expired-sessions: cron: '0 * * * *' timezone: Europe/Prague task: @App\Tasks\DeleteExpiredSessionsJob
Each job implements SchedulerJobInterface:
<?php declare(strict_types=1); namespace App\Tasks; use Lsr\Scheduler\SchedulerJobContext; use Lsr\Scheduler\SchedulerJobInterface; final readonly class DeleteExpiredSessionsJob implements SchedulerJobInterface { public function run(SchedulerJobContext $context): void { // Delete expired sessions or dispatch asynchronous work. } }
Use every instead of cron for interval-based jobs. Exactly one trigger must be configured:
scheduler: jobs: refresh-stats: every: '30 seconds' from: now until: '3000-01-01' task: @App\Tasks\RefreshStatsJob
The extension registers the scheduler:run command for lsr/console. Run it as a long-lived process:
php bin/console scheduler:run
Diagnose schedules
The extension also registers Symfony Scheduler's debug:scheduler command. It lists every configured job and attributed command with its trigger, provider name, and next run:
php bin/console debug:scheduler
Pass --date='2026-01-01 00:00:00 UTC' to calculate next runs from a fixed time. Pass --all to include recurring messages whose trigger has terminated. The integration exposes its single schedule under Symfony's conventional default name.
For RoadRunner, supervise the command as a service alongside the application workers:
service: scheduler: command: php bin/console scheduler:run process_num: 1 remain_after_exit: true restart_sec: 5
Persistent state and locking
Symfony Scheduler can remember the last run and prevent multiple replicas from processing the same schedule. Install compatible Symfony Cache and Lock implementations, define their services, then reference them:
scheduler: state: @scheduler.cache lock: @scheduler.lock
The lock service must implement Symfony\Component\Lock\LockInterface; the state service must implement Symfony\Contracts\Cache\CacheInterface. If multiple containers run the scheduler, both services must use shared storage.
Scheduled work should be idempotent. Long-running or retryable work is usually best dispatched to RoadRunner jobs from the scheduled job service.
Scheduling console commands
Console commands registered as DI services can schedule themselves with Symfony Scheduler's repeatable attributes. The scheduler extension discovers them during DI compilation, just like lsr/console discovers AsCommand:
<?php declare(strict_types=1); namespace App\Console\Commands; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Scheduler\Attribute\AsCronTask; use Symfony\Component\Scheduler\Attribute\AsPeriodicTask; #[AsCommand(name: 'sessions:delete-expired')] #[AsCronTask('0 * * * *', timezone: 'Europe/Prague')] #[AsPeriodicTask('15 minutes', arguments: ['--batch-size' => 500])] final class DeleteExpiredSessionsCommand extends Command { // Normal Symfony Console command implementation. }
Both attributes are repeatable. Their arguments value may be a Symfony ArrayInput-style array or a command-line string:
#[AsCronTask('0 3 * * *', arguments: ['tenant' => 'main', '--force' => true])] #[AsCronTask('0 4 * * *', arguments: 'archive --limit=500')]
Scheduled commands run non-interactively in the scheduler process. A non-zero exit code fails the scheduler run so the supervising process can report and restart it. The standalone integration supports the default schedule, command arguments, timezones, intervals, and jitter; Symfony Messenger transports and method-based task attributes are intentionally not supported.
Integration reference
The configuration schema and command discovery are defined in SchedulerExtension. Job services implement SchedulerJobInterface; SchedulerJobContext exposes the job ID, scheduled execution time and optional next scheduled time. sleep is the scheduler's polling sleep in microseconds.
The examples above illustrate application-owned job and command definitions: replace their bodies with the application's actual work and define any shown command arguments/options. bin/console is the application's entry point, not an executable installed by this package.
SchedulerRunnerInterface provides run() and stop() for custom process integration. The bundled command requests a stop on supported SIGINT/SIGTERM signals. Job and command lifecycle hooks are available in src/Lifecycle for instrumentation.
Development
CI runs the checks below on PHP 8.4 and 8.5. From a package checkout:
composer install --prefer-dist --no-interaction --no-progress composer cs vendor/bin/phpstan analyse --no-progress vendor/bin/phpunit --no-coverage
composer cs checks coding style without changing files. Run composer cs:fix (or composer cbf) to apply PHP CS Fixer rules from .php-cs-fixer.php.
The suite needs no external services or persistent cache/lock implementation. Install the DOM, mbstring, XML and XMLWriter extensions for PHPUnit. The composer test script enables Xdebug coverage; the CI command explicitly disables coverage collection.
AI coding assistance
See LSR Skills for AI agent skills for working with the LSR framework.
License
Licensed under the MIT License.