PageRenderTime 50ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/framework/console/CConsoleCommand.php

https://bitbucket.org/intel352/yii-framework
PHP | 574 lines | 360 code | 30 blank | 184 comment | 52 complexity | 97761bfe765c9cf778b4df5561350719 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, BSD-2-Clause
  1. <?php
  2. /**
  3. * CConsoleCommand class file.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright Copyright &copy; 2008-2011 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CConsoleCommand represents an executable console command.
  12. *
  13. * It works like {@link CController} by parsing command line options and dispatching
  14. * the request to a specific action with appropriate option values.
  15. *
  16. * Users call a console command via the following command format:
  17. * <pre>
  18. * yiic CommandName ActionName --Option1=Value1 --Option2=Value2 ...
  19. * </pre>
  20. *
  21. * Child classes mainly needs to implement various action methods whose name must be
  22. * prefixed with "action". The parameters to an action method are considered as options
  23. * for that specific action. The action specified as {@link defaultAction} will be invoked
  24. * when a user does not specify the action name in his command.
  25. *
  26. * Options are bound to action parameters via parameter names. For example, the following
  27. * action method will allow us to run a command with <code>yiic sitemap --type=News</code>:
  28. * <pre>
  29. * class SitemapCommand {
  30. * public function actionIndex($type) {
  31. * ....
  32. * }
  33. * }
  34. * </pre>
  35. *
  36. * @property string $name The command name.
  37. * @property CConsoleCommandRunner $commandRunner The command runner instance.
  38. * @property string $help The command description. Defaults to 'Usage: php entry-script.php command-name'.
  39. * @property array $optionHelp The command option help information. Each array element describes
  40. * the help information for a single action.
  41. *
  42. * @author Qiang Xue <qiang.xue@gmail.com>
  43. * @version $Id$
  44. * @package system.console
  45. * @since 1.0
  46. */
  47. abstract class CConsoleCommand extends CComponent
  48. {
  49. /**
  50. * @var string the name of the default action. Defaults to 'index'.
  51. * @since 1.1.5
  52. */
  53. public $defaultAction='index';
  54. private $_name;
  55. private $_runner;
  56. /**
  57. * Constructor.
  58. * @param string $name name of the command
  59. * @param CConsoleCommandRunner $runner the command runner
  60. */
  61. public function __construct($name,$runner)
  62. {
  63. $this->_name=$name;
  64. $this->_runner=$runner;
  65. $this->attachBehaviors($this->behaviors());
  66. }
  67. /**
  68. * Initializes the command object.
  69. * This method is invoked after a command object is created and initialized with configurations.
  70. * You may override this method to further customize the command before it executes.
  71. * @since 1.1.6
  72. */
  73. public function init()
  74. {
  75. }
  76. /**
  77. * Returns a list of behaviors that this command should behave as.
  78. * The return value should be an array of behavior configurations indexed by
  79. * behavior names. Each behavior configuration can be either a string specifying
  80. * the behavior class or an array of the following structure:
  81. * <pre>
  82. * 'behaviorName'=>array(
  83. * 'class'=>'path.to.BehaviorClass',
  84. * 'property1'=>'value1',
  85. * 'property2'=>'value2',
  86. * )
  87. * </pre>
  88. *
  89. * Note, the behavior classes must implement {@link IBehavior} or extend from
  90. * {@link CBehavior}. Behaviors declared in this method will be attached
  91. * to the controller when it is instantiated.
  92. *
  93. * For more details about behaviors, see {@link CComponent}.
  94. * @return array the behavior configurations (behavior name=>behavior configuration)
  95. * @since 1.1.11
  96. */
  97. public function behaviors()
  98. {
  99. return array();
  100. }
  101. /**
  102. * Executes the command.
  103. * The default implementation will parse the input parameters and
  104. * dispatch the command request to an appropriate action with the corresponding
  105. * option values
  106. * @param array $args command line parameters for this command.
  107. */
  108. public function run($args)
  109. {
  110. list($action, $options, $args)=$this->resolveRequest($args);
  111. $methodName='action'.$action;
  112. if(!preg_match('/^\w+$/',$action) || !method_exists($this,$methodName))
  113. $this->usageError("Unknown action: ".$action);
  114. $method=new ReflectionMethod($this,$methodName);
  115. $params=array();
  116. // named and unnamed options
  117. foreach($method->getParameters() as $i=>$param)
  118. {
  119. $name=$param->getName();
  120. if(isset($options[$name]))
  121. {
  122. if($param->isArray())
  123. $params[]=is_array($options[$name]) ? $options[$name] : array($options[$name]);
  124. else if(!is_array($options[$name]))
  125. $params[]=$options[$name];
  126. else
  127. $this->usageError("Option --$name requires a scalar. Array is given.");
  128. }
  129. else if($name==='args')
  130. $params[]=$args;
  131. else if($param->isDefaultValueAvailable())
  132. $params[]=$param->getDefaultValue();
  133. else
  134. $this->usageError("Missing required option --$name.");
  135. unset($options[$name]);
  136. }
  137. // try global options
  138. if(!empty($options))
  139. {
  140. $class=new ReflectionClass(get_class($this));
  141. foreach($options as $name=>$value)
  142. {
  143. if($class->hasProperty($name))
  144. {
  145. $property=$class->getProperty($name);
  146. if($property->isPublic() && !$property->isStatic())
  147. {
  148. $this->$name=$value;
  149. unset($options[$name]);
  150. }
  151. }
  152. }
  153. }
  154. if(!empty($options))
  155. $this->usageError("Unknown options: ".implode(', ',array_keys($options)));
  156. if($this->beforeAction($action,$params))
  157. {
  158. $method->invokeArgs($this,$params);
  159. $this->afterAction($action,$params);
  160. }
  161. }
  162. /**
  163. * This method is invoked right before an action is to be executed.
  164. * You may override this method to do last-minute preparation for the action.
  165. * @param string $action the action name
  166. * @param array $params the parameters to be passed to the action method.
  167. * @return boolean whether the action should be executed.
  168. */
  169. protected function beforeAction($action,$params)
  170. {
  171. if($this->hasEventHandler('onBeforeAction'))
  172. {
  173. $event = new CConsoleCommandEvent($this, $params, $action);
  174. $this->onBeforeAction($event);
  175. return !$event->stopCommand;
  176. }
  177. else
  178. {
  179. return true;
  180. }
  181. }
  182. /**
  183. * This method is invoked right after an action finishes execution.
  184. * You may override this method to do some postprocessing for the action.
  185. * @param string $action the action name
  186. * @param array $params the parameters to be passed to the action method.
  187. */
  188. protected function afterAction($action,$params)
  189. {
  190. if($this->hasEventHandler('onAfterAction'))
  191. $this->onAfterAction(new CConsoleCommandEvent($this, $params, $action));
  192. }
  193. /**
  194. * Parses the command line arguments and determines which action to perform.
  195. * @param array $args command line arguments
  196. * @return array the action name, named options (name=>value), and unnamed options
  197. * @since 1.1.5
  198. */
  199. protected function resolveRequest($args)
  200. {
  201. $options=array(); // named parameters
  202. $params=array(); // unnamed parameters
  203. foreach($args as $arg)
  204. {
  205. if(preg_match('/^--(\w+)(=(.*))?$/',$arg,$matches)) // an option
  206. {
  207. $name=$matches[1];
  208. $value=isset($matches[3]) ? $matches[3] : true;
  209. if(isset($options[$name]))
  210. {
  211. if(!is_array($options[$name]))
  212. $options[$name]=array($options[$name]);
  213. $options[$name][]=$value;
  214. }
  215. else
  216. $options[$name]=$value;
  217. }
  218. else if(isset($action))
  219. $params[]=$arg;
  220. else
  221. $action=$arg;
  222. }
  223. if(!isset($action))
  224. $action=$this->defaultAction;
  225. return array($action,$options,$params);
  226. }
  227. /**
  228. * @return string the command name.
  229. */
  230. public function getName()
  231. {
  232. return $this->_name;
  233. }
  234. /**
  235. * @return CConsoleCommandRunner the command runner instance
  236. */
  237. public function getCommandRunner()
  238. {
  239. return $this->_runner;
  240. }
  241. /**
  242. * Provides the command description.
  243. * This method may be overridden to return the actual command description.
  244. * @return string the command description. Defaults to 'Usage: php entry-script.php command-name'.
  245. */
  246. public function getHelp()
  247. {
  248. $help='Usage: '.$this->getCommandRunner()->getScriptName().' '.$this->getName();
  249. $options=$this->getOptionHelp();
  250. if(empty($options))
  251. return $help;
  252. if(count($options)===1)
  253. return $help.' '.$options[0];
  254. $help.=" <action>\nActions:\n";
  255. foreach($options as $option)
  256. $help.=' '.$option."\n";
  257. return $help;
  258. }
  259. /**
  260. * Provides the command option help information.
  261. * The default implementation will return all available actions together with their
  262. * corresponding option information.
  263. * @return array the command option help information. Each array element describes
  264. * the help information for a single action.
  265. * @since 1.1.5
  266. */
  267. public function getOptionHelp()
  268. {
  269. $options=array();
  270. $class=new ReflectionClass(get_class($this));
  271. foreach($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method)
  272. {
  273. $name=$method->getName();
  274. if(!strncasecmp($name,'action',6) && strlen($name)>6)
  275. {
  276. $name=substr($name,6);
  277. $name[0]=strtolower($name[0]);
  278. $help=$name;
  279. foreach($method->getParameters() as $param)
  280. {
  281. $optional=$param->isDefaultValueAvailable();
  282. $defaultValue=$optional ? $param->getDefaultValue() : null;
  283. $name=$param->getName();
  284. if($optional)
  285. $help.=" [--$name=$defaultValue]";
  286. else
  287. $help.=" --$name=value";
  288. }
  289. $options[]=$help;
  290. }
  291. }
  292. return $options;
  293. }
  294. /**
  295. * Displays a usage error.
  296. * This method will then terminate the execution of the current application.
  297. * @param string $message the error message
  298. */
  299. public function usageError($message)
  300. {
  301. echo "Error: $message\n\n".$this->getHelp()."\n";
  302. exit(1);
  303. }
  304. /**
  305. * Copies a list of files from one place to another.
  306. * @param array $fileList the list of files to be copied (name=>spec).
  307. * The array keys are names displayed during the copy process, and array values are specifications
  308. * for files to be copied. Each array value must be an array of the following structure:
  309. * <ul>
  310. * <li>source: required, the full path of the file/directory to be copied from</li>
  311. * <li>target: required, the full path of the file/directory to be copied to</li>
  312. * <li>callback: optional, the callback to be invoked when copying a file. The callback function
  313. * should be declared as follows:
  314. * <pre>
  315. * function foo($source,$params)
  316. * </pre>
  317. * where $source parameter is the source file path, and the content returned
  318. * by the function will be saved into the target file.</li>
  319. * <li>params: optional, the parameters to be passed to the callback</li>
  320. * </ul>
  321. * @see buildFileList
  322. */
  323. public function copyFiles($fileList)
  324. {
  325. $overwriteAll=false;
  326. foreach($fileList as $name=>$file)
  327. {
  328. $source=strtr($file['source'],'/\\',DIRECTORY_SEPARATOR);
  329. $target=strtr($file['target'],'/\\',DIRECTORY_SEPARATOR);
  330. $callback=isset($file['callback']) ? $file['callback'] : null;
  331. $params=isset($file['params']) ? $file['params'] : null;
  332. if(is_dir($source))
  333. {
  334. $this->ensureDirectory($target);
  335. continue;
  336. }
  337. if($callback!==null)
  338. $content=call_user_func($callback,$source,$params);
  339. else
  340. $content=file_get_contents($source);
  341. if(is_file($target))
  342. {
  343. if($content===file_get_contents($target))
  344. {
  345. echo " unchanged $name\n";
  346. continue;
  347. }
  348. if($overwriteAll)
  349. echo " overwrite $name\n";
  350. else
  351. {
  352. echo " exist $name\n";
  353. echo " ...overwrite? [Yes|No|All|Quit] ";
  354. $answer=trim(fgets(STDIN));
  355. if(!strncasecmp($answer,'q',1))
  356. return;
  357. else if(!strncasecmp($answer,'y',1))
  358. echo " overwrite $name\n";
  359. else if(!strncasecmp($answer,'a',1))
  360. {
  361. echo " overwrite $name\n";
  362. $overwriteAll=true;
  363. }
  364. else
  365. {
  366. echo " skip $name\n";
  367. continue;
  368. }
  369. }
  370. }
  371. else
  372. {
  373. $this->ensureDirectory(dirname($target));
  374. echo " generate $name\n";
  375. }
  376. file_put_contents($target,$content);
  377. }
  378. }
  379. /**
  380. * Builds the file list of a directory.
  381. * This method traverses through the specified directory and builds
  382. * a list of files and subdirectories that the directory contains.
  383. * The result of this function can be passed to {@link copyFiles}.
  384. * @param string $sourceDir the source directory
  385. * @param string $targetDir the target directory
  386. * @param string $baseDir base directory
  387. * @return array the file list (see {@link copyFiles})
  388. */
  389. public function buildFileList($sourceDir, $targetDir, $baseDir='')
  390. {
  391. $list=array();
  392. $handle=opendir($sourceDir);
  393. while(($file=readdir($handle))!==false)
  394. {
  395. if($file==='.' || $file==='..' || $file==='.svn' ||$file==='.gitignore')
  396. continue;
  397. $sourcePath=$sourceDir.DIRECTORY_SEPARATOR.$file;
  398. $targetPath=$targetDir.DIRECTORY_SEPARATOR.$file;
  399. $name=$baseDir===''?$file : $baseDir.'/'.$file;
  400. $list[$name]=array('source'=>$sourcePath, 'target'=>$targetPath);
  401. if(is_dir($sourcePath))
  402. $list=array_merge($list,$this->buildFileList($sourcePath,$targetPath,$name));
  403. }
  404. closedir($handle);
  405. return $list;
  406. }
  407. /**
  408. * Creates all parent directories if they do not exist.
  409. * @param string $directory the directory to be checked
  410. */
  411. public function ensureDirectory($directory)
  412. {
  413. if(!is_dir($directory))
  414. {
  415. $this->ensureDirectory(dirname($directory));
  416. echo " mkdir ".strtr($directory,'\\','/')."\n";
  417. mkdir($directory);
  418. }
  419. }
  420. /**
  421. * Renders a view file.
  422. * @param string $_viewFile_ view file path
  423. * @param array $_data_ optional data to be extracted as local view variables
  424. * @param boolean $_return_ whether to return the rendering result instead of displaying it
  425. * @return mixed the rendering result if required. Null otherwise.
  426. */
  427. public function renderFile($_viewFile_,$_data_=null,$_return_=false)
  428. {
  429. if(is_array($_data_))
  430. extract($_data_,EXTR_PREFIX_SAME,'data');
  431. else
  432. $data=$_data_;
  433. if($_return_)
  434. {
  435. ob_start();
  436. ob_implicit_flush(false);
  437. require($_viewFile_);
  438. return ob_get_clean();
  439. }
  440. else
  441. require($_viewFile_);
  442. }
  443. /**
  444. * Converts a word to its plural form.
  445. * @param string $name the word to be pluralized
  446. * @return string the pluralized word
  447. */
  448. public function pluralize($name)
  449. {
  450. $rules=array(
  451. '/move$/i' => 'moves',
  452. '/foot$/i' => 'feet',
  453. '/child$/i' => 'children',
  454. '/human$/i' => 'humans',
  455. '/man$/i' => 'men',
  456. '/tooth$/i' => 'teeth',
  457. '/person$/i' => 'people',
  458. '/([m|l])ouse$/i' => '\1ice',
  459. '/(x|ch|ss|sh|us|as|is|os)$/i' => '\1es',
  460. '/([^aeiouy]|qu)y$/i' => '\1ies',
  461. '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
  462. '/(shea|lea|loa|thie)f$/i' => '\1ves',
  463. '/([ti])um$/i' => '\1a',
  464. '/(tomat|potat|ech|her|vet)o$/i' => '\1oes',
  465. '/(bu)s$/i' => '\1ses',
  466. '/(ax|test)is$/i' => '\1es',
  467. '/s$/' => 's',
  468. );
  469. foreach($rules as $rule=>$replacement)
  470. {
  471. if(preg_match($rule,$name))
  472. return preg_replace($rule,$replacement,$name);
  473. }
  474. return $name.'s';
  475. }
  476. /**
  477. * Reads input via the readline PHP extension if that's available, or fgets() if readline is not installed.
  478. *
  479. * @param string $message to echo out before waiting for user input
  480. * @param string $default the default string to be returned when user does not write anything.
  481. * Defaults to null, means that default string is disabled. This parameter is available since version 1.1.11.
  482. * @return mixed line read as a string, or false if input has been closed
  483. *
  484. * @since 1.1.9
  485. */
  486. public function prompt($message,$default=null)
  487. {
  488. if($default!==null)
  489. $message.=" [$default] ";
  490. else
  491. $message.=' ';
  492. if(extension_loaded('readline'))
  493. {
  494. $input=readline($message);
  495. if($input!==false)
  496. readline_add_history($input);
  497. }
  498. else
  499. {
  500. echo $message;
  501. $input=fgets(STDIN);
  502. }
  503. if($input===false)
  504. return false;
  505. else{
  506. $input=trim($input);
  507. return ($input==='' && $default!==null) ? $default : $input;
  508. }
  509. }
  510. /**
  511. * Asks user to confirm by typing y or n.
  512. *
  513. * @param string $message to echo out before waiting for user input
  514. * @return bool if user confirmed
  515. *
  516. * @since 1.1.9
  517. */
  518. public function confirm($message)
  519. {
  520. echo $message.' [yes|no] ';
  521. return !strncasecmp(trim(fgets(STDIN)),'y',1);
  522. }
  523. /**
  524. * This event is raised before an action is to be executed.
  525. * @param CConsoleCommandEvent $event the event parameter
  526. * @since 1.1.11
  527. */
  528. public function onBeforeAction($event)
  529. {
  530. $this->raiseEvent('onBeforeAction',$event);
  531. }
  532. /**
  533. * This event is raised after an action finishes execution.
  534. * @param CConsoleCommandEvent $event the event parameter
  535. * @since 1.1.11
  536. */
  537. public function onAfterAction($event)
  538. {
  539. $this->raiseEvent('onAfterAction',$event);
  540. }
  541. }