Full View >>

PHP Daemons Tutorial

php-icon

Foreword

The following is a tutorial for creating PHP scripted daemons.  All examples will be performed on CentOS linux.  The daemon code itself will be written in PHP with a BASH script wrapper.

Getting Started

So to get started, let’s create a simple PHP file that will be executed as our daemon. Essentially, we’re going to create your everyday run of the mill command line PHP file.  Then we’ll worry about turning into a daemon a little later.  For this example, let’s create a simple PHP script that put’s text into a log file every second.  So let’s create a file called “Daemon.php” and let’s put the following in it:

#!/usr/bin/php
 
<?php
 
while(true){
    file_put_contents('/var/log/Daemon.log', 'Running...', FILE_APPEND);
    sleep(1);
}//end while
 
?>

Ok, that wasn’t so bad.  The first line tells the interpreter what to execute the file against, in this case we want the file to be interpreted as PHP.  Next we create a simple infinite loop that writes “Running…” to the “/var/log/Daemon.log” file, then sleeps for a second.  Now let’s test it, but first we need to make it executable.

user@computer:$ chmod a+x Daemon.php

Now let’s test it.

user@computer:$ ./Daemon.php

Now that the script is running let’s check the log file.  Open a new terminal and issue the following command to verify the output.

user@computer:$ tail -f /var/log/Daemon.log

If all has gone well you should see live updates that read “Running…Running…Running…”.

Now, let’s enhance the script a little to make it more user friendly.  Let’s add the ability to pass in command line arguments and display a help message.

#!/usr/bin/php
 
<?php
 
$log = '/var/log/Daemon.log';
 
/**
 * Method for displaying the help and default variables.
 **/
function displayUsage(){
    global $log;
 
    echo "\n";
    echo "Process for demonstrating a PHP daemon.\n";
    echo "\n";
    echo "Usage:\n";
    echo "\tDaemon.php [options]\n";
    echo "\n";
    echo "\toptions:\n";
    echo "\t\t--help display this help message\n";
    echo "\t\t--log=<filename> The location of the log file (default '$log')\n";
    echo "\n";
}//end displayUsage()
 
//configure command line arguments
if($argc > 0){
    foreach($argv as $arg){
        $args = explode('=',$arg);
        switch($args[0]){
            case '--help':
                return displayUsage();
            case '--log':
                $log = $args[1];
                break;
        }//end switch
    }//end foreach
}//end if
 
//the main process
while(true){
	file_put_contents($log, 'Running...', FILE_APPEND);
	sleep(1);
}//end while
 
?>

So now we have an elegant way to pass in command line arguments or options to our daemon process along with a nice display usage function which you can use to brand your process with author info, etc.  Now that’s all fine and dandy, but what does that have to do with creating a PHP daemon?  Hold on, we’re getting to that.  Passing in command line flags easily and elegantly will come in hand once we get our daemon up and running.  So now that we have a basic daemon process, let’s actually get it working as a daemon.  To do this we’re going to need a BASH daemon launcher, and we’ll eventually need to put it in the “/etc/init.d” directory.  So now the BASH daemon controller.

BASH Daemon Controller

Let’s start by creating a file called “Daemon” which we’ll use to control our “Daemon.php” file.  So first I’ll give you the entire BASH script, then I’ll explain it.

#!/bin/bash
#
#	/etc/init.d/Daemon
#
# Starts the at daemon
#
# chkconfig: 345 95 5
# description: Runs the demonstration daemon.
# processname: Daemon
 
# Source function library.
. /etc/init.d/functions
 
#startup values
log=/var/log/Daemon.log
 
#verify that the executable exists
test -x /home/godlikemouse/Daemon.php || exit 0RETVAL=0
 
#
#	Set prog, proc and bin variables.
#
prog="Daemon"
proc=/var/lock/subsys/Daemon
bin=/home/godlikemouse/Daemon.php
 
start() {
	# Check if Daemon is already running
	if [ ! -f $proc ]; then
	    echo -n $"Starting $prog: "
	    daemon $bin --log=$log
	    RETVAL=$?
	    [ $RETVAL -eq 0 ] && touch $proc
	    echo
	fi
 
	return $RETVAL
}
 
stop() {
	echo -n $"Stopping $prog: "
	killproc $bin
	RETVAL=$?
	[ $RETVAL -eq 0 ] && rm -f $proc
	echo
        return $RETVAL
}
 
restart() {
	stop
	start
}	
 
reload() {
	restart
}	
 
status_at() {
 	status $bin
}
 
case "$1" in
start)
	start
	;;
stop)
	stop
	;;
reload|restart)
	restart
	;;
condrestart)
        if [ -f $proc ]; then
            restart
        fi
        ;;
status)
	status_at
	;;
*)
 
echo $"Usage: $0 {start|stop|restart|condrestart|status}"
	exit 1
esac
 
exit $?
exit $RETVAL

Ok, so the above BASH script is the daemon controller responsible for starting, stopping, restarting, etc.   Initially it sets itself up for use with chkconfig so that it can be set to start when the OS boots, etc.  Next it includes the basic BASH functions include file.  Afterward we check to see if the executable file actually exists before we try and start it.  Next, we set up the default variables for our program including the proc filename, bin or executable and the program name.  Next, we define some basic default parameters to pass to the PHP file when starting up.  You could also modify this script to read variables from an “/etc/Daemon” configuration file, but for now we’ll just set them directly in the BASH file.  Lastly the PHP file is invoked along with the daemon command.  The rest of the file simple reads the users input to the BASH file and handles start, restart, etc.  Next, let’s make the BASH Daemon file executable so we can use it in just a bit.

user@computer:$ chmod a+x Daemon

PHP Daemon

So now that we have our controller, we’ll need to modify our PHP script to work in a daemon process.  To have our PHP working in a daemon process we’ll need to use fork, that way we can establish a child process which will continually run and return a value to our controller to let it know that we were able to start properly. Here’s the modification to the PHP file.

#!/usr/bin/php
 
<?php
 
$log = '/var/log/Daemon.log';
 
/**
 * Method for displaying the help and default variables.
 **/
function displayUsage(){
    global $log;
 
    echo "\n";
    echo "Process for demonstrating a PHP daemon.\n";
    echo "\n";
    echo "Usage:\n";
    echo "\tDaemon.php [options]\n";
    echo "\n";
    echo "\toptions:\n";
    echo "\t\t--help display this help message\n";
    echo "\t\t--log=<filename> The location of the log file (default '$log')\n";
    echo "\n";
}//end displayUsage()
 
//configure command line arguments
if($argc > 0){
    foreach($argv as $arg){
        $args = explode('=',$arg);
        switch($args[0]){
            case '--help':
                return displayUsage();
            case '--log':
                $log = $args[1];
                break;
        }//end switch
    }//end foreach
}//end if
 
//fork the process to work in a daemonized environment
file_put_contents($log, "Status: starting up.\n", FILE_APPEND);
$pid = pcntl_fork();
if($pid == -1){
	file_put_contents($log, "Error: could not daemonize process.\n", FILE_APPEND);
	return 1; //error
}
else if($pid){
	return 0; //success
}
else{
    //the main process
    while(true){
        file_put_contents($log, 'Running...', FILE_APPEND);
        sleep(1);
    }//end while
}//end if
 
?>

You’ll notice that I’ve added a few more calls to file_put_contents() just to let us know how we’re doing.  Now for the guts of the operation, we call pcntl_fork() to generate a child process and to let the parent caller return a value back to the BASH daemon controller.  The first if determines if the fork call worked at all, if it returns a -1, then it’s a failure, report the error and return a failed status back to the BASH daemon controller.  If $pid contains a valid number, then we’re good to go, but we’re still in the parent process, so we return 0 to let the BASH daemon controller know that all is well in the universe.  The else executes when the child process is created, and this is where the main part of our program executes.

Now, if all has gone well we should be able to start the daemon in normal daemon fashion.  If you’re running this in your /home/{username} directory then execute the following:

user@computer:$ ./Daemon start

You can also copy the Daemon BASH script to the “/etc/init.d/” directory, in which case you can start the daemon using the “service” command:

user@computer:$ service Daemon start

Stopping Daemon:              [ OK ]

Now to verify that everything is working correctly.  First let’s check the log file:

user@computer:$ tail -f /var/log/Daemon.log

Status: starting up.
Running…Running…Running…

Yep, all good there.  Now let’s check our process.

user@computer:$ ps ax | grep Daemon

14886 pts/0    S      0:00 /usr/bin/php /home/godlikemouse/Daemon.php --log=/var/log/Daemon.log
14944 pts/2    R+     0:00 grep Daemon

Ok, the process is running correctly.  Now, let’s stop it and verify again.  Issue the following command:

user@computer:$ ./Daemon stop

or if you copied the controller to the “/etc/init.d” directory

user@computer:$ service Daemon stop

Stopping Daemon:              [ OK ]

Now let’s verify that our process has stopped:

user@computer:$ ps ax | grep Daemon

14997 pts/2    R+     0:00 grep Daemon

Yep, all good…and there you have it.  Now you can write PHP daemons until you turn blue and pass out.  Hopefully this tutorial has been helpful, good luck.

The files used in this tutorial are available for download and use:

Download Project Files

Full View >>

6 Responses to “PHP Daemons Tutorial”

  1. Gene Ellis Says:

    Amazing. Thank you. This helped me solve some really important issues that were plaguing me. One quick question, how can we set a static PID value for the parent process? I want to use a monitoring system like Monit, however monit is based on monitoring PID values and I don’t see a PID file in the above example.

    Thank you for your help!

  2. GodLikeMouse Says:

    One thing you can do is use the getmypid() PHP function to return the current PID. You could kick this value out to a file and read it as input for Monit. As far as I know you can’t actually assign a PID, these are generated by the Kernel at execution time. Hope this helps.

  3. roman Says:

    hm…
    I thought you would tell about forking.
    This way works of course.
    But I think it’s better to avoid changing init scripts.
    May be it would be a good idea to run the daemon with exec() function,
    Then check if exists PID file and if it does. Kill itself if doesnt run its jobs.

    More interesting if the daemon can fork childs.

    I have problems with it

  4. GodLikeMouse Says:

    Hi roman, most daemons traditionally rely on configuration scripts to control execution. You can verify this by looking into the daemon scripts located in /etc/init.d/. The daemon command on linux actually does a fork and detaches the currently executing script/program from the terminal and allows it to run behind the scenes. You could write your own fork to do this, then control the PID, etc…but you’re basically re-inventing the wheel. You might want to have a look at the “daemon” command using man help, could save you quite a bit of time.

  5. Gene Ellis Says:

    Question…Once a PHP Daemon is running, how do we pass variables and execute functions from an external process. For example, if my daemon were taking in a publishing steam for various users, and then a user deactivate their account, I would need to fire certain functions in the already running daemon. How would I get access to those functions in the daemon, from a web form?

  6. GodLikeMouse Says:

    Hi Gene, there are quite a few ways you can do this. You can have the deamon listen to a port and fire commands to it that way. You can also have the daemon listen for changes in a database or watch a flat file and respond to events. I’ve never run into a situation where I needed to call a method of a php daemon directly from another interface, usually I have the daemon responding to events or polling. Hope this helps.

Leave a Reply