PHP daemons: how do you make one?

WHAT ARE PHP DAEMONS, AND HOW DO YOU MAKE ONE?

 

 

PHP is often associated with HTML code generation, but it can also be used as a background application for handling and analysing data. Many popular web applications (like WordPress, osTicket and many others) make use of PHP scripts for background operations like database clean up, email and messages retrieval and statistical data analysis.

How do these kind of scripts work?

 

 

 

Console

 

 

 

 

BACKGROUND AND FOREGROUND SCRIPTS

 

 

This kind of PHP scripts are different from “normal” ones, because they are not executed by the web server when someone visits a webpage. Instead, they are executed from the command line PHP interpreter without interacting with the web server.

Scripts executed from the web server usually output some HTML code, which is read by the web server itself and sent to the remote user browser. PHP scripts that run from the command line do not usually output HTML code, because their purpose is to perform background operations and data analysis instead of interacting with users.

The standard, HTML generating PHP scripts can be seen as “foreground scripts”, as their output is intended to be visible by users. The other kind of scripts can be called “background scripts” instead, underlining the fact that they are usually invisible to users and that they work regardless of HTTP requests.

 

Now you should be wandering: if there is no web server executing these background scripts, who does?

Usually, these scripts need to be executed at regular time intervals, for example once every hour or once a day. In Unix systems this is often done with CRON; if you are not familiar with Unix systems, you can think of CRON as a scheduler for launching applications (including PHP scripts) at specific times, at specific days of the week and so on.

For example, suppose you have a log database table and that you want to periodically delete logs older than one month. To do that, you can write a PHP script that deletes logs older than one month, and then configure CRON (or any other scheduler) to execute that script every day at a specific time.

 

Using a scheduler like CRON, however, has a few drawbacks. What if you need to dynamically change the execution time? What happens if the script is still being executed while the scheduler tries to run it again? And how could you do if you need to share informations between multiple executions of the script?

It is possible to address all this issues even when using CRON, but it’s very difficult and not very portable.

There is another solution: let the script itself iterate and never stop. Such a script is called a PHP daemon.

 

Here is a basic example:

<?php
/* Remove the execution time limit */
set_time_limit(0);
/* Iteration interval in seconds */
$sleep_time = 600;
while (TRUE)
{
   /* Sleep for the iteration interval */
   sleep($sleep_time);
   
   /* Print the time (to the console) */
   echo 'Time: ' . date('H:i:s');
}

 

 

This simple daemon prints the time every 10 minutes (600 seconds). You cannot execute it through a web server, as in that case the server would wait forever (or until a timeout) for the script to terminate. Instead, you need to execute it from the command line PHP interpreter, and every 10 minutes you would see the time printed on the console.

 

In this example you can see the two basic elements of a PHP daemon: an infinite loop (in this case, the while(TRUE) construct) and a sleep statement. The while loop lets the script run indefinitely, and the sleep statement sets the execution interval of the main code (in this case, the time output).

Without the sleep statement, the code would execute continuously using 100% of the CPU all the time, something you definitely do not want.

 

 

 

DAEMON EXAMPLE: AN EMAIL ALERT SYSTEM

 

Email alert

 

 

 

Now let’s see a more realistic example.

Suppose we want to create an alert system. We need to implement a PHP script which will connect to a database and search a table for error messages. To keep things simple, let’s say that every record in this table contains an error message.

For every record found, the script needs to send an email to a specific address using PHPMailer (if you are not familiar with it please take a look at my PHPMailer tutorial).

However, we don’t want to send more than 10 emails per hour, even if there are more errors in the database, so the script must know when this limit is reached.

 

First we need to choose the iteration interval, that is how many seconds we want the sleep statement to pause the script at every iteration. This will determine how much often the script will check for errors and eventually send the alert emails. Let’s say 10 minutes is a good choice.

Then we just need to implement the alert logic.

 

Important: PHP daemons too need to pay attention to SQL security: be sure to read my SQL injection prevention guide to know how to stay safe from injection attacks.

 

Here is the script’s code:

 

<?php
/* Remove the execution time limit */
set_time_limit(0);
/* Include PHPMailer autoloader */
require_once './phpmailer/PHPMailerAutoload.php';
/* Iteration interval in seconds (10 minutes) */
$sleep_time = 600;
/* How many emails we already sent this hour */
$sent_emails = 0;
/* When we sent the last email */
$last_email_time = 0;
while (TRUE)
{
   /* Sleep for the iteration interval */
   sleep($sleep_time);
   
   /* Retrieve errors from the database */
   $errors = get_errors();
   
   /* Send an email for each error */
   foreach ($errors as $error)
   {
      /* If the last email was sent more that 1 hour ago, we reset the counter */
      if (date('YmdH') !== date('YmdH', $last_email_time))
      {
         $sent_emails = 0;
      }
     
     /* We check how many emails we sent this hour */
    if ($sent_emails < 10)
    {
       /* Ok, we can send another one */
       send_error_email($error);
       
       /* Increment the counter and set the last email time */
       $sent_emails++;
       $last_email_time = time();
    }
}
/* Function to retrieve errors from database */
function get_errors()
{
   $errors = array();
   
   try
   {
      /* Connect to database (with PDO) */
      $db = new PDO('mysql:host=localhost;dbname=test', '', '');
      $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
      
      /* We suppose the "errors" table contains the errors we look for */
      $sql = 'SELECT * FROM errors';
      $st = $db->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
      $st->execute();
      $res = $st->fetch(PDO::FETCH_ASSOC);
      /* We add the error to the return array */
      $errors[] = $res;
   }
   catch (PDOException $e)
   {
     /* Exception (SQL error) */
     echo $e->getMessage();
     die();
   }
   
   return $errors;
}
/* Function to send error emails (with PHPMailer) */
function send_error_email($error)
{
   $mail = new PHPMailer();
   $mail->setFrom('darth@empire.com', 'Darth Vader');
   $mail->addAddress('palpatine@empire.com', 'The Emperor');
   $mail->Subject = 'Error alert';
   $mail->Body = 'New error detected: ' . print_r($error, TRUE);
   $mail->send())
}

 

 

The sleep statement pauses the script execution for 10 minutes at every iteration. This time can be changed: for example, if we need to receive the alerts more quickly we can set the iteration interval to 5 minutes or even less. Keep in mind that the lower the iteration interval, the higher the system load becomes.

This example script doesn’t use much system resources, so the iteration time could even be set to 1 minute without problems. When implementing more complex scripts, however, you should be careful in choosing the right iteration time.

 

Notice how we used two “global” variables ($sent_emails and $last_email_time) to avoid sending more than 10 emails per hour. If we used a scheduler (like CRON) to execute a background script, we would need to keep this record on the database or in a file.

 

A good idea is to use a boolean variable for the while loop check, instead of just using TRUE. You can initialize the variable to TRUE, and set it to FALSE when, for some reason, you want the script to terminate.

In another post I will show you how can implement a “control panel” for monitoring the status of multiple PHP daemons and also pausing or stopping them.

 

 

That’s it. If you want some other example or have any questions just leave a comment below.

 As always, I really thank you for reading this post, and if you liked it please take a second to share it!

 

Click the button below to download the example:

36 thoughts on “PHP daemons: how do you make one?”

  1. I am very glad that you posted this tutorial! I have learned a lot from it. Thank you for posting this!

    I was wondering if you were still going to post the howto for the control panel that you mentioned in the tutorial?

    I have written one for my daemons, but TBH, I am not a developer and it is not stable or good code. Knowing how to do something like a control panel well where it can interact with OS pids would be very useful and helpful.

    Is there a chance you would be able to talk about what it would take to make a basic control panel the way you described?

    Reply
  2. Two things worth mentioning:

    1) You can and might should add an signal handler (pcntl), so you can terminate the script gracefully (and maybe clean up things like a PID file, if you created one).
    2) Depending on what your daemon does, you might need to restart it when deploying updated code, or you’ll find yourself in the situation that the daemon is still running on the old code.

    Reply
  3. You really need to pagination or with more than 100 comments its just too much for user to scroll down to submite a comment

    Reply
  4. Hallo thanks very much , With a php application am working on , i need to send email alerts to my clients on every 25th for each month ,

    I need a working example from you on how i can implement this functionality using php deamons,

    Reply
    • The tutorial has the answers, you need to read it. Read it really. If you can’t understand it, you need to practice and make a simple local working copy. If you can’t understand your own code while you write it, then a job change is mandatory. Otherwise, if you need help, I could point you in the right direction, but don’t ask Alex to write code for you… it spends already time for writing such wonderful and easy to follow articles. You could find me on: rivdesign dot dev at gmail dot com
      Have a great coding session and a wonderful day/night.

      Reply
  5. Hi Alex, this is awesome. You have opened my eyes to new possibilities.

    My question is, how do I restart the daemon from the dashboard after it has stopped?

    Also how exactly do I make it begin executing?

    Reply
    • To control the daemon you can see the answer below (“START AND STOP THE DAEMON”).
      Anyway, I see that you have written on my FB group too, so I’ll answer you better there 🙂

      Reply
  6. Great article alex, been having issues with while loops, so i took time to read this wonderful article, my problem is how to see list of daemons running and stop them or pause them, and second i used to initialize my while loop using a form, something like

    isset($_POST[‘submit’]{

    $counter = 0;

    while($counter < 10) {
    echo ' i go';
    Sleep(60);
    }

    This does what i want but it keep running until i turn off my local server and this is not something i can do in a shared hosting.
    So i will love to know how to stop it, anytime i want to adjust something in the function.
    I have tried everything from deleting the code, changing $counter to 10 and Every thing.
    I need further guide please Alex especially regarding stopping it.
    Thanks.

    Reply
    • Hey Chris,

      the solution is to use the database to control the PHP daemon.
      You should create a database table that links each daemon with a set of values, so you can see what the daemon is doing and control it.

      – LIST OF DAEMONS
      Each daemon should have a name, or an ID.
      At every loop, the daemon updates its row on the table setting its status (running, paused, stopped…) and the timestamp of the update.
      This way, you can build a simple dashboard that looks all of the daemons status and the timestamp of their last iteration. From the dashboard, you can see what each daemon is doing.

      – START AND STOP THE DAEMON
      At each loop, each daemon will look in its table searching for a “command” message.
      You can easily set these commands directly from your dashboard.
      The daemon will decide what to do depending on the command. For example:
      – “run”: keep looping and performing the operations.
      – “pause”: keep iterating, but do not perform any operation.
      – “exit”: stop the loop and exit.
      You can add as many commands as you want, and also include more parameters. For example, you can set a command to increase the sleep time to a specific value.

      Let me know if this helps.

      Reply
      • Sure alex this helps in the sense that it gave me a sketch of the operations but frankly speaking i need a guide to it as how to stop the iteration, i can easily have these details inserted in my database but which command line will exist or stop the loop? Is there a php function that can stop the loop like global_loop_exist_function($loopname, exit)

        Reply
        • Thanks alex but am not a Facebook user, i only do whatsapp or email, and if you have a youtube channel where we can go watch the videos i can, please find time to give me sample functions to pause, exist or stop the while loop, i found using while loop better and reliable than cronjobs

        • If what you are saying is what am think then wont it overload my database if am query the database every 10mins.

          This is what am think

          $sql = (” SELECT * tablename WHERE column = daemon_id”);

          while($sql == ‘run’){
          // run my stuffs

          echo ‘my stuff is running’;

          sleep(600);
          }

          So if i want it to stop i will simply update the database table to stop.

          Will this work? and secondly will this not over stress the database?

          Thirdly the pause part is what i dont understand how to do it,

          And i notice using break; exists the code on first run

      • So thankful alex, i followed the link and i got it working, fixed some database error connections in your source code and that place you concatenated the daemon name variable has error too, but i guess it was an omission, am so greatful alex. You are a life saver.

        Reply
  7. “In another post I will show you how can implement a “control panel” for monitoring the status of multiple PHP daemons and also pausing or stopping them.”

    When? I want this post 🙂

    Reply

Leave a Comment