web/lib/Zend/Service/Console/Command.php
changeset 1230 68c69c656a2c
equal deleted inserted replaced
1229:5a6b6e770365 1230:68c69c656a2c
       
     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 }