/php-async

Framework for asynchronous executing shell command in PHP.

Primary LanguagePHP

PHP asynchronous execution framework
------------------------------------

This framework allows you to execute asynchronously arbitrary shell command from
PHP process. It features the following functionality:
* open file descriptors for communication between the PHP process and the child
  one. Right now we support reading child's STDOUT and STDERR.
* ability to query whether the child process has finished its execution
* query PID of the child process, possibly for sending signals to it
* read exit code of the child process once it has finished its execution
* pre-inclined for easy caching of the child process results

------------------
Examples of usage:
------------------

Most simple asynchronous execution:
-----------------------------------
<?php
$command = 'my-command';
$args = array();
$args[] = array(
  'key' => '--name-of-the-argument',
  'glue' => ' ',
  'value' => 'value of the argument',
);

$child = new ToolsAsyncResult($cmd, $args);

// Your 'my-command --name-of-the-argument "value of the argument"' is being
// executed right now. In the mean time you can do something useful in your main
// PHP process.
do_something_useful();

// When you decide you need the results of asynchronous child process, simply do
// the following. If the command has not finished yet, your main PHP process
// will sleep until the execution is finished.
$result = $child->result();

if ($result['exit'] != 0) {
  // Whoups... The child process did not terminate with exit code 0.
  // Let's see, maybe there is more hints about what went wrong in the STDERR.
  $stderr = $result['stderr'];
}

// Now let's save the STDOUT from the child process somewhere.
$stdout = $result['stdout'];
?>

Run the asynchronous command and query whether it has finished:
---------------------------------------------------------------
<?php
$command = 'my-command';
$args = array();
$args[] = array(
  'key' => '--name-of-the-argument',
  'glue' => ' ',
  'value' => 'value of the argument',
);

$child = new ToolsAsyncResult($cmd, $args);

// While the child process is running, and as we do not want to sleep in the
// main PHP process waiting for the results, let's keep doing something useful.
while ($child->isRunning()) {
  do_something_useful();
}

// Now we fetched the child process results without actually sleeping a second
// in the main PHP process.
$result = $child->result();

?>

Additional file descriptors passed to your asynchronous command:
----------------------------------------------------------------
<?php
$command = 'my-awesome-command';
$args = array();

// Sometimes your command will communicate on more file descriptors than just
// STDOUT & STDERR. So you can provide additional file descriptors to your child
// command, be it a pipe or an actual file.
// See http://php.net/manual/en/function.proc-open.php for full list of
// available options.
$extra_descriptors = array(
  4 => array('pipe', 'w'),
);

$result = new ToolsAsyncResult($cmd, $args, NULL, array(), $extra_descriptors)->result();

// Let's see what our child command has communicated on the 4th file descriptor.
$fd4 = $result['streams'][4];
?>


Example of synchronous caching:
-------------------------------
<?php

/**
 * This function simply encapsulate starting a child process asynchronously.
 *
 * But let's put a bit on top of it. Before we take off to create a child
 * process, let's check if the results of $cmd are not available in cache. If
 * they are available, we return them right away without bothering with the
 * whole thing of asynchronous command.
 * Also, when the asynchronous command finishes execution, we take a note of its
 * result and if it's positive (exit code equals 0) we store it in the cache
 * bin. That way we guarantee asynchronous command will be only initiated for
 * commands that have not been run before.
 */
function do_something_asynchronously($cmd) {
  $cache = cache_bin_get($cmd);
  if ($cache) {
    // ToolsResult class has identical methods as the ToolsAsyncResult does,
    // but this one does not execute any asynchronous command, but simply stores
    // the $cache result until it is requested at some later point. That way we
    // can freely swap between ToolsResult and ToolsAsyncResult classes without
    // modifying the rest of our code.
    return new ToolsResult($cache);
  }
  return new ToolsAsyncResult($cmd, array(), 'my_process_callback', array($cmd));
}

/**
 * This function plays on par with do_something_asynchronously().
 *
 * When the child process has finished its execution, its results will be passed
 * into this process function before being returned to whoever have requested
 * them. We take the opportunity to store the results of asynchronous command
 * execution into our cache bin so next time they can be fetched much faster
 * from there.
 */
function my_process_callback($info, $cmd) {
  if ($info['exit'] == 0) {
    cache_bin_set($cmd, $info);
  }
  // We could also alter what gets returned to whoever requested the results of
  // asynchronous command execution by just returning something else than $info.
  // But let's keep this example simple.
  return $info;
}

$result = do_something_asynchronously('my-command');
// This line takes to execute as long as the asynchronous command takes to
// execute.
$result->result();

// But if we repeat the same thing over again, our results are already cached
// and this time it happens instantaneously. All you have to do, is to implement
// some cache storage bin.
$result = do_something_asynchronously('my-command');
$result->result();
?>

-----------------
Issues/Questions?
-----------------

Do check the php-async.php file to see full options and features provided by the
framework.