Location: PHPKode > scripts > Process > process.php
<?php

/**
 * Process.
 * 
 * The Process class is a cross-platform OOP wrapper for the PHP's built-in
 * program execution functions (proc_* and the like).
 * 
 * The constructor creates a native process, which is represented by a
 * Process object. This object can then be used to control the process and
 * obtain information about it.
 * 
 * The subprocess is not killed when there are no more references to the
 * Process object.
 * 
 * Aimed at PHP 5.2.0 or greater.
 * 
 * @author Jaka Jancar [hide@address.com] [http://jaka.kubje.org/]
 * @link http://jaka.kubje.org/projects/process/
 * @version 1.0
 */
class Process {
    /**
     * Process resurce.
     * 
     * @var resource
     */
    private $process;

    /**
     * Standard input stream of the process.
     *
     * @var resource
     */
    private $stdin;

    /**
     * Standard output stream of the process.
     *
     * @var resource
     */
    private $stdout;

    /**
     * Standard error stream of the process.
     *
     * @var resource
     */
    private $stderr;

    // The following fields are updated by updateStatus()

    /**
     * Process id
     *
     * @var int
     */
    private $pid;

    /**
     * The last known state of the process.
     *
     * @var bool
     */
    private $isRunning;

    /**
     * Exit code of the process if it has terminated.
     * 
     * @var int
     */
    private $exitCode;

    /**
     * Starts a new process.
     *
     * Possible options:
     *  - no_capture_stderr (bool)
     *      Do not capture stderr. Default: false.
     *  - proc_open_options (array)
     *      The array of options to pass to proc_open(). default: array()
     * 
     * @param string $cmd
     * @param string|NULL $cwd
     * @param array|NULL $env
     * @param array $options
     */
    public function __construct($cmd, $cwd=NULL, $env=NULL, $options=array()) {
        $descriptorspec = array(
            0 => array('pipe', 'r'),
            1 => array('pipe', 'w'),
        );

        $captureStderr = (isset($options['no_capture_stderr']) and ($options['no_capture_stderr'] === true)) ? false : true;

        if ($captureStderr)
            $descriptorspec[2] = array('pipe', 'w');

        $procOpenOptions = isset($options['proc_open_options']) ? $options['proc_open_options'] : array();

        $this->process = proc_open($cmd, $descriptorspec, $pipes, $cwd, $env, $procOpenOptions);

        if ($this->process === false)
            throw new Exception("Error while opening the process.");

        if (!is_resource($this->process))
            throw new Exception("Return value of proc_open is not a resource.");

        $this->isRunning = true;

        $this->stdin = $pipes[0];
        $this->stdout = $pipes[1];
        $this->stderr = $captureStderr ? $pipes[2] : NULL;
    }

    /**
     * Gets the input stream of the process.
     *
     * @return resource
     */
    public function getInputStream() {
        return $this->stdin;
    }

    /**
     * Gets the output stream of the process.
     *
     * @return resource
     */
    public function getOutputStream() {
        return $this->stdout;
    }

    /**
     * Gets the error stream of the process.
     *
     * @return resource
     */
    public function getErrorStream() {
        return $this->stderr;
    }

    /**
     * Returns the PID of the process.
     *
     * @return int
     */
    public function getPid() {
        $this->updateStatus();

        return $this->pid;
    }

    /**
     * Returns the status of the process.
     *
     * If the process is still running, true is returned. If the process has
     * already terminated, false is returned.
     * 
     * @return bool
     */
    public function isRunning() {
        $this->updateStatus();

        return $this->isRunning;
    }

    /**
     * Wait, if necessary, until the process has terminated.
     * 
     * This method returns immediately if the subprocess has already
     * terminated.
     * 
     * If the subprocess has not yet terminated, the call will block until
     * the subprocess exits.
     * 
     * Returns the exit code returned by the process.
     * 
     * @return int
     */
    public function close() {
        $this->updateStatus();

        if (is_null($this->process))
            return $this->exitCode; // process resource already destroyed

        // Close standard streams
        fclose($this->stdin);
        fclose($this->stdout);
        if (isset($this->stderr)) // not set if option no_capture_stderr is used
            fclose($this->stderr);

        $termStatus = proc_close($this->process);

        // See: http://bugs.php.net/bug.php?id=17538
        // If it was us that terminated the process, we must store the exit
        // code, since it won't be avaialble the next time proc_get_status()
        // is called (the resource will be invalid).
        if ($this->isRunning) {
            $this->exitCode = ($termStatus >> 8) & 0xff;
            $this->isRunning = false;
        }

        // destroy resource
        $this->process = NULL;

        return $this->exitCode;
    }

    /**
     * Kills the process.
     * 
     * The process represented by this Process object is forcibly terminated.
     * 
     */
    public function kill() {
        // Update status to check if it was already terminated or to store
        // the pid before termination.
        $this->updateStatus();

        if (!$this->isRunning)
            return; // process not running anymore

        if (!proc_terminate($this->process, SIGKILL))
            throw new Exception("Cannot kill the process.");

        // proc_terminate() does not destroy the resource on php 5.2.1 or
        // earlier. We destroy it manually to be more deterministic.
        $this->process = NULL;
    }

    /**
     * The exit code returned by the process.
     * 
     * @return int
     * @throws Exception if process is still running.
     */
    public function getExitCode() {
        $this->updateStatus();

        if ($this->isRunning)
            throw new Exception("Cannot get exit code, the process with PID $this->pid is still running.");

        return $this->exitCode;
    }

    /**
     * Updates the status of the process.
     * 
     * This currently includes the following properties:
     *  - process resource
     *  - pid
     *  - isRunning
     *  - exitCode
     * 
     * Do not call proc_get_status() directly, use this function instead.
     * This is needed because the exit code returned by the process is
     * returned only on the first call to proc_get_status() (subsequent
     * calls return -1), and so the value must be stored, if it is received
     * at any time.
     * 
     */
    private function updateStatus() {
        if (!$this->isRunning)
            return; // process not running anymore, no more updating

        $status = proc_get_status($this->process);

        // Store pid if we don't have it yet
        if (!isset($this->pid))
            $this->pid = $status['pid'];

        // Store exit code if process exited, and set it as not running
        if ($status['running'] === false) {
            $this->exitCode = $status['exitcode'];
            $this->isRunning = false;
        }        
    }
}
Return current item: Process