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 }