Weave 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

Chaotic Schedule Laravel Package

skywarth/chaotic-schedule

Generate random, realistic cron schedules and intervals in PHP and Laravel. Chaotic Schedule makes it easy to fake unpredictable schedule frequencies for tests, seeders, demos, and load simulations, with constraints for hours, days, and expressions.

View on GitHub
Deep Wiki
Context7

Getting Started

The package adds chaotic (randomised-but-deterministic) scheduling macros to Laravel's Illuminate\Console\Scheduling\Schedule, so a job can fire "somewhere in this week" instead of at a fixed minute.

  1. Check your platform first. v3.0.0 requires PHP >= 8.3 and Laravel >= 13.x. If you're on Laravel 12 or PHP 8.2, pin ^2.0 — the API is identical, so you can write the same schedule code and migrate later.
  2. composer require skywarth/chaotic-schedule
  3. Nothing to register in Laravel 13's auto-discovery; if you publish a config, do it now and commit it so the seed basis is consistent across environments.
  4. Look first at: the RandomDateScheduleBasis enum (WEEK / MONTH / YEAR) and the randomDays() / randomTimeSchedule() macros in the README. Those two macros plus the enum are essentially the whole surface area.

First use case: a nightly report or sync that must run N times per week but must not hammer the same minute every night:

Schedule::command('reports:nightly')
    ->randomDays(RandomDateScheduleBasis::WEEK, 3)
    ->between('01:00', '05:00');

Run php artisan schedule:list immediately afterwards — you should see concrete, plausible timestamps rather than the macro name. That's your sanity check that the seeding works.

Implementation Patterns

Enum cases, not integers. The v3 call style is the documented one:

->randomDays(RandomDateScheduleBasis::WEEK, 3)
->randomDays(RandomDateScheduleBasis::MONTH, 10)
->randomDays(RandomDateScheduleBasis::YEAR, 24)

Keep the determinism tuple stable. Generated runs are a pure function of (uniqueIdentifier, basisDate, periodType). That means every web node, queue worker and CI box computes the same schedule — no leader election needed to avoid duplicate runs. Treat the identifier as part of your application's contract: renaming it silently reshuffles every future run for that job.

Translate config values at the boundary, not in routes/console.php. If periods come from .env or a DB settings table, convert once:

$basis = RandomDateScheduleBasis::tryFrom(strtoupper(config('reports.period')))
    ?? RandomDateScheduleBasis::WEEK;

tryFrom() replaces the old validate() / isValid() helpers, and cases() replaces getAll() — useful for populating an admin dropdown without hardcoding values.

Layer the usual schedule constraints on top. The macros pick runs; between(), weekdays(), when(), withoutOverlapping() and onOneServer() still behave normally. Prefer between() to express your maintenance window and let the macro choose the moment inside it, rather than post-filtering.

Combine random days with random times for a two-axis spread (which day and which minute), which is the usual way to avoid a thundering herd when you have dozens of tenants on one scheduler.

Test against a frozen basis date. Because seeding is deterministic, a test that pins the clock and asserts the rendered schedule:list output is stable and cheap. This is the single highest-value test to write — it catches accidental identifier renames and period changes in review.

Upgrade path: bump composer.json, run your test suite, then grep for the removed helpers:

grep -rn "getString\|getDayCount\|isValid\|getAll\|InvalidScheduleBasisProvided" app/ config/ routes/

Gotchas and Tips

  • TypeError is not an Exception. This is the migration trap that will bite you in production. Invalid enum values now fail at the type-system level, so a broad catch (\Exception $e) around schedule construction (a common pattern in a settings controller that builds schedules from user input) will no longer catch it and you'll get a 500. Widen to catch (\Throwable $e) or add catch (\TypeError $e) ahead of it. Better still, validate with tryFrom() before the value reaches the macro.
  • Delete the InvalidScheduleBasisProvided import. It's gone in v3. Static analysis catches this, but only if you actually run it — a stale use statement plus a catch block is silent dead code.
  • "Random" is stable, and that's the point — but it's also a footgun. If you hard-code basisDate to a fixed date, the schedule never reshuffles and you've built a fancy constant. Derive it from the current period (now()->startOfWeek(), startOfMonth()), and be aware that this means every consumer sees a new distribution at each period boundary — expected, but surprising if you assumed per-day variation.
  • Same identifier = same schedule. Two different commands sharing an identifier will get correlated runs. Use distinct, meaningful identifiers per job.
  • Timezones. The basis date and the generated times are evaluated in the app timezone unless you call ->timezone() on the event. Mixing the two produces runs that look random in local time but are deterministic in UTC — confusing when debugging.
  • The randomTimeSchedule float→int fix removes an implicit-conversion deprecation. Seed determinism is explicitly preserved across v2→v3, so existing schedules should not drift; still, diff schedule:list output before and after upgrading on a staging box if you have compliance requirements about run times.
  • Debugging aid: when a job "isn't running", check schedule:list before anything else. If the macro produced a time outside your between() window, or the event was filtered out entirely, it will be visible there.
  • Extension point: if the built-in WEEK/MONTH/YEAR grains are too coarse, wrap the macro in a small helper that computes the correct basis and identifier from your own domain concept (tenant ID, report slug) rather than reaching for internals — the seed tuple is the stable contract, so keep it under your control.
  • Don't rely on schedule:run alone in tests. It only fires events that are due this minute; with chaotic schedules that's almost never. Use schedule:list / schedule:test to assert the computed runs instead.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource