|
1 <?php |
|
2 /** |
|
3 * Zend Framework |
|
4 * |
|
5 * LICENSE |
|
6 * |
|
7 * This source file is subject to the new BSD license that is bundled |
|
8 * with this package in the file LICENSE.txt. |
|
9 * It is also available through the world-wide-web at this URL: |
|
10 * http://framework.zend.com/license/new-bsd |
|
11 * If you did not receive a copy of the license and are unable to |
|
12 * obtain it through the world-wide-web, please send an email |
|
13 * to license@zend.com so we can send you a copy immediately. |
|
14 * |
|
15 * @category Zend |
|
16 * @package Zend_Service_Console |
|
17 * @version $Id$ |
|
18 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) |
|
19 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
20 * @copyright Copyright (c) 2009 - 2011, RealDolmen (http://www.realdolmen.com) |
|
21 * @license http://phpazure.codeplex.com/license |
|
22 */ |
|
23 |
|
24 /** |
|
25 * @category Zend |
|
26 * @package Zend_Service_Console |
|
27 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) |
|
28 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
29 * @copyright Copyright (c) 2009 - 2011, RealDolmen (http://www.realdolmen.com) |
|
30 * @license http://phpazure.codeplex.com/license |
|
31 */ |
|
32 class Zend_Service_Console_Command |
|
33 { |
|
34 /** |
|
35 * The handler. |
|
36 * |
|
37 * @var array |
|
38 */ |
|
39 protected $_handler; |
|
40 |
|
41 /** |
|
42 * Gets the handler. |
|
43 * |
|
44 * @return array |
|
45 */ |
|
46 public function getHandler() |
|
47 { |
|
48 return $this->_handler; |
|
49 } |
|
50 |
|
51 /** |
|
52 * Sets the handler. |
|
53 * |
|
54 * @param array $handler |
|
55 * @return Zend_Service_Console_Command |
|
56 */ |
|
57 public function setHandler($handler) |
|
58 { |
|
59 $this->_handler = $handler; |
|
60 return $this; |
|
61 } |
|
62 |
|
63 /** |
|
64 * Replaces PHP's error handler |
|
65 * |
|
66 * @param mixed $errno |
|
67 * @param mixed $errstr |
|
68 * @param mixed $errfile |
|
69 * @param mixed $errline |
|
70 */ |
|
71 public static function phpstderr($errno, $errstr, $errfile, $errline) |
|
72 { |
|
73 self::stderr($errno . ': Error in ' . $errfile . ':' . $errline . ' - ' . $errstr); |
|
74 } |
|
75 |
|
76 /** |
|
77 * Replaces PHP's exception handler |
|
78 * |
|
79 * @param Exception $exception |
|
80 */ |
|
81 public static function phpstdex($exception) |
|
82 { |
|
83 self::stderr('Error: ' . $exception->getMessage()); |
|
84 } |
|
85 |
|
86 /** |
|
87 * Writes output to STDERR, followed by a newline (optional) |
|
88 * |
|
89 * @param string $errorMessage |
|
90 * @param string $newLine |
|
91 */ |
|
92 public static function stderr($errorMessage, $newLine = true) |
|
93 { |
|
94 if (error_reporting() === 0) { |
|
95 return; |
|
96 } |
|
97 file_put_contents('php://stderr', $errorMessage . ($newLine ? "\r\n" : '')); |
|
98 } |
|
99 |
|
100 /** |
|
101 * Bootstrap the shell command. |
|
102 * |
|
103 * @param array $argv PHP argument values. |
|
104 */ |
|
105 public static function bootstrap($argv) |
|
106 { |
|
107 // Abort bootstrapping depending on the MICROSOFT_CONSOLE_COMMAND_HOST constant. |
|
108 if (defined('MICROSOFT_CONSOLE_COMMAND_HOST') && strtolower(MICROSOFT_CONSOLE_COMMAND_HOST) != 'console') { |
|
109 return; |
|
110 } |
|
111 |
|
112 // Replace error handler |
|
113 set_error_handler(array('Zend_Service_Console_Command', 'phpstderr')); |
|
114 set_exception_handler(array('Zend_Service_Console_Command', 'phpstdex')); |
|
115 |
|
116 // Build the application model |
|
117 $model = self::_buildModel(); |
|
118 |
|
119 // Find a class that corresponds to the $argv[0] script name |
|
120 $requiredHandlerName = str_replace('.bat', '', str_replace('.sh', '', str_replace('.php', '', strtolower(basename($argv[0]))))); |
|
121 $handler = null; |
|
122 foreach ($model as $possibleHandler) { |
|
123 if ($possibleHandler->handler == strtolower($requiredHandlerName)) { |
|
124 $handler = $possibleHandler; |
|
125 break; |
|
126 } |
|
127 } |
|
128 if (is_null($handler)) { |
|
129 self::stderr("No class found that implements handler '" . $requiredHandlerName . "'. Create a class that is named '" . $requiredHandlerName . "' and extends Zend_Service_Console_Command or is decorated with a docblock comment '@command-handler " . $requiredHandlerName . "'. Make sure it is loaded either through an autoloader or explicitly using require_once()."); |
|
130 die(); |
|
131 } |
|
132 |
|
133 // Find a method that matches the command name |
|
134 $command = null; |
|
135 foreach ($handler->commands as $possibleCommand) { |
|
136 if (in_array(strtolower(isset($argv[1]) ? $argv[1] : '<default>'), $possibleCommand->aliases)) { |
|
137 $command = $possibleCommand; |
|
138 break; |
|
139 } |
|
140 } |
|
141 if (is_null($command)) { |
|
142 $commandName = (isset($argv[1]) ? $argv[1] : '<default>'); |
|
143 self::stderr("No method found that implements command " . $commandName . ". Create a method in class '" . $handler->class . "' that is named '" . strtolower($commandName) . "Command' or is decorated with a docblock comment '@command-name " . $commandName . "'."); |
|
144 die(); |
|
145 } |
|
146 |
|
147 // Parse parameter values |
|
148 $parameterValues = array(); |
|
149 $missingParameterValues = array(); |
|
150 $parameterInputs = array_splice($argv, 2); |
|
151 foreach ($command->parameters as $parameter) { |
|
152 // Default value: null |
|
153 $value = null; |
|
154 |
|
155 // Consult value providers for value. First one wins. |
|
156 foreach ($parameter->valueproviders as $valueProviderName) { |
|
157 if (!class_exists($valueProviderName)) { |
|
158 $valueProviderName = 'Zend_Service_Console_Command_ParameterSource_' . $valueProviderName; |
|
159 } |
|
160 $valueProvider = new $valueProviderName(); |
|
161 |
|
162 $value = $valueProvider->getValueForParameter($parameter, $parameterInputs); |
|
163 if (!is_null($value)) { |
|
164 break; |
|
165 } |
|
166 } |
|
167 if (is_null($value) && $parameter->required) { |
|
168 $missingParameterValues[] = $parameter->aliases[0]; |
|
169 } else if (is_null($value)) { |
|
170 $value = $parameter->defaultvalue; |
|
171 } |
|
172 |
|
173 // Set value |
|
174 $parameterValues[] = $value; |
|
175 $argvValues[$parameter->aliases[0]] = $value; |
|
176 } |
|
177 |
|
178 // Mising parameters? |
|
179 if (count($missingParameterValues) > 0) { |
|
180 self::stderr("Some parameters are missing:\r\n" . implode("\r\n", $missingParameterValues)); |
|
181 die(); |
|
182 } |
|
183 |
|
184 // Supply argv in a nice way |
|
185 $parameterValues['argv'] = $parameterInputs; |
|
186 |
|
187 // Run the command |
|
188 $className = $handler->class; |
|
189 $classInstance = new $className(); |
|
190 $classInstance->setHandler($handler); |
|
191 call_user_func_array(array($classInstance, $command->method), $parameterValues); |
|
192 |
|
193 // Restore error handler |
|
194 restore_exception_handler(); |
|
195 restore_error_handler(); |
|
196 } |
|
197 |
|
198 /** |
|
199 * Builds the handler model. |
|
200 * |
|
201 * @return array |
|
202 */ |
|
203 protected static function _buildModel() |
|
204 { |
|
205 $model = array(); |
|
206 |
|
207 $classes = get_declared_classes(); |
|
208 foreach ($classes as $class) { |
|
209 $type = new ReflectionClass($class); |
|
210 |
|
211 $handlers = self::_findValueForDocComment('@command-handler', $type->getDocComment()); |
|
212 if (count($handlers) == 0 && $type->isSubclassOf('Zend_Service_Console_Command')) { |
|
213 // Fallback: if the class extends Zend_Service_Console_Command, register it as |
|
214 // a command handler. |
|
215 $handlers[] = $class; |
|
216 } |
|
217 $handlerDescriptions = self::_findValueForDocComment('@command-handler-description', $type->getDocComment()); |
|
218 $handlerHeaders = self::_findValueForDocComment('@command-handler-header', $type->getDocComment()); |
|
219 $handlerFooters = self::_findValueForDocComment('@command-handler-footer', $type->getDocComment()); |
|
220 |
|
221 for ($hi = 0; $hi < count($handlers); $hi++) { |
|
222 $handler = $handlers[$hi]; |
|
223 $handlerDescription = isset($handlerDescriptions[$hi]) ? $handlerDescriptions[$hi] : isset($handlerDescriptions[0]) ? $handlerDescriptions[0] : ''; |
|
224 $handlerDescription = str_replace('\r\n', "\r\n", $handlerDescription); |
|
225 $handlerDescription = str_replace('\n', "\n", $handlerDescription); |
|
226 |
|
227 $handlerModel = (object)array( |
|
228 'handler' => strtolower($handler), |
|
229 'description' => $handlerDescription, |
|
230 'headers' => $handlerHeaders, |
|
231 'footers' => $handlerFooters, |
|
232 'class' => $class, |
|
233 'commands' => array() |
|
234 ); |
|
235 |
|
236 $methods = $type->getMethods(); |
|
237 foreach ($methods as $method) { |
|
238 $commands = self::_findValueForDocComment('@command-name', $method->getDocComment()); |
|
239 if (substr($method->getName(), -7) == 'Command' && !in_array(substr($method->getName(), 0, -7), $commands)) { |
|
240 // Fallback: if the method is named <commandname>Command, |
|
241 // register it as a command. |
|
242 $commands[] = substr($method->getName(), 0, -7); |
|
243 } |
|
244 for ($x = 0; $x < count($commands); $x++) { |
|
245 $commands[$x] = strtolower($commands[$x]); |
|
246 } |
|
247 $commands = array_unique($commands); |
|
248 $commandDescriptions = self::_findValueForDocComment('@command-description', $method->getDocComment()); |
|
249 $commandExamples = self::_findValueForDocComment('@command-example', $method->getDocComment()); |
|
250 |
|
251 if (count($commands) > 0) { |
|
252 $command = $commands[0]; |
|
253 $commandDescription = isset($commandDescriptions[0]) ? $commandDescriptions[0] : ''; |
|
254 |
|
255 $commandModel = (object)array( |
|
256 'command' => $command, |
|
257 'aliases' => $commands, |
|
258 'description' => $commandDescription, |
|
259 'examples' => $commandExamples, |
|
260 'class' => $class, |
|
261 'method' => $method->getName(), |
|
262 'parameters' => array() |
|
263 ); |
|
264 |
|
265 $parameters = $method->getParameters(); |
|
266 $parametersFor = self::_findValueForDocComment('@command-parameter-for', $method->getDocComment()); |
|
267 for ($pi = 0; $pi < count($parameters); $pi++) { |
|
268 // Initialize |
|
269 $parameter = $parameters[$pi]; |
|
270 $parameterFor = null; |
|
271 $parameterForDefaultValue = null; |
|
272 |
|
273 // Is it a "catch-all" parameter? |
|
274 if ($parameter->getName() == 'argv') { |
|
275 continue; |
|
276 } |
|
277 |
|
278 // Find the $parametersFor with the same name defined |
|
279 foreach ($parametersFor as $possibleParameterFor) { |
|
280 $possibleParameterFor = explode(' ', $possibleParameterFor, 4); |
|
281 if ($possibleParameterFor[0] == '$' . $parameter->getName()) { |
|
282 $parameterFor = $possibleParameterFor; |
|
283 break; |
|
284 } |
|
285 } |
|
286 if (is_null($parameterFor)) { |
|
287 die('@command-parameter-for missing for parameter $' . $parameter->getName()); |
|
288 } |
|
289 |
|
290 if (is_null($parameterForDefaultValue) && $parameter->isOptional()) { |
|
291 $parameterForDefaultValue = $parameter->getDefaultValue(); |
|
292 } |
|
293 |
|
294 $parameterModel = (object)array( |
|
295 'name' => '$' . $parameter->getName(), |
|
296 'defaultvalue' => $parameterForDefaultValue, |
|
297 'valueproviders' => explode('|', $parameterFor[1]), |
|
298 'aliases' => explode('|', $parameterFor[2]), |
|
299 'description' => (isset($parameterFor[3]) ? $parameterFor[3] : ''), |
|
300 'required' => (isset($parameterFor[3]) ? strpos(strtolower($parameterFor[3]), 'required') !== false && strpos(strtolower($parameterFor[3]), 'required if') === false : false), |
|
301 ); |
|
302 |
|
303 // Add to model |
|
304 $commandModel->parameters[] = $parameterModel; |
|
305 } |
|
306 |
|
307 // Add to model |
|
308 $handlerModel->commands[] = $commandModel; |
|
309 } |
|
310 } |
|
311 |
|
312 // Add to model |
|
313 $model[] = $handlerModel; |
|
314 } |
|
315 } |
|
316 |
|
317 return $model; |
|
318 } |
|
319 |
|
320 /** |
|
321 * Finds the value for a specific docComment. |
|
322 * |
|
323 * @param string $docCommentName Comment name |
|
324 * @param unknown_type $docComment Comment object |
|
325 * @return array |
|
326 */ |
|
327 protected static function _findValueForDocComment($docCommentName, $docComment) |
|
328 { |
|
329 $returnValue = array(); |
|
330 |
|
331 $commentLines = explode("\n", $docComment); |
|
332 foreach ($commentLines as $commentLine) { |
|
333 if (strpos($commentLine, $docCommentName . ' ') !== false) { |
|
334 $returnValue[] = trim(substr($commentLine, strpos($commentLine, $docCommentName) + strlen($docCommentName) + 1)); |
|
335 } |
|
336 } |
|
337 |
|
338 return $returnValue; |
|
339 } |
|
340 |
|
341 /** |
|
342 * Display information on an object |
|
343 * |
|
344 * @param object $object Object |
|
345 * @param array $propertiesToDump Property names to display |
|
346 */ |
|
347 protected function _displayObjectInformation($object, $propertiesToDump = array()) |
|
348 { |
|
349 foreach ($propertiesToDump as $property) { |
|
350 printf('%-16s: %s' . "\r\n", $property, $object->$property); |
|
351 } |
|
352 printf("\r\n"); |
|
353 } |
|
354 |
|
355 /** |
|
356 * Displays the help information. |
|
357 * |
|
358 * @command-name <default> |
|
359 * @command-name -h |
|
360 * @command-name -help |
|
361 * @command-description Displays the current help information. |
|
362 */ |
|
363 public function helpCommand() { |
|
364 $handler = $this->getHandler(); |
|
365 $newline = "\r\n"; |
|
366 |
|
367 if (count($handler->headers) > 0) { |
|
368 foreach ($handler->headers as $header) { |
|
369 printf('%s%s', $header, $newline); |
|
370 } |
|
371 printf($newline); |
|
372 } |
|
373 printf('%s%s', $handler->description, $newline); |
|
374 printf($newline); |
|
375 printf('Available commands:%s', $newline); |
|
376 foreach ($handler->commands as $command) { |
|
377 $description = str_split($command->description, 50); |
|
378 printf(' %-25s %s%s', implode(', ', $command->aliases), $description[0], $newline); |
|
379 for ($di = 1; $di < count($description); $di++) { |
|
380 printf(' %-25s %s%s', '', $description[$di], $newline); |
|
381 } |
|
382 printf($newline); |
|
383 |
|
384 if (count($command->parameters) > 0) { |
|
385 foreach ($command->parameters as $parameter) { |
|
386 $description = str_split($parameter->description, 50); |
|
387 printf(' %-23s %s%s', implode(', ', $parameter->aliases), $description[0], $newline); |
|
388 for ($di = 1; $di < count($description); $di++) { |
|
389 printf(' %-23s %s%s', '', $description[$di], $newline); |
|
390 } |
|
391 printf($newline); |
|
392 } |
|
393 } |
|
394 printf($newline); |
|
395 |
|
396 if (count($command->examples) > 0) { |
|
397 printf(' Example usage:%s', $newline); |
|
398 foreach ($command->examples as $example) { |
|
399 printf(' %s%s', $example, $newline); |
|
400 } |
|
401 printf($newline); |
|
402 } |
|
403 } |
|
404 |
|
405 if (count($handler->footers) > 0) { |
|
406 printf($newline); |
|
407 foreach ($handler->footers as $footer) { |
|
408 printf('%s%s', $footer, $newline); |
|
409 } |
|
410 printf($newline); |
|
411 } |
|
412 } |
|
413 } |