Simple Task Scheduler With PHP
Follow along as one developer explains how to create a scheduling tool for DevOps teams that he dreamed up using the PHP language.
Join the DZone community and get the full member experience.
Join For FreeDuring a more or less large project, a situation may arise when the number of scheduled tasks (cron jobs) becomes so large that their support becomes a DevOps nightmare. To solve this problem, I came up with the idea of creating a PHP scheduler implementation, thereby making it a part of the project, allowing the tasks themselves to be part of its configuration. In this case, the necessary and sufficient number of cron jobs will be equal to one.
Some time ago, I was able to develop a module for event planning. It was just a simplified version of Google/Apple Calendar for users of the application. For storing the dates and rules regarding the repetition of events, it was decided to use the iCalendar format (RFC 5545), which allows one line to describe a schedule for repeating an event, while taking into account the days of the week, months, the number of repetitions, and much more. A few examples:
FREQ = WEEKLY; BYDAY = SU, WE - Weekly on Saturday and Wednesday
FREQ = MONTHLY; COUNT = 5 - Every month, five times
FREQ = YEARLY; INTERVAL = 2; BYMONTH = 1; BYDAY = SU - Every second year on every Saturday of January
As you can see, this standard allows us to describe the rules for repeating the event in a much more flexible way than cron.
To work with the iCalendar format, I was lucky enough to find a wonderful library (don't fret its stars).
Having a tool for working with RRULE (Recurrence Rule), the application becomes much simpler. Write several classes that allow you to schedule and run tasks (which are any manifestation of the PHP callable type).
Installing the Library:
composer require hutnikau / job-scheduler
Scheduling and Launching Tasks:
The class representing the task is: \Scheduler\Job\Job
To create its instance, you need a rule for its repetition (RRULE) and an instance of type callable:
$startTime = new \DateTime('2017-12-12 20:00:00');
$rule = new \Scheduler\Job\RRule('FREQ=MONTHLY;COUNT=5', $startTime); //run monthly, at 20:00:00 starting from the 12th of December 2017, 5 times
$job = new \Scheduler\Job\Job($rule, function () {
//do something
});
Alternatively, use \Scheduler\Job\ Job:: createFromString ()
:
$job = \Scheduler\Job\Job::createFromString(
'FREQ=MONTHLY;COUNT=5', //Recurrence rule
'2017-12-28T21:00:00', //Start date
function() {}, //Callback
'Europe/Minsk' //Timezone. If $timezone is omitted, the current timezone will be used
);
Do not forget about time zones. I strongly advise you to always specify them explicitly (not only when working with this library, but with \DateTime
as a whole) in order to avoid unpleasant surprises.
Add the task to the schedule:
$scheduler = new \Scheduler\Scheduler()
$scheduler->addJob($job);
You can also transfer an array of tasks to the constructor:
$scheduler = new \Scheduler\Scheduler([
$job,
//more jobs here
])
Now, launch the scheduled tasks:
$jobRunner = new \Scheduler\JobRunner\JobRunner();
$from = new \DateTime('2017-12-12 20:00:00');
$to = new \DateTime('2017-12-12 20:10:00');
$reports = $jobRunner->run($scheduler, $from, $to, true);
In this example, all tasks scheduled for the specified time period (10 minutes) will be performed.
So you only need one cron job, which runs JobRunner
.
You can omit the
$
from the parameter so that all tasks will be performed, starting from$
up to the current time.
The last parameter determines whether the tasks are completed, the execution time of which falls exactly on the boundary values ('2017-12-12 20:00:00' and '2017-12-12 20:10:00' from the example above).
When starting the scheduler with cron, I advise you to save the time of the last run, and the next time you run it, pass it to $
from the parameter by adding one second, because the cron's accuracy is not perfect, and it is possible to skip any tasks or execute them twice.
$ jobRunner-> run (...)
returns an array of results of completed tasks (array of objects of type \Scheduler\Action\Report
).
\Scheduler\Action\Report {
/* Methods */
public mixed getReport ( void )
public Action getAction ( void )
public string getType ( void )
}
By calling \Scheduler\Action\Report:: getReport ()
, you can get the result of executing a callable (the value returned by it).
In the event that an exception was thrown during the execution of the task, \Scheduler\Action\Report:: getReport ()
returns the same exception.
The method \Scheduler\Action\Report:: getAction ()
will return an instance of the type \Scheduler\Action\ActionInterface
, which describes the action taken. By using it you can find out the time of the action or get the action itself.
It's also worth noting that if a scheduled task had to be executed more than once (for example, if the MINUTELY interval was used in RRULE, and the difference between $ from and $ to, transferred in JobRunner 10 minutes), then the action will be performed several times. In other words, they will not be grouped.
The library is really small, but I hope someone will be useful.
Opinions expressed by DZone contributors are their own.
Comments