Weave Code
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 Laravel Package

symfony/scheduler

Symfony Scheduler provides simple task scheduling built on Symfony Messenger. Define recurring or delayed jobs and run them via workers, integrating cleanly with your existing messaging setup. Links to docs, contributing, and issue tracking in the main Symfony repo.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require symfony/scheduler
    

    Ensure symfony/messenger is also installed (required dependency).

  2. Basic Configuration: Add to config/packages/messenger.yaml:

    framework:
        messenger:
            transports:
                async: '%env(MESSENGER_TRANSPORT_DSN)%'
            routing:
                'App\Message\YourMessage': async
    
  3. First Use Case: Schedule a command to run daily using a ScheduleProvider:

    // src/Message/YourMessage.php
    use Symfony\Component\Scheduler\Attribute\AsCronTask;
    
    #[AsCronTask("0 0 * * *", name: "daily.task")]
    class YourMessage {}
    

    Register the provider in config/services.yaml:

    services:
        App\Message\YourMessage:
            tags: ['scheduler.task']
    
  4. Run the Scheduler:

    php bin/console messenger:consume async -vv
    

Implementation Patterns

Core Workflows

  1. Declarative Scheduling: Use attributes (#[AsCronTask], #[AsFrequencyTask]) for cron-like or interval-based tasks:

    #[AsCronTask("*/5 * * * *", name: "every_five_minutes")]
    class EveryFiveMinutesTask {}
    
  2. Dynamic Scheduling: Implement ScheduleProviderInterface for runtime-generated schedules:

    use Symfony\Component\Scheduler\ScheduleProviderInterface;
    use Symfony\Component\Scheduler\Schedule;
    
    class DynamicScheduleProvider implements ScheduleProviderInterface
    {
        public function getSchedule(): Schedule
        {
            return (new Schedule())
                ->addCronTask('* * * * *', new YourMessage());
        }
    }
    

    Register it in config/services.yaml:

    services:
        App\Message\DynamicScheduleProvider:
            tags: ['scheduler.schedule_provider']
    
  3. Command Integration: Schedule console commands:

    #[AsCronTask("0 0 3 * *", name: "backup_db")]
    class BackupDatabaseCommand {}
    

    Ensure the command is tagged as a console.command in services.yaml.

  4. Event-Driven Scheduling: Trigger tasks via Messenger events or other Symfony events:

    use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    use Symfony\Component\Scheduler\Event\PostRunEvent;
    
    class SchedulerEventSubscriber implements EventSubscriberInterface
    {
        public static function getSubscribedEvents(): array
        {
            return [
                PostRunEvent::NAME => 'onTaskRun',
            ];
        }
    
        public function onTaskRun(PostRunEvent $event): void
        {
            // Handle post-run logic (e.g., logging, notifications)
        }
    }
    

Integration Tips

  • Laravel Bridge: Use a custom MessageBus to bridge Symfony Messenger with Laravel Queues:

    use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
    use Symfony\Component\Messenger\Transport\Serialization\Serializer;
    use Illuminate\Bus\Dispatcher;
    
    $dispatcher = app(Dispatcher::class);
    $serializer = new Serializer();
    $bus = new BridgeBus($dispatcher, $serializer);
    
  • Horizon Compatibility: Extend Symfony’s Transport to work with Laravel’s queue drivers (e.g., Redis, database):

    use Symfony\Component\Messenger\Transport\TransportInterface;
    use Illuminate\Queue\QueueManager;
    
    class LaravelQueueTransport implements TransportInterface
    {
        public function __construct(private QueueManager $queue)
        {}
    
        public function get(): iterable
        {
            while ($job = $this->queue->pop('scheduler')) {
                yield $job;
            }
        }
    
        // Implement other TransportInterface methods...
    }
    
  • Debugging & Monitoring: Use Symfony’s built-in commands:

    # List scheduled tasks
    php bin/console scheduler:list
    
    # Show next runs
    php bin/console scheduler:show next
    
    # Run a specific task manually
    php bin/console scheduler:run "daily.task"
    

Gotchas and Tips

Pitfalls

  1. Duplicate Schedule Names: Symfony throws an error if two ScheduleProviders register the same schedule name. Validate names in getSchedule():

    if ($this->schedule->has('duplicate.name')) {
        throw new \RuntimeException('Schedule name already exists');
    }
    
  2. Checkpoint Recovery: If the consumer crashes midway, pending RecurringMessages may not recover. Ensure your transport supports checkpointing (e.g., Doctrine transport with checkpoint_interval):

    messenger:
        transports:
            async:
                dsn: 'doctrine://default'
                options:
                    checkpoint_interval: 100
    
  3. Time Zone Handling: Cron expressions and frequency tasks use the server’s time zone. Explicitly set it in config/packages/messenger.yaml:

    framework:
        messenger:
            transports:
                async:
                    options:
                        timezone: 'Europe/Paris'
    
  4. Command Arguments: Passing arguments to scheduled commands requires explicit handling:

    #[AsCronTask("0 0 * * *")]
    class BackupCommand implements CommandInterface
    {
        public function __invoke(Command $command, string $arg1, int $arg2)
        {
            // Handle args
        }
    }
    

    Register the command with arguments in services.yaml:

    services:
        App\Message\BackupCommand:
            arguments:
                $arg1: 'value'
                $arg2: 123
            tags: ['scheduler.task']
    
  5. Cache TTL Issues: Checkpoint states expire if the cache TTL is too short. Increase it in config/packages/cache.yaml:

    framework:
        cache:
            app: 'doctrine'
            default_ttl: 3600  # 1 hour
    

Debugging Tips

  1. Log Next Runs: Use the scheduler:show next command to verify schedules:

    php bin/console scheduler:show next --format=json
    
  2. Simulate Time: Override the ClockInterface for testing:

    use Symfony\Component\Scheduler\ClockInterface;
    
    class FixedClock implements ClockInterface
    {
        public function now(): \DateTimeInterface
        {
            return new \DateTime('2023-01-01 12:00:00');
        }
    }
    

    Bind it in config/services_test.yaml:

    services:
        Symfony\Component\Scheduler\ClockInterface: '@App\Clock\FixedClock'
    
  3. Transport-Specific Issues:

    • Doctrine Transport: Ensure the message_history table exists and is accessible.
    • Redis Transport: Verify the connection and keyspace (default: messenger).
    • Sync Transport: Not recommended for production; use async transports.
  4. Event Dispatching: Debug PostRunEvent by subscribing to it:

    use Symfony\Component\Scheduler\Event\PostRunEvent;
    
    $eventDispatcher->addListener(PostRunEvent::NAME, function (PostRunEvent $event) {
        \Log::debug('Task ran:', [
            'name' => $event->getTaskName(),
            'result' => $event->getResult(),
        ]);
    });
    

Extension Points

  1. Custom Transports: Implement TransportInterface for Laravel-specific backends (e.g., database queues):

    use Symfony\Component\Messenger\Transport\TransportInterface;
    use Illuminate\Database\Eloquent\Model;
    
    class EloquentTransport implements TransportInterface
    {
        public function __construct(private Model $queueModel) {}
    
        public function get(): iterable
        {
            foreach ($this->queueModel->available() as $job) {
                yield $job->message;
            }
        }
    
        // Implement other methods...
    }
    
  2. Dynamic Schedule Providers: Fetch schedules from an API or database:

    class ApiScheduleProvider implements ScheduleProviderInterface
    {
        public function getSchedule(): Schedule
        {
            $schedules = $this->apiClient->fetchSchedules();
            $schedule = new Schedule();
    
            foreach ($schedules as $task) {
                $schedule->addCronTask($task['cron'], new ($task['class']));
            }
    
            return $schedule;
        }
    }
    
  3. Middleware for Tasks: Add pre/post-run logic via TaskMiddleware:

    use Symfony\Component\Scheduler\Event\TaskEvent;
    
    class LoggingMiddleware implements TaskMiddlewareInterface
    {
        public function handle(TaskEvent $event): void
        {
            \Log::info('Task started', ['name' => $event->getTaskName()]);
        }
    
        public function onPostRun(PostRunEvent $
    
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.
directorytree/privacy-filter-classifier
directorytree/privacy-filter
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi
splash/scopes
splash/toolkit
testo/output-teamcity
testo/bridge-symfony
spatie/flare-daemon-runtime