Handling Timezone Offset

Suppose you have a PHP/MySQL app with clients in many timezones. Each client has a timezone config set. Your tables have an updated_at timestamp field which is maintained in the clients time, and you want to display that in a report. You want to make sure that the field displays properly in the user’s browser, taking into account that the user may not be in the same timezone as the configured value.

Another option is to maintain updated_at in GMT, but in our scenario you inherited the data situation as it is.

config.php

<?php
  $time_zone = 'America/Mexico_City';

Any time you do a save you do something like:

baseModel.php

<?php
  public function save() {
    ...
    $this->updated_at = date('Y-m-d H:i:s', time());
    ...

Now when you pull data for the report you want to do the following:

someReport.php

<?php
  ...
  $tzo = getTzOffset($time_zone); // time zone offset string for the site
  ...
  $columns['updated_at'] => array(
    'select' => 'DATE_FORMAT(pm.updated_at, "%Y/%m/%d %H:%i:%s ' . $tzo . '")',
    'type' => 'datetime',
    'title' => 'Updated At'
  );

And in a helper somewhere you need to return the offset string, such as ‘-5:00′:

helper.php

<?php
  function getTzOffset($time_zone)
  {
    $origin_dtz = new DateTimeZone($time_zone);
    $secs = $origin_dtz->getOffset(new DateTime);
    $gmd = gmdate('G:i', abs($secs));
    $sign = $secs < 0 ? '-' : '+';
    return $sign.$gmd;
  }

Now, in the json results from the query you get a value like:

  {updated_at:2014/10/01 21:27:09 -5:00, ...}

And in the javascript that handles formatting the data for the report you want:

formatter.js

  ...
  switch(format) {
    case 'datetime':
      var y = new Date(value);
      new_result[key] = y;
      break;
  ...

CSV Exports for Excel for Mac

Exporting a UTF-8 CSV readable by Excel for Mac as well as other popular spreadsheets proved a challenge. Using the UTF-8 BOM with comma delimiter and UTF data worked for everything except Excel for Mac. The solution was found by reading stackexchange — convert to UTF-16LE, UTF-16LE data and delimit with a tab.

Here is some PHP code for that:

  $data = array(
    array('空手', '家族'),
    array('My name is', 'John'),
    array('नमस्ते', '四')
  );

  $file = 'test.csv';
  $fp = fopen($file, 'w');
  $utf16_representation_BOM = b"\xFF\xFE";
  fprintf($fp, $utf16_representation_BOM);
  foreach ($data as $row)
  {
    putCsvRow($fp, $row);
  }
  fclose($fp);

  // can't use fputcsv; must convert the entire line to UTF-16LE
  function putCsvRow($f, $data)
  {
    // item by item, decide if we need quotes or escaping
    if (!empty($data))
    {
      // replace " with ""
      $to_escape = array('"');
      $escaped = array('""');

      // enclose anything containing whitespace, comma or quote
      $things_that_require_quotes = '/[\s,"]/';

      $row = array();
      foreach ($data as $item)
      {
        $item = str_replace($to_escape, $escaped, $item);
        $needs_quotes = preg_match($things_that_require_quotes, $item);
        if ($needs_quotes)
        {
          $item = '"' . $item . '"';
        }
        $row[] = $item;
      }
    }
    else
    {
      $row = array();
    }
    $line = implode("\t", $row) . PHP_EOL;
    $cv_row = mb_convert_encoding($line, 'UTF-16LE', 'UTF-8');
    fwrite($f, $cv_row);
  }

Now the file will be viewable as expected even in Excel for Mac. Horrible workaround? Yes. Excel for Mac should handle the more standard UTF-8.

Parallel Computing in PHP and Pythagorean Triples

Parallel Computing in PHP and Pythagorean Triples

This is from a training I recently did. We often have the need to spawn tasks when a user requests something, and in the case that several requests happen at once, or there is a complicated request involving various tasks that could be run at the same time, we like to process them in parallel when possible.

Though the main idea of this training is parallel computing in PHP, that’s a rather dry subject, so let’s mix it up with something everybody loves – MATH! We’ll be pitting different algorithms for finding Pythagorean Triples against one another in a sort of Fight to the Death!

What is Parallel Computing?

  • Being able to run stuff “at the same time”…
  • Not quite the same as multithreading, in which one process runs a bunch of threads in shared memory. Parallel computing is a broader term which can mean running different processes simultaneously that need to use pipes or sockets should they wish to  communicate with one another.
  • Although we will be running processes in parallel, avoid the term “parallel processing” here as that refers to something else.

What are Pythagorean Triples?

Nontrivial Integer solutions to a2 + b2 = c2. (3,4,5) is the classic example, since 32 + 42 = 52. (6,8,10) is another, but we don’t like it since it’s just a multiple of (3,4,5). (4,3,5) is also one, but it’s really the same as (3,4,5), so we don’t like it either. Let’s look at how to generate Pythagorean Triples (PTs) that we like using a few algorithms, then we’ll compute in parallel to see what each algorithm finds.

Fibonacci’s Algorithm

Let S = 1, 3, 5, 7, 9,11,…

and consider the sequence of partial sums T

T = 1, 1+3, 1+3+5, 1+3+5+7, …

T = 1, 4, 9, 16, 25, 36…

So T just consists of the perfect squares. For any perfect square in S, we can generate a PT as follows:

Fibonacci Algorithm

  • Let x = Sk (the kth member of S) be a perfect square.
  • Tk-1 = the sum of the elements in S up to x
  • Tk = the sum of the elements in S up to and including x.
  • All 3 are perfect squares and
  • x + Tk-1 = Tk

So let’s write some code

Let’s store this in a MySQL table:

CREATE TABLE `pythag` (
`a` int(10) unsigned NOT NULL,
`b` int(10) unsigned NOT NULL,
`c` int(10) unsigned NOT NULL,
`found_by` varchar(25) NOT NULL,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`a`,`b`,`c`));

The base class

class tripleFinder

public $a, $b, $c, $name;

functions:

gcd3 calculates the gcd of 3 numbers. Uses gcd to do this

findTriples calls the algorithm for each child class

checkTriple discards unwanted triples

getFactors returns the factor pairs of a number, used in the other algorithms

if we find a triple we like, we’ll use INSERT IGNORE to attempt to put it into our table.

tripleFinder.php

<?php  

/*
 * Base class for Pythagorean Triple finding algorithms
 */

require_once('config.php');
require_once('log.php');

class tripleFinder
{
  public 
    // the sides of the triangle
    $a, $b, $c,

    // the name of the algorithm
    $name;

  protected
    $logfile = NULL,
    $connection = NULL,

    // the number of iterations to run in the finder loop
    $max_tries = 0, 

    // how many seconds to wait before beginning, for leveling the playing field
    $wait = 0,

    // truncate the table before beginning
    $truncate = FALSE;

  public function __construct($params)
  {
    if (isset($params['max_tries']))
      $this->max_tries = $params['max_tries'];
    if (isset($params['wait']))
      $this->wait = $params['wait'];
    if (isset($params['truncate']))
      $this->truncate = $params['truncate'];
    $this->connect();
  }

  public function execute()
  {
    if ($this->truncate)
    {
      print "truncating\n";
      $this->truncate();
    }
    print "sleeping ".$this->wait." s\n";
    sleep($this->wait);

    print "executing\n";
    $this->findTriples();

    $this->report();
  }

  protected function findTriples()
  {
    // main algorithm for child class
  }

  // discard triples with a common factor or that don't satisfy the equation; 
  public function checkTriple($a,$b,$c)
  {
    log::write("check triple $a $b $c");
    if (!is_int($a) || !$a)
      return FALSE;
    if (!is_int($b) || !$b)
      return FALSE;
    if (!is_int($c) || !$c)
      return FALSE;
    if ($this->gcd3($a,$b,$c) > 1)
    {
      log::write("triple $a $b $c has common factor");
      return FALSE;
    }
    $arr = array($a, $b, $c);
    sort($arr);
    $a = $arr[0];
    $b = $arr[1];
    $c = $arr[2];
    if ($a * $a + $b * $b != $c * $c)
    {
      log::write("triple $a $b $c does not work");
      return FALSE;
    }
    return TRUE;
  }

  // find the greatest common factor of three numbers
  protected function gcd3($n, $m, $p)
  {
    $x = $this->gcd($n, $m);
    $y = $this->gcd($n, $p);
    return $this->gcd($x, $y); 
  }

  // modified  common factor binary algorithm --> $m == $n == 0 case removed
  protected function gcd($n, $m)
  {
    if ($n == $m) 
      return $n; 
    if ($n == 0) 
      return $m; 
    if ($m == 0) 
      return $n; 
    if (!($n & 1)) 
    {
      if ($m & 1) 
        return $this->gcd($n/2, $m);
      return 2*$this->gcd($n/2, $m/2);
    }
    if (!($m & 1)) 
    {
      return $this->gcd($n, $m/2);
    }
    return ($m < $n) ? $this->gcd(($n-$m)/2,$n) : $this->gcd($n,($m-$n)/2); 
  }

  // put the sides in order and attempt to insert the record
  public function insertTriple($a, $b, $c)
  {
    $now = date('Y-m-d H:i:s', time());

    log::write("triple $a,$b,$c");

    $arr = array($a, $b, $c);
    sort($arr);
    $this->a = $arr[0];
    $this->b = $arr[1];
    $this->c = $arr[2];

    $query = '
    INSERT IGNORE pythag (a,b,c,found_by, created_at) VALUES
    ('.$this->a.','.$this->b.','.$this->c.',"'.$this->name.'","'. $now .'")';

    $this->exec($query);
  }

  // return an array of all tuples (m, n) such that m * n = p, and m <= n
  public function getFactors($p)
  {
    $factorSets = array();
    for ($m = 1; $m <= sqrt($p); $m++)     
    {       
      if ($p % $m == 0)       
      {
         $n = $p / $m;
         $factorSets[] = array($m, $n);
       }
     }     
    return $factorSets;
  }

  public function truncate()
  {
     $query = 'TRUNCATE TABLE pythag';
     $this->exec($query);
  }

  public function report()
  {
    $query = 'SELECT count(1) AS count, found_by
    FROM pythag 
    WHERE found_by = "'. $this->name .'"';

    $stmt = $this->connection->prepare($query);
    $stmt->execute();
    $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
    log::write(print_r($results,1), 1);
  }

  protected function connect()
  {
    try
    {
      $connection = new PDO('mysql:host='.HOST.';dbname='.DATABASE, USER, PASS);
    }
    catch (Exception $e)
    {
      print $e->getMessage();
    }

    $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $this->connection = $connection;
  }

  protected function exec($query, $log = TRUE)
  {
    if (!$this->connection)
      $this->connect();
    $connection = $this->connection;
    if ($log)
    {
      log::write($query);
    }
    try
    {
      $connection->exec($query);  
    }
    catch (Exception $e)
    {
      print $e->getMessage();
    }
  }
}

fibonacciTripleFinder.php

<?php 

require_once('tripleFinder.php');

class fibonacciTripleFinder extends tripleFinder
{
  public $name = 'Fibonacci';

  protected function findTriples()
  {
    log::write("finding $this->name triples");
    $number = 9;
    $delta = 8;
    $tries = 0;
    $max_tries = $this->max_tries;
    while ($tries++ < $max_tries)     
    {       
      log::write("finding $this->name triple given $number");
      $sum_odds_previous = $this->sqrtSumOddsPrevious($number);
      $sum_odds_current = $this->sqrtSumOddsCurrent($number);

      $a = (int)sqrt($number);
      $b = $sum_odds_previous;
      $c = $sum_odds_current;
      if ($this->checkTriple($a,$b,$c))
      {
        $this->insertTriple($a, $b, $c);
      }
      $delta = $delta + 8;
      $number += $delta;
    }
  }

  protected function sqrtSumOddsPrevious($number)
  {
    return ((($number + 1) / 2) - 1);
  }

  protected function sqrtSumOddsCurrent($number)
  {
    return (($number + 1) / 2);
  }
}

$max_tries = isset($argv[1]) ? $argv[1] : (isset($_GET['max_tries']) ? $_GET['max_tries'] : 10);
$wait = isset($argv[2]) ? $argv[2] : (isset($_GET['wait']) ? $_GET['wait'] : 0);
$truncate = isset($argv[3]) ? $argv[3] : (isset($_GET['truncate']) ? $_GET['truncate'] : 0);
$params = array('max_tries'=>$max_tries, 'wait'=>$wait, 'truncate'=>$truncate);
$x = new fibonacciTripleFinder($params);
$x->execute();

You’ll also need config.php and log.php:

config.php

<?php
  define('HOST', 'localhost');
  define('USER', 'xxx');
  define('PASS', 'xxx');
  define('DATABASE', 'xxx');
  define('LOG_FILE', 'pythag.log');

log.php

<?php
class log
{
  public static
    $log = NULL;

  public static function initializeLogFile()
  {
    try
    {
      $filepath = LOG_FILE;
      $logfile = __DIR__.'/logs/'.$filepath;
      self::$log = fopen($logfile, 'a');
    }
    catch(Exception $e)
    {
      error_log("ERROR: ". $e);
      exit;
    }
  }

  public static function write($message, $to_ui = FALSE)
  {
    $now = date('Y-m-d H:i:s', time());
    $message = $now.': '.$message.PHP_EOL;

    if (!self::$log)
    {
      self::initializeLogFile();
    }

    fwrite(self::$log, $message);

    // Output to the screen
    if ($to_ui)
    {
      echo $message ."<br/>";
    }
  }
}

Try It

See if it works — the command below runs 100 iterations after truncating the table:

php fibonacciTripleFinder.php 100 0 1

Euclid’s Algorithm

Start with any even integer b, and find all (m,n) such that b = 2mn. Then

  • a = (m2-n2), b = 2mn, c = (m2 + n2) is a PT.
  • Example: Let b = 24
  • 24 = 2mn so that 12 = mn. The factor pairs (m,n) of 12 are (12,1), (6,2) and (4,3).
  • The three possible triples are therefore:
  • a = 12- 12 = 14, b = 24,  c = 122 + 12 = 145
  • a = 6- 22 = 32, b = 24,  c = 62 + 22 = 40
  • a = 4- 32 = 7, b = 24,  c = 42 + 32 = 25

euclidTripleFinder.php

<?php 

require_once('tripleFinder.php');

class euclidTripleFinder extends tripleFinder
{
  public $name = 'Euclid';

  protected function findTriples()
  {
    log::write("finding $this->name triples");
    $side = 2;
    $tries = 0;
    $max_tries = $this->max_tries;
    while ($tries++ < $max_tries)
    {
       $this->findTriplesGivenSide($side);
       $side += 2;
    }
  }

  protected function findTriplesGivenSide($side)
  { 
    log::write("find triple for $side"); 
    $b = $side; 
    $factorSets = $this->getFactors($b/2);
    foreach ($factorSets as $factors)
    {
      $m = $factors[0];
      $n = $factors[1];
      log::write("factors $m $n");
      $a = abs($m*$m - $n*$n);
      $c = $m*$m + $n*$n;
      if ($this->checkTriple($a,$b,$c))
      {
        $this->insertTriple($a, $b, $c);
      }
    }
  }
}

$max_tries = isset($argv[1]) ? $argv[1] : ($_GET['max_tries'] ? $_GET['max_tries'] : 10);
$wait = isset($argv[2]) ? $argv[2] : (isset($_GET['wait']) ? $_GET['wait'] : 0);
$truncate = isset($argv[3]) ? $argv[3] : (isset($_GET['truncate']) ? $_GET['truncate'] : 0);
$arr = array('max_tries'=>$max_tries, 'wait'=>$wait, 'truncate'=>$truncate);
$x = new euclidTripleFinder($arr);
$x->execute();

dicksonTripleFinder.php

<?php 

require_once('tripleFinder.php');

class dicksonTripleFinder extends tripleFinder
{
  public $name = 'Dickson',
    $biggest_to_smallest = FALSE;

  public function __construct($params)
  {
    log::write('constructing '.__CLASS__);

    if (isset($params['biggest_to_smallest']) && $params['biggest_to_smallest'])
      $this->biggest_to_smallest = TRUE;

    parent::__construct($params);
  }

  protected function findTriples()
  {
    log::write("finding $this->name triples");
    $tries = 0;
    $max_tries = $this->max_tries;
    $number = $this->biggest_to_smallest ? (2 * $max_tries) : 2;
    while ($tries++ < $max_tries)
    {
      $this->findTriplesGivenSide($number);
      $number = $this->biggest_to_smallest ? ($number - 2) : ($number + 2);
    }
  }

  protected function findTriplesGivenSide($number)
  {
    log::write("find triple for $number");
    $b = $number;
    $factorSets = $this->getFactors(($number * $number)/2);
    foreach ($factorSets as $factors)
    {
      $m = $factors[0];
      $n = $factors[1];
      log::write("factors $m $n");
      $a = $number + $m;
      $b = $number + $n;
      $c = $a + $n;
      if ($this->checkTriple($a,$b,$c))
      {
        $this->insertTriple($a, $b, $c);
      }
    }
  }
}

$max_tries = isset($argv[1]) ? $argv[1] : ($_GET['max_tries'] ? $_GET['max_tries'] : 10);
$wait = isset($argv[2]) ? $argv[2] : (isset($_GET['wait']) ? $_GET['wait'] : 0);
$truncate = isset($argv[3]) ? $argv[3] : (isset($_GET['truncate']) ? $_GET['truncate'] : 0);
$biggest_to_smallest = isset($argv[4]) ? $argv[4] : (isset($_GET['biggest_to_smallest']) ? $_GET['biggest_to_smallest'] : 0);
$arr = array('max_tries'=>$max_tries, 'wait'=>$wait, 'truncate'=>$truncate, 'biggest_to_smallest'=>$biggest_to_smallest);
$x = new dicksonTripleFinder($arr);
$x->execute();

Finally,  Parallel Computing

Some Options in PHP:

  • popen
    • spawn a new process. fire and forget.
    • resource popen ( string $command , string $mode )
    • returns a unidirectional (either reading or writing) file pointer
    • $mode is ‘r’ or ‘w’
    • close with pclose()
    • in standard PHP distribution
    • use proc_open() for more control
  • curl, fsockopen, fopen
    • like popen, a bit harder to use perhaps, internet or socket connection
    • takes a $hostname and optional arguments
    • returns a file pointer, closed with fclose
    • blocking by default, but that can be overridden
  • pctnl_fork
    • pcntl extension. spawn a clone of the parent process.
    • makes a copy of the process that called it, but parent and child go their separate ways
    • returns the PID of the spawned process
    • in the pcntl extension (process control)
    • PHP’s documentation – “Process Control should not be enabled within a web server environment and unexpected results may happen if any Process Control functions are used within a web server environment.”
  • pthreads
    • a pecl extension. not a good option in a web server environment. but sure looks like it would be fun to play with….

Let’s Run the 3 Algorithms Using popen

OK, so here’s the idea — we want to run these three algorithms at the same time and see who finds the most PTs. I will not argue that there are no bugs in the code and that the algorithms are optimal. Perhaps with your comments the code can be improved to give better results. But as they are, let’s run them at the same time with a php script using popen:

popenParallelComputer.php

<?php
require_once('config.php');
require_once('log.php');

class popenParallelComputer 
{
  protected function execute()
  {
    $this->spawnTask('php fibonacciTripleFinder.php 1000 1 1');
    $this->spawnTask('php dicksonTripleFinder.php 1000 1');
    $this->spawnTask('php euclidTripleFinder.php 1000 1');
  }

  protected function spawnTask($task)
  {
    log::write('processing task '. $task);

        $handle = popen($task .' &', 'w');
        pclose($handle);
  }

  public static function autoExecute($parameters)
  {
    $task = new popenParallelComputer($parameters);
    $task->execute();
  } 
}

$parameters = array();
popenParallelComputer::autoExecute($parameters);

Run it:

php popenParallelComputer.php

I put a 1 second delay on each process since we are truncating the table at the start. BTW, obviously, the truncate method does not belong in the algorithm class, but I’m lazy, and this is just a tutorial…

I get that Fibonacci found 998, Euclid 1429 and Dickson 989.

socketsParallelComputer.php

<?php

require_once('config.php');
require_once('log.php');

class socketsParallelComputer 
{
  public function __construct($params)
  {
    log::write('socketsParallelComputer');
  } 

  protected function execute()
  {
    $fp1 = $this->spawnProcess('localhost','/pythag/dicksonTripleFinder.php?max_tries=1000');
    $fp2 = $this->spawnProcess('localhost','/pythag/euclidTripleFinder.php?max_tries=1000');
    $fp3 = $this->spawnProcess('localhost','/pythag/fibonacciTripleFinder.php?max_tries=10000');
    while (true) {
      sleep(1);

      $r1 = $this->pollProcess($fp1);
      $r2 = $this->pollProcess($fp2);
      $r3 = $this->pollProcess($fp3);

      if ($r1 === false && $r2 === false && $r3 === false) 
        break;
      flush(); @ob_flush();
    }
  }

  protected function spawnProcess($server, $url, $port=80,$conn_timeout=30, $rw_timeout=86400)
  {
    $errno = '';
    $errstr = '';

    log::write('processing task '. $url);

    set_time_limit(0);

    $fp = fsockopen($server, $port, $errno, $errstr, $conn_timeout);

    if (!$fp) {
       echo "$errstr ($errno)<br />\n";
       return false;
    }
    $out = "GET $url HTTP/1.1\r\n";
    $out .= "Host: $server\r\n";
    $out .= "Connection: Close\r\n\r\n";

    stream_set_blocking($fp, false);
    stream_set_timeout($fp, $rw_timeout);
    fwrite($fp, $out);

    return $fp;
  }

  // returns false if HTTP disconnect (EOF), or a string (could be empty string) if still connected
  protected function pollProcess(&$fp) 
  {
    if ($fp === false) return false;

    if (feof($fp)) {
      fclose($fp);
      $fp = false;
      return false;
    }

    return fread($fp, 10000);
  }

  public static function autoExecute($parameters)
  {
    $task = new socketsParallelComputer($parameters);
    $task->execute();
  } 
}

$parameters = array();
socketsParallelComputer::autoExecute($parameters);

If you look at the code you can see I have these files in the pythag directory at the root level of my web server. For some reason they don’t write to the log, but they insert into the db. Bet it’s something silly I overlooked…

forkParallelComputer.php

<?php
require_once('config.php');
require_once('log.php');
declare(ticks=1);

class forkParallelComputer
{
  protected
    $tasks_running = 0,
    $max_tasks = 3,
    $parentPID = NULL,
    $current_jobs = array();

  public function __construct($parameters = array())
  {
    if (!function_exists('pcntl_fork')) 
      die('PCNTL functions are not available on this PHP installation'); 

    log::write('constructing mother forker');
    $this->parentPID = getmypid(); 
    if (pcntl_signal(SIGCHLD, array($this, "childSignalHandler")))
    {
      log::write('signal handler started'); 
    }
    else 
    {
      log::write('problem with the signal handler');
      throw new Exception("Signal Handler Error");
    }
  }

  public function execute()
  {
    log::write('STARTING PCNTL_FORK EXAMPLE');

    $this->processEntries();

    log::write('FINISHED PCNTL_FORK EXAMPLE');
  }

  protected function processEntries()
  {
    $entries = array(
      'php fibonacciTripleFinder.php 10000 2 1',
      'php euclidTripleFinder.php 1000 1',
      'php dicksonTripleFinder.php 1000',
    );

    foreach ($entries as $entry)
    {
      while(count($this->current_jobs) >= $this->max_tasks)
      { 
         log::write("Maximum children allowed, waiting..."); 
         sleep(1); 
      } 

      $this->launchProcess($entry);

    }
    while(count($this->current_jobs))
    { 
      log::write("Waiting for current jobs to finish... "); 
      log::write("current jobs are ".print_r($this->current_jobs,1));
      sleep(3); 
    }
  }

  protected function launchProcess($entry)
  {
    $pid = pcntl_fork(); 
    log::write("launch job $entry");
    log::write("pid $pid");
    if($pid == -1)
    { 
      //Problem launching the job 
      log::write('Could not launch new job, exiting'); 
      return false; 
    } 
    else if ($pid)
    { 
      // Parent process 
      $this->current_jobs[$pid] = $entry; 
      log::write("added $pid to current jobs");
      log::write("current jobs ".print_r($this->current_jobs,1));
    } 
    else{ 
      // Forked child
      $exitStatus = 0;
      log::write("Child task pid ".getmypid());
      exec($entry);
      exit($exitStatus); 
    } 
    return true; 
  }

  protected function childSignalHandler($signo, $pid=null, $status=null)
  { 
       //Let's figure out which child process ended 
    if(!$pid){ 
      $pid = pcntl_waitpid(-1, $status, WNOHANG); 
    } 

    //Make sure we get all of the exited children 
    while($pid > 0){ 
      if($pid && isset($this->current_jobs[$pid])){ 
        log::write("$pid has left the building");
        unset($this->current_jobs[$pid]); 
      } 
      $pid = pcntl_waitpid(-1, $status, WNOHANG); 
    } 
    return true; 
  } 

  public static function autoExecute($parameters = array())
  {
    $this_guy = new forkParallelComputer($parameters);
    $this_guy->execute();
  }
}

$parameters = array();
forkParallelComputer::autoExecute($parameters);