Search by

therayox / laravel-schedule-attributes

Define Laravel Artisan command schedules using PHP attributes.

Maintainers

Package info

gitlab.com/TheRayoX/laravel-schedule-routes

pkg:composer/therayox/laravel-schedule-attributes

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

1.0.0 2026-09-06 19:29 UTC

This package is not auto-updated.

Last update: 2026-09-06 23:48:14 UTC


README

Define schedules for Artisan command classes with PHP 8.3 attributes. The package registers native Laravel scheduler events, so php artisan schedule:list and php artisan schedule:run keep working as usual.

Installation

composer require therayox/laravel-schedule-attributes

Optionally publish the discovery configuration:

php artisan vendor:publish --tag=schedule-attributes-config

Usage

Frequency attributes create one event per declaration. Modifier attributes on the command class apply to every event created for that command.

Run a command once per day

The smallest useful declaration schedules a command at midnight:

use Illuminate\Console\Command;
use TheRayoX\ScheduleAttributes\Attributes\Daily;

#[Daily]
final class PruneExpiredTokens extends Command
{
    protected $signature = 'tokens:prune';

    public function handle(): int
    {
        // ...

        return self::SUCCESS;
    }
}

Use DailyAt when the time matters:

#[DailyAt('02:30')]
final class GenerateNightlyReport extends Command
{
    protected $signature = 'reports:generate';
}

Apply an operational policy to a command

use Illuminate\Console\Command;
use TheRayoX\ScheduleAttributes\Attributes\DailyAt;
use TheRayoX\ScheduleAttributes\Attributes\HourlyAt;
use TheRayoX\ScheduleAttributes\Attributes\OnOneServer;
use TheRayoX\ScheduleAttributes\Attributes\Timezone;
use TheRayoX\ScheduleAttributes\Attributes\WithoutOverlapping;

#[DailyAt('02:00', arguments: ['--force'])]
#[HourlyAt(30)]
#[Timezone('Europe/Madrid')]
#[WithoutOverlapping(60)]
#[OnOneServer]
final class SyncInvoices extends Command
{
    protected $signature = 'invoices:sync {--force}';

    public function handle(): int
    {
        return self::SUCCESS;
    }
}

DailyAt and HourlyAt create separate events. Both events receive the timezone, overlap lock and single-server policy. Arguments belong to the frequency attribute because they are passed when Laravel creates the command event.

Schedule the same command more than once

Multiple frequency attributes create independent Laravel events for the same command. This is useful when the same operation has an incremental execution during the week and a more expensive reconciliation at the weekend:

use TheRayoX\ScheduleAttributes\Attributes\DailyAt;
use TheRayoX\ScheduleAttributes\Attributes\Name;
use TheRayoX\ScheduleAttributes\Attributes\OnOneServer;
use TheRayoX\ScheduleAttributes\Attributes\Timezone;
use TheRayoX\ScheduleAttributes\Attributes\WeeklyOn;
use TheRayoX\ScheduleAttributes\Attributes\WithoutOverlapping;
use TheRayoX\ScheduleAttributes\Support\Day;

#[DailyAt('01:15', arguments: ['--mode=incremental'])]
#[WeeklyOn(Day::Sunday, '03:30', arguments: ['--mode=full', '--verify'])]
#[Timezone('Europe/Madrid')]
#[WithoutOverlapping(180)]
#[OnOneServer]
#[Name('sync-catalog:{schedule}:{index}')]
final class SyncCatalog extends Command
{
    protected $signature = 'catalog:sync {--mode=} {--verify}';
}

The frequency attributes may use different command arguments. Modifiers deliberately remain class-wide: both events get the same timezone, lock duration and single-server policy. If two executions need fundamentally different operational policies, model them as separate commands.

Run only during business hours

Use frequency and constraint attributes together to express a polling command that is active only during weekday office hours:

use TheRayoX\ScheduleAttributes\Attributes\Between;
use TheRayoX\ScheduleAttributes\Attributes\EveryMinute;
use TheRayoX\ScheduleAttributes\Attributes\Environments;
use TheRayoX\ScheduleAttributes\Attributes\Timezone;
use TheRayoX\ScheduleAttributes\Attributes\Weekdays;

#[EveryMinute]
#[Weekdays]
#[Between('08:00', '18:00')]
#[Timezone('Europe/Madrid')]
#[Environments(['staging', 'production'])]
final class PollPartnerApi extends Command
{
    protected $signature = 'partner:poll';
}

UnlessBetween('23:00', '04:00') is the inverse constraint and is useful for tasks that should avoid a maintenance window.

Use a custom cron expression

Cron is the escape hatch for schedules that do not have a dedicated convenience attribute. This example runs on the 5th minute of 04:00 on the third day of February when it is a Monday:

use TheRayoX\ScheduleAttributes\Attributes\Cron;
use TheRayoX\ScheduleAttributes\Attributes\EvenInMaintenanceMode;
use TheRayoX\ScheduleAttributes\Attributes\RunInBackground;

#[Cron('5 4 3 2 1')]
#[RunInBackground]
#[EvenInMaintenanceMode]
final class RebuildSearchIndex extends Command
{
    protected $signature = 'search:rebuild';
}

Monthly and yearly maintenance

use TheRayoX\ScheduleAttributes\Attributes\LastDayOfMonth;
use TheRayoX\ScheduleAttributes\Attributes\MonthlyOn;
use TheRayoX\ScheduleAttributes\Attributes\TwiceMonthly;
use TheRayoX\ScheduleAttributes\Attributes\YearlyOn;

#[MonthlyOn(1, '04:00', arguments: ['--period=month-start'])]
#[TwiceMonthly(1, 16, '02:30', arguments: ['--period=mid-month'])]
#[LastDayOfMonth('23:45', arguments: ['--period=month-end'])]
#[YearlyOn(1, 1, '00:15', arguments: ['--period=year-start'])]
final class CloseAccountingPeriods extends Command
{
    protected $signature = 'accounting:close {--period=}';
}

Available attributes

Current frequency attributes include Cron, EveryMinute, Hourly, HourlyAt, Daily, DailyAt, TwiceDaily, TwiceDailyAt, Weekly, WeeklyOn, Monthly, MonthlyOn, TwiceMonthly, LastDayOfMonth, Yearly, and YearlyOn.

Current class-wide modifiers include Timezone, Environments, Weekdays, Weekends, Days, Between, UnlessBetween, WithoutOverlapping, OnOneServer, RunInBackground, EvenInMaintenanceMode, Name, and Description.

WeeklyOn and Days accept integer day values (0 for Sunday) or the Day enum, for example #[WeeklyOn(Day::Monday, '08:00')].

When OnOneServer is used with multiple schedules and no Name is supplied, the package generates stable, distinct event names to prevent mutex collisions. A custom Name can use {command}, {schedule}, and {index} placeholders.

Name and Description map to the same Laravel event description. Use one or the other on a command; if both are present, the last declared attribute determines the displayed description.

Compatibility

The package supports Laravel 12 and Laravel 13 on PHP 8.3 or newer.

Discovery

Commands are discovered from app/Console/Commands by default. Additional directories can be configured:

return [
    'enabled' => true,
    'directories' => [
        app_path('Console/Commands'),
        base_path('modules/Billing/Commands') => [
            'patterns' => ['*Command.php'],
            'not_patterns' => ['*Test.php'],
        ],
    ],
];