PageRenderTime 63ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/demo/yii/console/CConsoleCommand.php

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