therayox / laravel-schedule-attributes
Define Laravel Artisan command schedules using PHP attributes.
Package info
gitlab.com/TheRayoX/laravel-schedule-routes
pkg:composer/therayox/laravel-schedule-attributes
Requires
- php: ^8.3
- illuminate/console: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- symfony/finder: ^7.4|^8.0
Requires (Dev)
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.5.50|^12.5.8
Suggests
None
Provides
None
Conflicts
None
Replaces
None
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'],
],
],
];