Monthly Archives: March 2011

PHP Daemons Tutorial

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 "tt--help display this help messagen";
    echo "tt--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 "tt--help display this help messagen";
    echo "tt--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

AJAX And Facebook Like Buttons

Facebook “like” buttons are pretty easy to implement – if you have a straight forward site.  If you are loading your content using AJAX or some other form of delayed load, you might notice a problem if you’re not using Facebook’s iframe.  FBML users have probably noticed that when loading “like” buttons using AJAX that they don’t show up.  There’s any easy fix for this.

First a little background information.  What’s happening is this: Facebook is waiting for the DOM to load and then begins parsing the FBML.  This is all fine an dandy except that when you make an AJAX call, this doesn’t cause the FBML to be re-parsed.  So the fix is simple, make Facebook parse again after you’re AJAX content has been inserted into the DOM.

The method to call is:

FB.XFBML.parse();

If you’re using jQuery, there’s a real easy and slick solution to this problem:

$(document).ajaxComplete(function(){
    try{
        FB.XFBML.parse(); 
    }catch(ex){}
});

This way, all AJAX complete events that occur on the page will have an additional parse called tacked on to the end of them.  That’s all there is to it.  Hopefully this helps someone out there.