* @version 1.0.5 * @license http://www.opensource.org/licenses/MIT */ class command { /** * @var bool whether to escape any argument passed through addArg(). Default is true. */ public $escapeArgs = true; /** * @var bool whether to escape the command passed to setCommand() or the constructor. * This is only useful if $escapeArgs is false. Default is false. */ public $escapeCommand = false; /** * @var bool whether to use `exec()` instead of `proc_open()`. This can be used on Windows system * to workaround some quirks there. Note, that any errors from your command will be output directly * to the PHP output stream. `getStdErr()` will also not work anymore and thus you also won't get * the error output from `getError()` in this case. You also can't pass any environment * variables to the command if this is enabled. Default is false. */ public $useExec = false; /** * @var bool whether to capture stderr (2>&1) when `useExec` is true. This will try to redirect the * stderr to stdout and provide the complete output of both in `getStdErr()` and `getError()`. * Default is `true`. */ public $captureStdErr = true; /** * @var string|null the initial working dir for proc_open(). Default is null for current PHP working dir. */ public $procCwd; /** * @var array|null an array with environment variables to pass to proc_open(). Default is null for none. */ public $procEnv; /** * @var array|null an array of other_options for proc_open(). Default is null for none. */ public $procOptions; /** * @var string the command to execute */ protected $_command; /** * @var array the list of command arguments */ protected $_args = array(); /** * @var string the full command string to execute */ protected $_execCommand; /** * @var string the stdout output */ protected $_stdOut = ''; /** * @var string the stderr output */ protected $_stdErr = ''; /** * @var int the exit code */ protected $_exitCode; /** * @var string the error message */ protected $_error = ''; /** * @var bool whether the command was successfully executed */ protected $_executed = false; /** * @param string|array $options either a command string or an options array (see setOptions()) */ public function __construct($options = null) { if (is_array($options)) { $this->setOptions($options); } elseif (is_string($options)) { $this->setCommand($options); } } /** * @param array $options array of name => value options that should be applied to the object * You can also pass options that use a setter, e.g. you can pass a 'fileName' option which * will be passed to setFileName(). * @return Command for method chaining */ public function setOptions($options) { foreach ($options as $key => $value) { if (property_exists($this, $key)) { $this->$key = $value; } else { $method = 'set'.ucfirst($key); if (method_exists($this, $method)) { call_user_func(array($this,$method), $value); } else { throw new \Exception("Unknown configuration option '$key'"); } } } return $this; } /** * @param string $command the command or full command string to execute, like 'gzip' or 'gzip -d'. * You can still call addArg() to add more arguments to the command. If $escapeCommand was set to true, * the command gets escaped through escapeshellcmd(). * @return Command for method chaining */ public function setCommand($command) { $this->_command = $this->escapeCommand ? escapeshellcmd($command) : $command; return $this; } /** * @return string|null the command that was set through setCommand() or passed to the constructor. Null if none. */ public function getCommand() { return $this->_command; } /** * @return string|bool the full command string to execute. If no command was set with setCommand() * or passed to the constructor it will return false. */ public function getExecCommand() { if ($this->_execCommand===null) { $command = $this->getCommand(); if (!$command) { $this->_error = 'Could not locate any executable command'; return false; } $args = $this->getArgs(); $this->_execCommand = $args ? $command.' '.$args : $command; } return $this->_execCommand; } /** * @param string $args the command arguments as string. Note that these will not get escaped! * @return Command for method chaining */ public function setArgs($args) { $this->_args = array($args); return $this; } /** * @return string the command args that where set through setArgs() or added with addArg() separated by spaces */ public function getArgs() { return implode(' ', $this->_args); } /** * @param string $key the argument key to add e.g. `--feature` or `--name=`. If the key does not end with * and `=`, the $value will be separated by a space, if any. Keys are not escaped unless $value is null * and $escape is `true`. * @param string|array|null $value the optional argument value which will get escaped if $escapeArgs is true. * An array can be passed to add more than one value for a key, e.g. `addArg('--exclude', array('val1','val2'))` * which will create the option `--exclude 'val1' 'val2'`. * @param bool|null $escape if set, this overrides the $escapeArgs setting and enforces escaping/no escaping * @return Command for method chaining */ public function addArg($key, $value = null, $escape = null) { $old=setlocale(LC_ALL,0); setlocale(LC_ALL,'us'); $doEscape = $escape!==null ? $escape : $this->escapeArgs; if ($value===null) { // Only escape single arguments if explicitely requested $this->_args[] = $escape ? escapeshellarg($key) : $key; } else { $separator = substr($key, -1)==='=' ? '' : ' '; if (is_array($value)) { $params = array(); foreach ($value as $v) { $params[] = $doEscape ? escapeshellarg($v) : $v; } $this->_args[] = $key.$separator.implode(' ',$params); } else { $this->_args[] = $key.$separator.($doEscape ? escapeshellarg($value) : $value); } } //print_r($old); setlocale(LC_ALL,$old); return $this; } /** * @return string the command output (stdout). Empty if none. */ public function getOutput() { return $this->_stdOut; } /** * @return string the error message, either stderr or internal message. Empty if none. */ public function getError() { return $this->_error; } /** * @return string the stderr output. Empty if none. */ public function getStdErr() { return $this->_stdErr; } /** * @return int|null the exit code or null if command was not executed yet */ public function getExitCode() { return $this->_exitCode; } /** * @return string whether the command was successfully executed */ public function getExecuted() { return $this->_executed; } /** * Execute the command * * @return bool whether execution was successful. If false, error details can be obtained through * getError(), getStdErr() and getExitCode(). */ public function execute() { $command = $this->getExecCommand(); if (!$command) { return false; } if ($this->useExec) { $execCommand = $this->captureStdErr ? "$command 2>&1" : $command; exec($execCommand, $output, $this->_exitCode); $this->_stdOut = trim(implode("\n", $output)); if ($this->_exitCode!==0) { $this->_stdErr = $this->_stdOut; $this->_error = empty($this->_stdErr) ? 'Command failed' : $this->_stdErr; return false; } } else { $descriptors = array( 1 => array('pipe','w'), 2 => array('pipe','a'), ); $process = proc_open($command, $descriptors, $pipes, $this->procCwd, $this->procEnv, $this->procOptions); if (is_resource($process)) { $this->_stdOut = trim(stream_get_contents($pipes[1])); $this->_stdErr = trim(stream_get_contents($pipes[2])); fclose($pipes[1]); fclose($pipes[2]); $this->_exitCode = proc_close($process); if ($this->_exitCode!==0) { $this->_error = $this->_stdErr ? $this->_stdErr : "Failed without error message: $command"; return false; } } else { $this->_error = "Could not run command $command"; return false; } } $this->_executed = true; return true; } /** * @return string the current command string to execute */ public function __toString() { return (string)$this->getExecCommand(); } }