A few days ago I had a special requirement for the Laravel scheduler. I needed to run a command every day, but it had to be at a different (or random) moment each day. This seemed like something that was easy enough, however the solutions I came up with were rather messy and had too many dependencies.
At first, the easiest way to accomplish this was to run another command at the start of the day that picked a time at which the aforementioned command should run. However, this needed a place to store the time somewhere and this is something I wanted to avoid.
It took me a while, but later that day I discovered a simple and elegant way to accomplish this functionality. PHP, like most other programming languages, has a random number generated that can be manually seeded. The great thing about this is that the same seed will always return the same consecutive numbers. So for our use case: if we seed the random number generator with today’s date, we can let the “random” number generator decide how many seconds from midnight we want the command to run.
srand(intval(date('Ymd')));
$hour = floor(rand(0, 1440) / 60);
$minute = rand(0, 1440) % 60;
$schedule->command(Commands\MyCommand::class)->dailyAt("{$hour}:{$minute}");
I like this solution very much. The code is clean and there is no storage dependency.
One small problem with this way of working is when you want to use the random number generator inside your scheduler. If you have any scheduled command that uses this, it will generate the same sequence of numbers each day.
When you are in this situation I have found two possible solutions. You could either set a new seed using
hrtime
as a semi-random value. Or you could execute the commands
asynchronously using queues which run as a new PHP process.
The same logic can also be applied to database queries. Say you want to display a random product on your website each
day without having to cache it for 24 hours. The Laravel query builder includes the
inRandomOrder
function
which can be passed a seed.
Product::query()
->inRandomOrder(date('Ymd'))
->first();
Important notice
This functionality only works for MySQL databases. Other drivers do not support setting a seed for theinRandomOrder
function.