#native_company# #native_desc#
#native_cta#

Process to background

By Mike Baynton
on July 16, 2004

Version: 0.1

Type: Class

Category: Other

License: GNU General Public License

Description: This is a PHP 5 class that sends a specified php file, dynamically generated php code, or any system call to a background process on linux/unix servers (a little modification to the forkToStream method would make it run on windows…or maybe it does already, haven’t tested) and returns a filename that will contian that processe’s output. In addition, if you use the forkPHPFile or forkPHPCode methods, the forked script will have access to the output file’s name in its first $argv.

This is helpful for starting scripts or programs that take a long time to execute and that you do not want to have the user wait for. Since all the fork* methods return immediately, php can continue to generate the page while the time-consuming process runs on another thread on the server. It can also start a bunch of little processes going – for instance I wrote it to simultaneously make a bunch of connections to remote systems instead of waiting for it to retieve the data from each, one at a time.

This was written in 1 night and could use some improvement.

Tips:
-pass false to the constructor to have all the output files deleted at the end of your script’s execution.
-If you want to start many processes simultaneously but none of them will take long to finish, you can use the php’s sleep() function to check their output every second until they’re all done.
-Since your php scripts know the name of their output file (its the first command-line argument variable), you can re-write their contents (i.e. 27% turns to 28%…) and then just output the % done whenever the user refreshes (you’d have to store the output filename in a session variable).

<?
class fork {
	private $streams; //array of stream names.
	private $tmpfiles; //array of temporary files we're using

	//pass false to destroy streams at end of this scripts execution. Helpful if this script will be waiting for its children to report in.
	//otherwise it's your responsibility to delete the stream whenever you get done with it.
	public function __construct($persist = true){
		if(! $persist)	register_shutdown_function(array($this, "cleanup"));
	}

	private function forkToStream($command, $stream){
		//echo "$command > $stream &";
		system("$command > $stream &");
		return true;
	}

	//just pass this method any system command.
	public function forkCommand($command){
		$s = $this->newStream();
		if($s) {
			$this->forkToStream($command, $s);
			return $s;
		} else return false;
	}

	//pass complete php code including start and end tags
	public function forkPHPCode($code){
		$s = tempnam("", "phpCode_");
		if($s) {
			$this->tmpfiles[] = $s;
			file_put_contents($s, $code);
			$this->forkPHPFile($s);
		} else return false;
	}

	//pass the name of a php script
	public function forkPHPFile($filename){
		$s = $this->newStream();
		if($s) {
			$this->forkToStream("php "$filename" "$s"", $s);
			return $s;
		} else return false;
	}

	//returns all the output filenames from your processes in an array.
	public function getStreamArray(){
		return $this->streams;
	}

	//returns a specific filename from the array. 0-based, in order of creation.
	public function getStream($index){
		return $this->streams[$index];
	}

	public function getNumStreams(){
		return count($this->streams);
	}

	private function registerStream($tname){
		if(! touch($tname)){
			throw new Exception("Could not create output stream for fork at "$tname". Check permissions and temporary dir in php.ini.");
			return false;
		} else {
			$this->streams[] = $tname;
			$this->tmpfiles[] = $tname;
			return true;
		}
	}

	private function dropStream($tname){
		$this->array_remove($this->streams, $tname);
		if(unlink($tname)){
			if(! $this->array_remove($this->tmpfiles, $tname))
				trigger_error("Internal stream tracker is out of sync with reality.");
		} else {
			trigger_error("Stream "$tname" could not be destroyed. If the stream exists, consider deleting manually.");
		}
	}

	private function array_remove($array, $value){
		$x = array_search($value, $array);
		if($x === false){
			return false;
		} else {
			unset($array[$x]);
			return true;
		}
	}

	private function newStream(){
		//acquire a new output stream
		$tname = tempnam("", "phpForkStream"); //create stream in system's temp directory.
		if($this->registerStream($tname)){
			if(is_readable($tname) && is_writable($tname)){
				return $tname;
			} else {
				throw new Exception("Created stream "$tname", but cannot read and write to it. Check umask. Stream will be deleted.");
				dropStream($tname);
				//TODO: update with chmod
				return false;
			}
		} else {
			trigger_error("could not create or register stream for fork.");
		}
	}

	public function cleanup(){ //must be declared public for php to run it.
		echo "Shutting down...destroying all fork streams.";
		for($i = 0; $i < count($this->tmpfiles); $i++){
			$this->dropStream($this->tmpfiles[$i]);
		}
	}
}
?>