1 : <?php
2 : /**
3 : * This file is part of NoiseLabs-PHP-ToolKit
4 : *
5 : * NoiseLabs-PHP-ToolKit is free software; you can redistribute it
6 : * and/or modify it under the terms of the GNU Lesser General Public
7 : * License as published by the Free Software Foundation; either
8 : * version 3 of the License, or (at your option) any later version.
9 : *
10 : * NoiseLabs-PHP-ToolKit is distributed in the hope that it will be
11 : * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
12 : * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : * Lesser General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU Lesser General Public
16 : * License along with NoiseLabs-PHP-ToolKit; if not, see
17 : * <http://www.gnu.org/licenses/>.
18 : *
19 : * Copyright (C) 2011 Vítor Brandão
20 : *
21 : * @category NoiseLabs
22 : * @package Runner
23 : * @author Vítor Brandão <noisebleed@noiselabs.org>
24 : * @copyright (C) 2011 Vítor Brandão <noisebleed@noiselabs.org>
25 : * @license http://www.gnu.org/licenses/lgpl-3.0-standalone.html LGPL-3
26 : * @link http://www.noiselabs.org
27 : * @since 0.2.0
28 : */
29 :
30 : namespace NoiseLabs\ToolKit\Runner;
31 :
32 : use NoiseLabs\ToolKit\Runner\ParameterBag;
33 : use NoiseLabs\ToolKit\Runner\ProcessInterface;
34 :
35 : class Process implements ProcessInterface
36 : {
37 : const PACKAGE = 'Runner';
38 : protected $command;
39 : protected $_resource;
40 : protected $_output = array();
41 : protected $_retcode;
42 : protected $_descriptorspec;
43 :
44 : /**
45 : * Known settings:
46 : *
47 : * 'sudo':
48 : * If TRUE, prepend every command with 'sudo'.
49 : *
50 : * 'cwd':
51 : * The initial working dir for the command. This must be an absolute
52 : * directory path, or NULL if you want to use the default value (the
53 : * working dir of the current PHP process).
54 : *
55 : * 'env':
56 : * An array with the environment variables for the command that will
57 : * be run, or NULL to use the same environment as the current PHP
58 : * process.
59 : *
60 : * @var array
61 : */
62 : protected $_defaults = array(
63 : 'sudo' => false,
64 : 'cwd' => null,
65 : 'env' => null
66 : );
67 :
68 : public $settings;
69 :
70 : public function __construct($command, array $settings = array())
71 : {
72 5 : $this->setCommand($command);
73 :
74 : // default settings
75 5 : $this->settings = new ParameterBag($this->_defaults);
76 5 : $this->settings->add($settings);
77 :
78 5 : $this->_descriptorspec = array(
79 5 : 0 => array('pipe', 'r'), // stdin
80 5 : 1 => array('pipe', 'w'), // stdout
81 5 : 2 => array('pipe', 'w') // stderr
82 5 : );
83 :
84 5 : $this->reset();
85 5 : }
86 :
87 : public static function create($command, array $settings = array())
88 : {
89 0 : return new static($command, $settings);
90 : }
91 :
92 : public function __toString()
93 : {
94 0 : return 'Return code: '.$this->getReturnCode();
95 : }
96 :
97 : public function getCommand()
98 : {
99 1 : return $this->command;
100 : }
101 :
102 : public function setCommand($command)
103 : {
104 6 : $this->command = escapeshellcmd($command);
105 6 : }
106 :
107 : /**
108 : * Append arguments to the currently set $command.
109 : *
110 : * @throws InvalidArgumentException if $args is not a string
111 : */
112 : public function addArguments($args)
113 : {
114 1 : if (!is_string($args)) {
115 0 : throw new \InvalidArgumentException('arguments must be a string');
116 : }
117 :
118 1 : $this->command .= ' '.trim($args);
119 1 : }
120 :
121 : /**
122 : * Replace arguments set in $command with a new string
123 : *
124 : * @throws InvalidArgumentException if $args is not a string
125 : */
126 : public function replaceArguments($args)
127 : {
128 1 : if (!is_string($args)) {
129 0 : throw new \InvalidArgumentException('arguments must be a string');
130 : }
131 :
132 1 : $this->command = reset(explode(' ', $this->command));
133 :
134 1 : return $this->addArguments($args);
135 : }
136 :
137 : protected function reset()
138 : {
139 5 : $this->_resource = false;
140 5 : $this->_output = array();
141 5 : $this->_retcode = null;
142 5 : }
143 :
144 : public function run()
145 : {
146 1 : $this->reset();
147 :
148 : // use sudo?
149 1 : $command = (true === $this->settings->get('sudo', $this->_defaults['sudo'])) ?
150 1 : 'sudo '.$this->command : $this->command;
151 :
152 : // current working directory
153 1 : $cwd = $this->settings->get('cwd', $this->_defaults['cwd']);
154 :
155 : // environment variables
156 1 : $env = $this->settings->get('env', $this->_defaults['env']);
157 :
158 1 : $this->_resource = proc_open(
159 1 : $command,
160 1 : $this->_descriptorspec,
161 1 : $pipes,
162 1 : $cwd,
163 1 : $env);
164 :
165 1 : if (is_resource($this->_resource)) {
166 : // $pipes now looks like this:
167 : // 0 => writeable handle connected to child stdin
168 : // 1 => readable handle connected to child stdout
169 : // 2 => readable handle connected to child stderr
170 1 : fclose($pipes[0]);
171 1 : $this->_output['stdout'] = stream_get_contents($pipes[1]);
172 1 : fclose($pipes[1]);
173 1 : $this->_output['stderr'] = stream_get_contents($pipes[2]);
174 1 : fclose($pipes[2]);
175 :
176 : // It is important that you close any pipes before calling
177 : // proc_close in order to avoid a deadlock
178 1 : $this->_retcode = proc_close($this->_resource);
179 :
180 1 : $msg = sprintf("%s: executed '%s' (ReturnCode: %d",
181 1 : static::PACKAGE,
182 1 : $this->command,
183 1 : $this->getReturnCode()
184 1 : );
185 1 : if ($this->getErrorMessage() != null) {
186 1 : $msg .= sprintf(", Error: '%s'", $this->getErrorMessage());
187 1 : }
188 1 : $msg .= ")";
189 :
190 1 : $this->log($msg);
191 1 : }
192 : else {
193 0 : $this->log(static::PACKAGE.": failed to open resource using proc_open");
194 : }
195 :
196 1 : return $this;
197 : }
198 :
199 : public function exec()
200 : {
201 0 : return $this->run();
202 : }
203 :
204 : public function success()
205 : {
206 0 : return (0 === $this->getReturnCode());
207 : }
208 :
209 : public function getOutput()
210 : {
211 1 : return isset($this->_output['stdout']) ? $this->_output['stdout'] : null;
212 : }
213 :
214 : public function getErrorMessage()
215 : {
216 1 : return isset($this->_output['stderr']) ? trim($this->_output['stderr']) : null;
217 : }
218 :
219 : public function getReturnCode()
220 : {
221 1 : return $this->_retcode;
222 : }
223 :
224 : public function getName()
225 : {
226 0 : return basename(reset(explode(' ', $this->command)));
227 : }
228 :
229 : public function log($message, $level = 'info')
230 : {
231 0 : error_log($message);
232 0 : }
233 : }
234 :
|