Some good thoughts in other answers here. I would also point out that you should give consideration to using PHP's DateTime
, DateInterval
, DatePeriod
, and related classes if you find yourself needing to do more complex date handling (like displaying all scheduled tasks in calendar in GUI admin tool)
You might have a DB table containing task schedule rules that would look something like:
id - unique auto-increment
name - human-readable task name
owner - perhaps forieg key to user tables so you know who owns tasks
interval - An string interval specification as used in DateInterval
start_time - Datetime When rule goes into effect
end_time - Datetime When rule is no longer in effect
script_path - path to script of some sort of command recognized by your applcation
last_execution - Datetime for last time script was triggered
next_execution - Datetime in which you store value calculated to be next execution point
active - maybe a flag to enable/disable a rule
perhaps other admin fields like created_time, error_tracking, etc.
And you could easily build a collection of of DatePeriod objects you can iterate on from each table row. That might look something like:
// have one authoritative now that you use in this script
$now = DateTime();
$now_sql = $now->format('Y-m-d H:i:s');
$sql = <<<EOT
SELECT
id,
name,
interval,
/* etc */
FROM task_rules
WHERE
active = 1
AND
(IS_NULL(start_time) OR start_time <= '{$now_sql}')
AND
(IS_NULL(end_time) OR eend_time > '{$now_sql}')
/* Add this filter if you are trying to query this table
for overdue events */
AND
next_execution <= '{$now_sql}'
/* any other filtering you might want to do */
/* Any ORDER BY and LIMIT clauses */
EOT;
$tasks = array();
//logic to read rows from DB
while ($row = /* Your DB fetch mechanism */) {
// build your task (probably could be its own class,
// perhaps saturated via DB retrieval process), but this is jist
$task = new stdClass();
$task->id = $row->id
$task->name = $row->name;
$task->interval = $row->interval;
$task->start_time = $row->start_time;
// etc. basically map DB row to an object
// start building DateTime and related object representations
// of your tasks
$task->dateInterval = new DateInterval($task->interval);
// determine start/end dates for task sequence
if(empty($task->start_time)) {
// no defined start date, so build start date from last executed time
$task->startDateTime = DateTime::createFromFormat(
'Y-m-d H:i:s',
$task->last_execution
);
} else {
// start date known, so we want to base period sequence on start date
$task->startDateTime = DateTime::createFromFormat(
'Y-m-d H:i:s',
$task->start_date
);
}
if(empty($task->end_time)) {
// No defined end. So set artificial end date based on app needs
// (like we need to show next week, month, year)
$end_datetime = clone $now;
$end_datetime->modify(+ 1 month);
$task->endDateTime = $end_datetime;
} else {
$task->endDateTime = DateTime::createFromFormat(
'Y-m-d H:i:s',
$task->end_time
);
}
$task->datePeriod = new DatePeriod(
$task->startDateTime,
$task->dateInterval,
$task->endDateTime
);
// iterate datePeriod to build array of occurences
// which is more useful than just working with Traversable
// interface of datePeriod and allows you to filter out past
// scheduled occurences
$task->future_occurrences = [];
foreach ($task->datePeriod as $occurence) {
if ($occurence < $now) {
// this is occcurrence in past, do nothing
continue;
}
$task->future_occurrences[] = $occurence;
}
$task->nextDateTime = null;
if(count($task->future_occurrences) > 0) {
$task->nextDateTime = $task->future_occurrences[0];
$task->next_execution = $task->nextDateTime->format('Y-m-d H:i:s');
}
$tasks[] = $task;
}
Here $tasks
would contain an array of objects each representing a single rule along with tangible PHP DateTime, DatePeriod constructs you can use to to execute and/or display tasks.
For example:
// execute all tasks
// just using a simple loop example here
foreach($tasks as $task) {
$command = 'php ' . $task->script_path;
exec($command);
// update DB
$sql = <<<EOT
UPDATE task_rules
SET
last_execution = '{$now_sql}',
next_execution = '{$task->next_execution}'
WHERE id = {$task->id}
EOT;
// and execute using DB tool of choice
}