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: