symfony/scheduler
Symfony Scheduler Component lets you define recurring and one-off tasks and run them via Symfony Messenger. Supports cron-like schedules, delays, and integration with transports and workers. See docs for configuration, commands, and usage.
Installation:
composer require symfony/scheduler
Requires Symfony Messenger and PHP 8.4+ (Symfony 8.x requirement).
Configure Messenger Transport:
Define a transport (e.g., doctrine, amqp, or sync) in config/packages/messenger.yaml:
framework:
messenger:
transports:
async: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
'App\Message\YourMessage': async
Create a Scheduled Task:
Use the #[AsCronTask] or #[AsFrequencyTask] attribute on a command or message handler:
use Symfony\Component\Scheduler\Attribute\AsCronTask;
#[AsCronTask("0 0 * * *", name: "daily-report")]
class GenerateDailyReportCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
// Task logic
return Command::SUCCESS;
}
}
Register the Schedule Provider:
In config/services.yaml, tag your provider:
services:
App\Scheduler\DailyReportScheduleProvider:
tags: [scheduler.schedule_provider]
Run the Scheduler: Execute the scheduler worker:
php bin/console messenger:consume async -vv
Replace a cron job with a Symfony Scheduler task:
#[AsCronTask("*/5 * * * *", name: "cleanup-temp-files")]
class CleanupTempFilesCommand extends Command
{
public function __construct(private Filesystem $filesystem) {}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->filesystem->remove($this->getTempDir());
return Command::SUCCESS;
}
}
Key: No external cron needed; Symfony handles execution via Messenger.
#[AsCronTask] and #[AsFrequencyTask]: Core attributes for defining schedules.ScheduleProviderInterface: For dynamic schedules (e.g., runtime-generated cron jobs).doctrine, amqp) is configured in messenger.yaml.debug:scheduler.Use attributes for clean, type-safe scheduling:
#[AsCronTask("0 12 * * 1", name: "weekly-backup")]
class WeeklyBackupCommand extends Command
{
// ...
}
Pros: IDE-friendly, no XML/YAML.
Cons: Less flexible for dynamic schedules (use ScheduleProviderInterface instead).
ScheduleProviderInterfaceFor runtime-defined schedules (e.g., user-specific tasks):
use Symfony\Component\Scheduler\ScheduleProviderInterface;
use Symfony\Component\Scheduler\TriggerInterface;
class UserSpecificScheduleProvider implements ScheduleProviderInterface
{
public function getSchedule(): array
{
return [
'user-' . $userId => new CronTrigger('0 0 * * *', new Message('App\Message\ProcessUserData', ['userId' => $userId])),
];
}
}
Register in services.yaml:
services:
App\Scheduler\UserSpecificScheduleProvider:
tags: [scheduler.schedule_provider]
Leverage Messenger’s transports (e.g., doctrine, amqp) for reliability:
# config/packages/messenger.yaml
framework:
messenger:
transports:
async: 'doctrine://default?queue_name=scheduler'
routing:
'Symfony\Component\Scheduler\Message\ScheduleMessage': async
Key: Use ScheduleMessage for internal scheduler communication.
Access command output or handle failures:
use Symfony\Component\Scheduler\Event\PostRunEvent;
class SchedulerListener
{
public function onPostRun(PostRunEvent $event): void
{
if ($event->getExitCode() !== 0) {
// Log failure or send alert
}
}
}
Register in services.yaml:
services:
App\Scheduler\SchedulerListener:
tags: [kernel.event_listener, scheduler.event_listener]
php bin/console messenger:consume async -vv
scheduler:list and scheduler:run commands.
debug:scheduler now uses checkpoints for accurate next-run calculations.Bridge Symfony Scheduler with Laravel via a custom Artisan command:
use Symfony\Component\Process\Process;
class RunSymfonyScheduler extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
$process = new Process(['php', 'bin/console', 'messenger:consume', 'async']);
$process->run();
$output->write($process->getOutput());
return $process->getExitCode();
}
}
Register in app/Console/Kernel.php:
protected function schedule(Schedule $schedule)
{
$schedule->command('run-symfony-scheduler')->everyMinute();
}
The debug:scheduler command now uses stored checkpoints as the base date for calculating next runs, improving accuracy:
php bin/console debug:scheduler
Key: More reliable debugging for cron-based schedules.
PHP Version Mismatch:
Duplicate Schedule Names:
app.daily-backup vs. user.daily-backup).Messenger Transport Misconfiguration:
php bin/console messenger:failed:list.#[AsCronTask] Argument Issues:
#[AsFrequencyTask] with Message objects instead.Time Zone Awareness:
SYMFONY_CRON_TIMEZONE in .env:
SYMFONY_CRON_TIMEZONE=America/New_York
Command Alias Conflicts:
php bin/console list --all.List All Schedules with Checkpoints:
php bin/console debug:scheduler
Run a Single Schedule:
php bin/console scheduler:run "daily-report"
Check Failed Tasks:
php bin/console messenger:failed:list
Enable Debug Logging:
Add to config/packages/dev/monolog.yaml:
handlers:
scheduler
Verify Checkpoint Storage:
Ensure your transport (e.g., Doctrine) persists checkpoints correctly. For doctrine transport, check the failed and scheduled tables.
debug:scheduler may show incorrect next-run times.amqp).How can I help you explore Laravel packages today?