File indexing completed on 2025-01-26 05:29:52
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Service_Console 0017 * @version $Id$ 0018 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0019 * @license http://framework.zend.com/license/new-bsd New BSD License 0020 * @copyright Copyright (c) 2009 - 2011, RealDolmen (http://www.realdolmen.com) 0021 * @license http://phpazure.codeplex.com/license 0022 */ 0023 0024 /** 0025 * @category Zend 0026 * @package Zend_Service_Console 0027 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0028 * @license http://framework.zend.com/license/new-bsd New BSD License 0029 * @copyright Copyright (c) 2009 - 2011, RealDolmen (http://www.realdolmen.com) 0030 * @license http://phpazure.codeplex.com/license 0031 */ 0032 class Zend_Service_Console_Command 0033 { 0034 /** 0035 * The handler. 0036 * 0037 * @var array 0038 */ 0039 protected $_handler; 0040 0041 /** 0042 * Gets the handler. 0043 * 0044 * @return array 0045 */ 0046 public function getHandler() 0047 { 0048 return $this->_handler; 0049 } 0050 0051 /** 0052 * Sets the handler. 0053 * 0054 * @param array $handler 0055 * @return Zend_Service_Console_Command 0056 */ 0057 public function setHandler($handler) 0058 { 0059 $this->_handler = $handler; 0060 return $this; 0061 } 0062 0063 /** 0064 * Replaces PHP's error handler 0065 * 0066 * @param mixed $errno 0067 * @param mixed $errstr 0068 * @param mixed $errfile 0069 * @param mixed $errline 0070 */ 0071 public static function phpstderr($errno, $errstr, $errfile, $errline) 0072 { 0073 self::stderr($errno . ': Error in ' . $errfile . ':' . $errline . ' - ' . $errstr); 0074 } 0075 0076 /** 0077 * Replaces PHP's exception handler 0078 * 0079 * @param Exception $exception 0080 */ 0081 public static function phpstdex($exception) 0082 { 0083 self::stderr('Error: ' . $exception->getMessage()); 0084 } 0085 0086 /** 0087 * Writes output to STDERR, followed by a newline (optional) 0088 * 0089 * @param string $errorMessage 0090 * @param string $newLine 0091 */ 0092 public static function stderr($errorMessage, $newLine = true) 0093 { 0094 if (error_reporting() === 0) { 0095 return; 0096 } 0097 file_put_contents('php://stderr', $errorMessage . ($newLine ? "\r\n" : '')); 0098 } 0099 0100 /** 0101 * Bootstrap the shell command. 0102 * 0103 * @param array $argv PHP argument values. 0104 */ 0105 public static function bootstrap($argv) 0106 { 0107 // Abort bootstrapping depending on the MICROSOFT_CONSOLE_COMMAND_HOST constant. 0108 if (defined('MICROSOFT_CONSOLE_COMMAND_HOST') && strtolower(MICROSOFT_CONSOLE_COMMAND_HOST) != 'console') { 0109 return; 0110 } 0111 0112 // Replace error handler 0113 set_error_handler(array('Zend_Service_Console_Command', 'phpstderr')); 0114 set_exception_handler(array('Zend_Service_Console_Command', 'phpstdex')); 0115 0116 // Build the application model 0117 $model = self::_buildModel(); 0118 0119 // Find a class that corresponds to the $argv[0] script name 0120 $requiredHandlerName = str_replace('.bat', '', str_replace('.sh', '', str_replace('.php', '', strtolower(basename($argv[0]))))); 0121 $handler = null; 0122 foreach ($model as $possibleHandler) { 0123 if ($possibleHandler->handler == strtolower($requiredHandlerName)) { 0124 $handler = $possibleHandler; 0125 break; 0126 } 0127 } 0128 if (is_null($handler)) { 0129 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()."); 0130 die(); 0131 } 0132 0133 // Find a method that matches the command name 0134 $command = null; 0135 foreach ($handler->commands as $possibleCommand) { 0136 if (in_array(strtolower(isset($argv[1]) ? $argv[1] : '<default>'), $possibleCommand->aliases)) { 0137 $command = $possibleCommand; 0138 break; 0139 } 0140 } 0141 if (is_null($command)) { 0142 $commandName = (isset($argv[1]) ? $argv[1] : '<default>'); 0143 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 . "'."); 0144 die(); 0145 } 0146 0147 // Parse parameter values 0148 $parameterValues = array(); 0149 $missingParameterValues = array(); 0150 $parameterInputs = array_splice($argv, 2); 0151 foreach ($command->parameters as $parameter) { 0152 // Default value: null 0153 $value = null; 0154 0155 // Consult value providers for value. First one wins. 0156 foreach ($parameter->valueproviders as $valueProviderName) { 0157 if (!class_exists($valueProviderName)) { 0158 $valueProviderName = 'Zend_Service_Console_Command_ParameterSource_' . $valueProviderName; 0159 } 0160 $valueProvider = new $valueProviderName(); 0161 0162 $value = $valueProvider->getValueForParameter($parameter, $parameterInputs); 0163 if (!is_null($value)) { 0164 break; 0165 } 0166 } 0167 if (is_null($value) && $parameter->required) { 0168 $missingParameterValues[] = $parameter->aliases[0]; 0169 } else if (is_null($value)) { 0170 $value = $parameter->defaultvalue; 0171 } 0172 0173 // Set value 0174 $parameterValues[] = $value; 0175 $argvValues[$parameter->aliases[0]] = $value; 0176 } 0177 0178 // Mising parameters? 0179 if (count($missingParameterValues) > 0) { 0180 self::stderr("Some parameters are missing:\r\n" . implode("\r\n", $missingParameterValues)); 0181 die(); 0182 } 0183 0184 // Supply argv in a nice way 0185 $parameterValues['argv'] = $parameterInputs; 0186 0187 // Run the command 0188 $className = $handler->class; 0189 $classInstance = new $className(); 0190 $classInstance->setHandler($handler); 0191 call_user_func_array(array($classInstance, $command->method), $parameterValues); 0192 0193 // Restore error handler 0194 restore_exception_handler(); 0195 restore_error_handler(); 0196 } 0197 0198 /** 0199 * Builds the handler model. 0200 * 0201 * @return array 0202 */ 0203 protected static function _buildModel() 0204 { 0205 $model = array(); 0206 0207 $classes = get_declared_classes(); 0208 foreach ($classes as $class) { 0209 $type = new ReflectionClass($class); 0210 0211 $handlers = self::_findValueForDocComment('@command-handler', $type->getDocComment()); 0212 if (count($handlers) == 0 && $type->isSubclassOf('Zend_Service_Console_Command')) { 0213 // Fallback: if the class extends Zend_Service_Console_Command, register it as 0214 // a command handler. 0215 $handlers[] = $class; 0216 } 0217 $handlerDescriptions = self::_findValueForDocComment('@command-handler-description', $type->getDocComment()); 0218 $handlerHeaders = self::_findValueForDocComment('@command-handler-header', $type->getDocComment()); 0219 $handlerFooters = self::_findValueForDocComment('@command-handler-footer', $type->getDocComment()); 0220 0221 for ($hi = 0; $hi < count($handlers); $hi++) { 0222 $handler = $handlers[$hi]; 0223 $handlerDescription = isset($handlerDescriptions[$hi]) ? $handlerDescriptions[$hi] : isset($handlerDescriptions[0]) ? $handlerDescriptions[0] : ''; 0224 $handlerDescription = str_replace('\r\n', "\r\n", $handlerDescription); 0225 $handlerDescription = str_replace('\n', "\n", $handlerDescription); 0226 0227 $handlerModel = (object)array( 0228 'handler' => strtolower($handler), 0229 'description' => $handlerDescription, 0230 'headers' => $handlerHeaders, 0231 'footers' => $handlerFooters, 0232 'class' => $class, 0233 'commands' => array() 0234 ); 0235 0236 $methods = $type->getMethods(); 0237 foreach ($methods as $method) { 0238 $commands = self::_findValueForDocComment('@command-name', $method->getDocComment()); 0239 if (substr($method->getName(), -7) == 'Command' && !in_array(substr($method->getName(), 0, -7), $commands)) { 0240 // Fallback: if the method is named <commandname>Command, 0241 // register it as a command. 0242 $commands[] = substr($method->getName(), 0, -7); 0243 } 0244 for ($x = 0; $x < count($commands); $x++) { 0245 $commands[$x] = strtolower($commands[$x]); 0246 } 0247 $commands = array_unique($commands); 0248 $commandDescriptions = self::_findValueForDocComment('@command-description', $method->getDocComment()); 0249 $commandExamples = self::_findValueForDocComment('@command-example', $method->getDocComment()); 0250 0251 if (count($commands) > 0) { 0252 $command = $commands[0]; 0253 $commandDescription = isset($commandDescriptions[0]) ? $commandDescriptions[0] : ''; 0254 0255 $commandModel = (object)array( 0256 'command' => $command, 0257 'aliases' => $commands, 0258 'description' => $commandDescription, 0259 'examples' => $commandExamples, 0260 'class' => $class, 0261 'method' => $method->getName(), 0262 'parameters' => array() 0263 ); 0264 0265 $parameters = $method->getParameters(); 0266 $parametersFor = self::_findValueForDocComment('@command-parameter-for', $method->getDocComment()); 0267 for ($pi = 0; $pi < count($parameters); $pi++) { 0268 // Initialize 0269 $parameter = $parameters[$pi]; 0270 $parameterFor = null; 0271 $parameterForDefaultValue = null; 0272 0273 // Is it a "catch-all" parameter? 0274 if ($parameter->getName() == 'argv') { 0275 continue; 0276 } 0277 0278 // Find the $parametersFor with the same name defined 0279 foreach ($parametersFor as $possibleParameterFor) { 0280 $possibleParameterFor = explode(' ', $possibleParameterFor, 4); 0281 if ($possibleParameterFor[0] == '$' . $parameter->getName()) { 0282 $parameterFor = $possibleParameterFor; 0283 break; 0284 } 0285 } 0286 if (is_null($parameterFor)) { 0287 die('@command-parameter-for missing for parameter $' . $parameter->getName()); 0288 } 0289 0290 if (is_null($parameterForDefaultValue) && $parameter->isOptional()) { 0291 $parameterForDefaultValue = $parameter->getDefaultValue(); 0292 } 0293 0294 $parameterModel = (object)array( 0295 'name' => '$' . $parameter->getName(), 0296 'defaultvalue' => $parameterForDefaultValue, 0297 'valueproviders' => explode('|', $parameterFor[1]), 0298 'aliases' => explode('|', $parameterFor[2]), 0299 'description' => (isset($parameterFor[3]) ? $parameterFor[3] : ''), 0300 'required' => (isset($parameterFor[3]) ? strpos(strtolower($parameterFor[3]), 'required') !== false && strpos(strtolower($parameterFor[3]), 'required if') === false : false), 0301 ); 0302 0303 // Add to model 0304 $commandModel->parameters[] = $parameterModel; 0305 } 0306 0307 // Add to model 0308 $handlerModel->commands[] = $commandModel; 0309 } 0310 } 0311 0312 // Add to model 0313 $model[] = $handlerModel; 0314 } 0315 } 0316 0317 return $model; 0318 } 0319 0320 /** 0321 * Finds the value for a specific docComment. 0322 * 0323 * @param string $docCommentName Comment name 0324 * @param unknown_type $docComment Comment object 0325 * @return array 0326 */ 0327 protected static function _findValueForDocComment($docCommentName, $docComment) 0328 { 0329 $returnValue = array(); 0330 0331 $commentLines = explode("\n", $docComment); 0332 foreach ($commentLines as $commentLine) { 0333 if (strpos($commentLine, $docCommentName . ' ') !== false) { 0334 $returnValue[] = trim(substr($commentLine, strpos($commentLine, $docCommentName) + strlen($docCommentName) + 1)); 0335 } 0336 } 0337 0338 return $returnValue; 0339 } 0340 0341 /** 0342 * Display information on an object 0343 * 0344 * @param object $object Object 0345 * @param array $propertiesToDump Property names to display 0346 */ 0347 protected function _displayObjectInformation($object, $propertiesToDump = array()) 0348 { 0349 foreach ($propertiesToDump as $property) { 0350 printf('%-16s: %s' . "\r\n", $property, $object->$property); 0351 } 0352 printf("\r\n"); 0353 } 0354 0355 /** 0356 * Displays the help information. 0357 * 0358 * @command-name <default> 0359 * @command-name -h 0360 * @command-name -help 0361 * @command-description Displays the current help information. 0362 */ 0363 public function helpCommand() { 0364 $handler = $this->getHandler(); 0365 $newline = "\r\n"; 0366 0367 if (count($handler->headers) > 0) { 0368 foreach ($handler->headers as $header) { 0369 printf('%s%s', $header, $newline); 0370 } 0371 printf($newline); 0372 } 0373 printf('%s%s', $handler->description, $newline); 0374 printf($newline); 0375 printf('Available commands:%s', $newline); 0376 foreach ($handler->commands as $command) { 0377 $description = str_split($command->description, 50); 0378 printf(' %-25s %s%s', implode(', ', $command->aliases), $description[0], $newline); 0379 for ($di = 1; $di < count($description); $di++) { 0380 printf(' %-25s %s%s', '', $description[$di], $newline); 0381 } 0382 printf($newline); 0383 0384 if (count($command->parameters) > 0) { 0385 foreach ($command->parameters as $parameter) { 0386 $description = str_split($parameter->description, 50); 0387 printf(' %-23s %s%s', implode(', ', $parameter->aliases), $description[0], $newline); 0388 for ($di = 1; $di < count($description); $di++) { 0389 printf(' %-23s %s%s', '', $description[$di], $newline); 0390 } 0391 printf($newline); 0392 } 0393 } 0394 printf($newline); 0395 0396 if (count($command->examples) > 0) { 0397 printf(' Example usage:%s', $newline); 0398 foreach ($command->examples as $example) { 0399 printf(' %s%s', $example, $newline); 0400 } 0401 printf($newline); 0402 } 0403 } 0404 0405 if (count($handler->footers) > 0) { 0406 printf($newline); 0407 foreach ($handler->footers as $footer) { 0408 printf('%s%s', $footer, $newline); 0409 } 0410 printf($newline); 0411 } 0412 } 0413 }