Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to schedule dynamic function with cron job?

I want to know how I can schedule a dynamic(auto populated data) function to auto run everyday at saved time?

Let's say I have a form that once the button is clicked it sends the data to the function, which the posts the data. I simply want to automate that so that I don't have to press the button.

<ul>
    <?php 
    foreach($Class->retrieveData as $data)
    {
        <form method="post" action="">
            <li>
                <input type="hidden" name="name">'.$data['name'].'<br/>
                <input type="hidden" name="description">'.$data['description'].'<br/>
                <input type="submit" name="post_data"  value="Post">
            </li>
        </form>
    }
    ?>
</ul>

Now, the form will pass the data to the function.

if(isset($_POST['post_data'])) // if post_data button is clicked then it runs myFunction()
{
    myFunction();
}

myFunction()
{
    $name        = $_POST['name'];
    $description = $_POST['description'];
}

I tried doing the following but the problem is that Cron Job can only run the whole .php file, and I am retrieving the saved time to run from MySQL.

foreach($Class->getTime() as $timeData)
{
    $timeHour    = $timeData['timeHour'];
    $timeMinute = $timeData['timeMinute'];

    $hourMin    = date('H:i');
    $timeData   = ''.$timeHour.':'.$timeMinute.'';

    if($hourMin == $timeData)
    {
        run myFunction.
    }
}

$hourMin is the current hour:minute which is being matched against a saved time to auto run from Mysql. So if $hourMin == $timeData then the function will run.

How can I run Cron Job to auto run myFunction() if the $hourMin equals $timeData?

So...

List 1 = is to be runned at 10am
List 2 = is to be runned at 12pm
List 3 = is to be runned at 2pm

The 10am, 12pm, 2pm is the $timeHour and $timeMinute that is retrieved from MySQL but based on each list id's.

EDIT

@randomSeed,

1) I can schedule cron jobs.
2) $name and $description will all be arrays, so the following is what I am trying to accomplish.

$name = array(
    'Jon',
    'Steven',
    'Carter'
);

$description = array(
    'Jon is a great person.',
    'Steven has an outgoing character.',
    'Carter is a horrible person.'
);

I want to parse the first arrays from both $name and $description if the scheduled time is correct.

In database I have the following

postDataTime table

+----+---------+----------+------------+--------+
| iD | timeDay | timeHour | timeMinute | postiD |
+--------------------------------------+--------+
| 1  | *       | 9        | 0          | 21     |
|----|---------|----------|------------|--------|
| 2  | *       | 10       | 30         | 22     |
|----|---------|----------|------------|--------|
| 3  | *       | 11       | 0          | 23     |
+----|---------+----------+------------+--------+

iD         = auto incremented on upload.
timeDay    = * is everyday (cron job style)
timeHour   = Hour of the day to run the script
timeMinute = minute of the hour to run script
postiD     = this is the id of the post that is located in another table (n+1 relationship)

If it's difficult to understand.. what is quinoa

if(time() == 10:30(time from MySQL postiD = 22))
{
    // run myFunction with the data that is retrieved for that time ex:

    $postiD = '22';
    $name   = 'Steven';
    $description = 'Steven has an outgoing character.';

    // the above is what will be in the $_POST from the form and will be
    // sent to the myFunction()
}

I simply want to schedule everything according to the time that is saved to MySQL as I showed at the very top(postDataTime table). (I'd show what I have tried, but I have searched for countless hours for an example of what I am trying to accomplish but I cannot find anything and what I tried doesn't work.).

I thought I could use the exec() function but from what it seems that does not allow me to run functions, otherwise I would do the following..

$time = '10:30';
if($time == time())
{
    exec(myFunction());
}
like image 613
iBrazilian2 Avatar asked May 22 '14 01:05

iBrazilian2


People also ask

What is dynamic cron job?

It is an ideal solution for applications that must dynamically start and manage scheduled tasks at runtime.

What is the use of * * * * * In cron?

It is a wildcard for every part of the cron schedule expression. So * * * * * means every minute of every hour of every day of every month and every day of the week . 0 1 * * * - this means the cron will run always at 1 o'clock. * 1 * * * - this means the cron will run each minute when the hour is 1.

What does cron 0 * * * * * mean?

*/5 * * * * Execute a cron job every 5 minutes. 0 * * * * Execute a cron job every hour.


4 Answers

Cron tasks require you to preset the times at which they run, they cannot (yes you could hack this by having a script which edits your crontab, but I would not say that is a very good idea) have their time to run decided dynamically. This means you essentially have two options:

1) Set a cronjob to run every minute and use a temp file which you touch to tell the last time that it ran one of the scheduled tasks Each time that it runs it checks if there was a task to run in between the last timestamp of your temp file and the current time, and if there is it runs the task. This is a gross but simple solution.

2) Don't use cron. Create a daemon which checks what times tasks need to be run and puts them into a priority queue, then it pops the earliest element and sleeps until it is time to run that task. It runs the task and reinserts it to be run 24 hours in the future and repeats. This solution is by far more elegant, but it also requires more work.

like image 75
Brendan F Avatar answered Oct 13 '22 23:10

Brendan F


you have 2 ways, although only one will do exactly what you want to do;

  • 1st way requires that you have access and privileges to change cron-jobs server side (example via PHP or other). Depending on what OS there are tutorials: Win , Nix

  • 2nd way will do something close to what you want but without the minutes precision, you will loose at max 2 minutes each cycle. (see exaplanation below).

1st Way perfect way

  • As soon as the user hit the form create a unique cron-task for that user using the desired datatime.

if you don't have those privileges you can use 3d part service like www.easycron.com they also offer a Free version with limited query. they also provide a REST API method to manage (CRUDE) cron-tasks.

2nd Way imperfect way

  • add a new VARCHAR column, i called it today with this we will ensure that the task will run only once per day.

-

+----+---------+----------+------------+--------+----------+
| iD | timeDay | timeHour | timeMinute | postiD |   today  |
+--------------------------------------+--------+----------+
| 1  | *       | 9        | 0          | 21     | 30-05-04 |
|----|---------|----------|------------|--------|----------+
| 2  | *       | 10       | 30         | 22     |          |
|----|---------|----------|------------|--------|----------+
| 3  | *       | 11       | 0          | 23     |          |
+----|---------+----------+------------+--------+----------+
  • after that create a php file i called it crontask.php we will call it each 5 minutes

  • add this to your cronjob panel:

  • 0,5 * * * * /usr/bin/php /www/virtual/username/crontask.php > /dev/null 2>&1

  • in the crontask.php file

-

<?php
// include() Database Config file here with mysql_connect etc...
// include() the required files ex. the file where myFunction reside...

$cron_cycle = 5; // set it equal to what used in cron command-line
$today = date('Y-m-d');
if($result = mysql_query("SELECT * FROM postDataTime WHERE today != '{$today}'")){
    while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) { 
        $postID = $row['postID'];
        $timeHour = (int) $row['timeHour'];
        $current_hours = (int) date('H'); // current hours
        $current_minutes = (int) date('i'); // current minutes
        $timeMinute = (int) $row['timeMinute'];
        // force to run at the closest cycle
        $timeMinute = ($timeMinute % $cycle === 0) ? $timeMinute : toCloser($timeMinute, $cron_cycle); 
        if( $current_hours === $timeHour && $current_minutes === $timeMinute ){
            // ensure that we have already runned a cron for this user...
            mysql_query("UPDATE postDataTime SET today = '{$today}' WHERE postID = '{$postID}'");
            myFunction($postID);
        }
    }
}
function toCloser($n,$x=5) {
    $j = (round($n)%$x === 0) ? round($n) : (round(($n+$x/2)/$x)*$x);
    return ($j-$n) >= round($x/2) ? ($j-$x) : $j;
}

?>

Explanation of the function:

Assuming that the cron shedule runs each 5 minutes, lat's say we are at 20:00 o'clock, now the cron will run at 20:05, 20:10, 20:15, 20:20 and so on...

then assuming in our DB we have those time

Jonh  : 20:05, 
Mario : 20:32, 
luke  : 20:48, 
David : 20:57, 
Jimmy : 20:06, 
Eddy  : 20:16

when the script checks against those times it will run as below:

at 20:05 -> run 20:05 Jonh, 20:06 Jimmy
at 20:10 -> run null
at 20:15 -> run 20:16 Eddy
at 20:20 -> run null
and so on....

As you see you would loose in the worst case 2 minutes each time. I think it's fair enough! ;)

like image 31
Luca Filosofi Avatar answered Oct 13 '22 22:10

Luca Filosofi


It is possible to set up a cron job that runs every minute and when triggered it checks what jobs are scheduled for that moment.

As a simple idea which could be easily modified to push through the run time details for a particular script if you wanted:-

<?php

include '/core/config.php');

// Test script to allow jobs to be set up (cron style) on a database, but with the addition that jobs can be made
// dependent on other jobs completing first.
// Currently does not support jobs being dependent on more than one parent job.
// It uses a database of 2 tables. One for servers and the other for jobs.
// The server is selected as the one that matches the value of php_uname('n') (hence this can be run on many servers accessing a single database and only executing jobs for the particular server an instance is running on)
// Time ranges are specified in the same way as on CRON jobs:-
//  *   = no restriction based on that field
//  x   = when the value of that time parameter matches x
//  /x  = every x of that field (ie, mod current of that field by x and match if result is 0)
//  x-y = when the value of that time parameter is between x and y
//  x,y = when the value of the time parameter matches x or y (or z, etc)
// The script field on the scheduling table contains the script / command to be executed. For example if a php script then it might be 'php /usr/webdata/cron_scripts/some_script.php
// Parentid is the id of a job that must have finished before the job is executed.

class scheduling extends core_class
{

    public $connections;
    private $db;

    private $year;
    private $month;
    private $day;
    private $hour;
    private $minute;
    private $second;
    private $day_of_week;
    private $background_kick_off = true;

    private $completed_jobs = array();

    function __construct($connections, $background_kick_off = true) 
    {
        parent::__construct($connections);

        $this->background_kick_off = $background_kick_off;

        $this->debug_time_start();

        $this->connections = $connections;
        $this->db = new database($connections['EO'], 'em_scheduling');
        if (!$this->db->no_error)
            $this->error('E_ERROR', $this->db->error());

        $run_date = date('Y/m/d H:i:s w');

        list($date_part, $time_part, $this->day_of_week) = explode(' ', $run_date);
        list($this->year, $this->month, $this->day) = explode('/', $date_part);
        list($this->hour, $this->minute, $this->second) = explode(':', $time_part);     

        $this->find_jobs(0);
    }

    function find_jobs($parent_id)
    {
        $sql = "SELECT a.id, a.script, a.parent_id, a.minutes, a.hours, a.day_of_month, a.months, a.day_of_week, a.script_description, COUNT(DISTINCT b.id) AS child_count
                FROM scheduling a
                ON s.id = a.server_id
                LEFT OUTER JOIN scheduling b
                ON a.id = b.parent_id
                AND b.enabled = 1
                AND (b.minutes = '*' OR FIND_IN_SET('".$this->minute."', b.minutes) OR (SUBSTR(b.minutes, 1, 1) = '/' AND (".$this->minute." % CAST(SUBSTR(b.minutes, 2) AS UNSIGNED)) = 0) OR (b.minutes LIKE '%-%' AND ".$this->minute." BETWEEN CAST(SUBSTRING_INDEX(b.minutes, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(b.minutes, '-', -1) AS UNSIGNED)))
                AND (b.hours = '*' OR FIND_IN_SET('".$this->hour."', b.hours) OR (SUBSTR(b.hours, 1, 1) = '/' AND (".$this->hour." % CAST(SUBSTR(b.hours, 2) AS UNSIGNED)) = 0) OR (b.hours LIKE '%-%' AND ".$this->hour." BETWEEN CAST(SUBSTRING_INDEX(b.hours, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(b.hours, '-', -1) AS UNSIGNED)))
                AND (b.months = '*' OR FIND_IN_SET('".$this->month."', b.months) OR (SUBSTR(b.months, 1, 1) = '/' AND (".$this->month." % CAST(SUBSTR(b.months, 2) AS UNSIGNED)) = 0) OR (b.months LIKE '%-%' AND ".$this->month." BETWEEN CAST(SUBSTRING_INDEX(b.months, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(b.months, '-', -1) AS UNSIGNED)))
                AND ((b.day_of_month = '*' OR FIND_IN_SET('".$this->day."', b.day_of_month) OR (SUBSTR(b.day_of_month, 1, 1) = '/' AND (".$this->day." % CAST(SUBSTR(b.day_of_month, 2) AS UNSIGNED)) = 0) OR (b.day_of_month LIKE '%-%' AND ".$this->day." BETWEEN CAST(SUBSTRING_INDEX(b.day_of_month, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(b.day_of_month, '-', -1) AS UNSIGNED)))
                OR (b.day_of_week = '*' OR FIND_IN_SET('".$this->day_of_week."', b.day_of_week) OR (SUBSTR(b.day_of_week, 1, 1) = '/' AND (".$this->day_of_week." % CAST(SUBSTR(b.day_of_week, 2) AS UNSIGNED)) = 0) OR (b.day_of_week LIKE '%-%' AND ".$this->day_of_week." BETWEEN CAST(SUBSTRING_INDEX(b.day_of_week, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(b.day_of_week, '-', -1) AS UNSIGNED))))
                WHERE a.parent_id = ".(int)$parent_id."
                AND a.enabled = 1
                AND (a.minutes = '*' OR FIND_IN_SET('".$this->minute."', a.minutes) OR (SUBSTR(a.minutes, 1, 1) = '/' AND (".$this->minute." % CAST(SUBSTR(a.minutes, 2) AS UNSIGNED)) = 0) OR (a.minutes LIKE '%-%' AND ".$this->minute." BETWEEN CAST(SUBSTRING_INDEX(a.minutes, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(a.minutes, '-', -1) AS UNSIGNED)))
                AND (a.hours = '*' OR FIND_IN_SET('".$this->hour."', a.hours) OR (SUBSTR(a.hours, 1, 1) = '/' AND (".$this->hour." % CAST(SUBSTR(a.hours, 2) AS UNSIGNED)) = 0) OR (a.hours LIKE '%-%' AND ".$this->hour." BETWEEN CAST(SUBSTRING_INDEX(a.hours, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(a.hours, '-', -1) AS UNSIGNED)))
                AND (a.months = '*' OR FIND_IN_SET('".$this->month."', a.months) OR (SUBSTR(a.months, 1, 1) = '/' AND (".$this->month." % CAST(SUBSTR(a.months, 2) AS UNSIGNED)) = 0) OR (a.months LIKE '%-%' AND ".$this->month." BETWEEN CAST(SUBSTRING_INDEX(a.months, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(a.months, '-', -1) AS UNSIGNED)))
                AND ((a.day_of_month = '*' OR FIND_IN_SET('".$this->day."', a.day_of_month) OR (SUBSTR(a.day_of_month, 1, 1) = '/' AND (".$this->day." % CAST(SUBSTR(a.day_of_month, 2) AS UNSIGNED)) = 0) OR (a.day_of_month LIKE '%-%' AND ".$this->day." BETWEEN CAST(SUBSTRING_INDEX(a.day_of_month, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(a.day_of_month, '-', -1) AS UNSIGNED)))
                OR (a.day_of_week = '*' OR FIND_IN_SET('".$this->day_of_week."', a.day_of_week) OR (SUBSTR(a.day_of_week, 1, 1) = '/' AND (".$this->day_of_week." % CAST(SUBSTR(a.day_of_week, 2) AS UNSIGNED)) = 0) OR (a.day_of_week LIKE '%-%' AND ".$this->day_of_week." BETWEEN CAST(SUBSTRING_INDEX(a.day_of_week, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(a.day_of_week, '-', -1) AS UNSIGNED))))
                GROUP BY a.id, a.script, a.parent_id, a.minutes, a.hours, a.day_of_month, a.months, a.day_of_week
                ORDER BY child_count
                ";

        //echo "\r\n $sql \r\n";

        $this->db->query($sql) or die($this->db->error());

        $process_array = array();

        while ($row = $this->db->fetch_assoc())
        {
            $process_array[] = $row;
        }   

        foreach($process_array as $aProcess)
        {
            if ($this->background_kick_off and $aProcess['child_count'] == 0)
            {
                // No jobs to follow so just kick them off as a background task
                $this->launchBackgroundProcess($aProcess['script']);
                $completed_jobs[$aProcess['id']] = $aProcess['script_description'];
            }
            else
            {
                passthru($aProcess['script'].'', $return_var);
                if ($return_var == 0)
                {
                    $completed_jobs[$aProcess['id']] = $aProcess['script_description'];
                    $this->find_jobs($aProcess['id']);
                }
            }
        }
    }

    private function launchBackgroundProcess($call) 
    {

        // Windows
        if($this->is_windows())
        {
            pclose(popen('start /b '.$call, 'r'));
        }

        // Some sort of UNIX
        else 
        {
            pclose(popen($call.' /dev/null &', 'r'));
        }
        return true;
    }

    private function is_windows()
    {
        if(PHP_OS == 'WINNT' || PHP_OS == 'WIN32')
        {
            return true;
        }
        return false;
    }
}

$Scheduling = new scheduling($connections, true);

?>

Tables like this:-

CREATE TABLE IF NOT EXISTS `scheduling` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `enabled` tinyint(1) NOT NULL DEFAULT '1',
  `script` varchar(255) DEFAULT NULL,
  `parent_id` int(11) DEFAULT NULL,
  `minutes` varchar(5) DEFAULT NULL,
  `hours` varchar(5) DEFAULT NULL,
  `day_of_month` varchar(5) DEFAULT NULL,
  `months` varchar(5) DEFAULT NULL,
  `day_of_week` varchar(5) DEFAULT NULL,
  `script_description` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `parent_id` (`server_id`,`parent_id`,`enabled`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=15 ;

--
-- Dumping data for table `scheduling`
--

INSERT INTO `scheduling` (`id`,  `enabled`, `script`, `parent_id`, `minutes`, `hours`, `day_of_month`, `months`, `day_of_week`, `script_description`) VALUES
(1, 1, 'php download.php', 0, '*', '*', '*', '*', '*', 'Download files'),
(2, 1, 'php load_data.php', 1, '*', '*', '*', '*', '*', 'Load files to database'),
(3, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
(4, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
(5, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
(6, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
(7, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
(8, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
(9, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
(10, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
(11, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
(12, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
(13, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
(14, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL);
like image 22
Kickstart Avatar answered Oct 13 '22 23:10

Kickstart


I am suggesting you create Cron Entries dynamically through a wrapper script which configure the cron entry to run your particular function when you actually wanted it to run.

For your specific case here Below is what I would suggest :

  1. Create a wrapper script And schedule it in Cron to run every Second.
  2. This wrapper script will talk to MySQL and fetch the time at which a specific function is to run.
  3. Then it will Dynamically create a Cron Entry to run that function at that specific retrieved timestamp. You can dynamically add and remove the Cron Entry using the shell script. Please see references below for details.
  4. Once your function is completed, There should be some indication like status stored somewhere maybe in your DB, or some file, so that the wrapper can get/know the status and remove the respective cron entry.

References
1. Create Cron Using Bash
crontab -l | { cat; echo "0 0 0 0 0 some entry"; } | crontab -
2. Delete/Automate Cron
crontab -l -u | grep -v <unique command> | crontab -

like image 37
Suraj Biyani Avatar answered Oct 13 '22 22:10

Suraj Biyani