10

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());
}
iBrazilian2
  • 1,826
  • 4
  • 19
  • 42
  • 1
    In order for something to run every day and relying on PHP and/or SQL to perform this, will require to run a CRON job. It's way easier that way. Otherwise, you will need to run your PHP file yourself, every day and I'm sure you don't want to be doing that. There's also another way, using a trigger with SQL. – Funk Forty Niner May 22 '14 at 02:41
  • @Fred-ii-, I checked for MySQL Event Scheduler but don't think I can rewrite everything to parse the data straight from MySQL to the function. Not even sure I can do that. – iBrazilian2 May 24 '14 at 16:12
  • 1. Please confirm you *are* able to schedule cron jobs on your shared host? 2. What should be the values for `$name` and `$description` when `myFunction()` is started from a cron job? – RandomSeed May 25 '14 at 09:49
  • @RandomSeed, I have edited the question specifically for you. – iBrazilian2 May 25 '14 at 19:09
  • Schedule a cron job .. fetch to be run time and check inside cron script that if its time to run it or not, if yes then do so ;) – Syed Qarib May 26 '14 at 13:46
  • How often/when is the `postDataTime` table updated? Are you allowed to run CLI php? – FuzzyTree May 28 '14 at 01:58
  • @FuzzyTree , at least 5 times a week. No for CLI. – iBrazilian2 May 28 '14 at 15:21
  • @iBrazilian2: i have updated my answer with better code and explanation. – Luca Filosofi Jun 01 '14 at 11:08

6 Answers6

4

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.

Brendan F
  • 559
  • 2
  • 8
  • Well, I can't seem to be able to run daemon on shared hosting. Personally "touching/clicking" a file is what I am trying to get out of so that wouldn't work either. Which is pretty much what I tried to do. – iBrazilian2 May 24 '14 at 14:12
  • "touch" is a linux/unix command which does nothing more than make sure the file exists and updates it's last modified timestamp; I did not mean that you should manually click the file or anything. You can substitute this out for any other way of persistently tracking timestamps such as using another table in your database. The reason I suggest this is because I would not rely on the cron scheduler to run you task at exactly the correct time. – Brendan F May 24 '14 at 16:28
3

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! ;)

Community
  • 1
  • 1
Luca Filosofi
  • 30,086
  • 8
  • 65
  • 74
2

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);
Kickstart
  • 21,106
  • 2
  • 17
  • 32
2

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 -

Community
  • 1
  • 1
Suraj Biyani
  • 161
  • 2
0

with nodeJS, Using node-schedule solved the issue:

start the custom Job:

  const campaignId = "MY_CUSTOM_ID"
  let job = schedule.scheduleJob(campaignId, '* * * * * *', function () {
    console.log('running campaign: ' + campaignId)
  })

stop the custom Job:

  const campaignId = "MY_CUSTOM_ID"
  let current_job = schedule.scheduledJobs[campaignId]
  current_job.cancel()
Alan
  • 4,576
  • 25
  • 43
-2

If I understand correctly, I think something like this may work for you, using the hours as keys to the function you want run, in a cron set to run every two hours:

$listArray = Array(8=>"list1_function",10=>"list2_function");//etc...
$hour = Date("G");

if(array_key_exists($hour,$listArray))
{
    $listArray[$hour]();
}

function list1_function()
{
     echo "do list 1 stuff";
}

function list2_function()
{
     echo "do list 2 stuff";
 }
user3473534
  • 86
  • 10