CrudCommand.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <?php
  2. /**
  3. * CrudCommand class file.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright 2008-2013 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CrudCommand generates code implementing CRUD operations.
  12. *
  13. * @author Qiang Xue <qiang.xue@gmail.com>
  14. * @package system.cli.commands.shell
  15. * @since 1.0
  16. */
  17. class CrudCommand extends CConsoleCommand
  18. {
  19. /**
  20. * @var string the directory that contains templates for crud commands.
  21. * Defaults to null, meaning using 'framework/cli/views/shell/crud'.
  22. * If you set this path and some views are missing in the directory,
  23. * the default views will be used.
  24. */
  25. public $templatePath;
  26. /**
  27. * @var string the directory that contains functional test classes.
  28. * Defaults to null, meaning using 'protected/tests/functional'.
  29. * If this is false, it means functional test file should NOT be generated.
  30. */
  31. public $functionalTestPath;
  32. /**
  33. * @var array list of actions to be created. Each action must be associated with a template file with the same name.
  34. */
  35. public $actions=array('create','update','index','view','admin','_form','_view','_search');
  36. public function getHelp()
  37. {
  38. return <<<EOD
  39. USAGE
  40. crud <model-class> [controller-ID] ...
  41. DESCRIPTION
  42. This command generates a controller and views that accomplish
  43. CRUD operations for the specified data model.
  44. PARAMETERS
  45. * model-class: required, the name of the data model class. This can
  46. also be specified as a path alias (e.g. application.models.Post).
  47. If the model class belongs to a module, it should be specified
  48. as 'ModuleID.models.ClassName'.
  49. * controller-ID: optional, the controller ID (e.g. 'post').
  50. If this is not specified, the model class name will be used
  51. as the controller ID. In this case, if the model belongs to
  52. a module, the controller will also be created under the same
  53. module.
  54. If the controller should be located under a subdirectory,
  55. please specify the controller ID as 'path/to/ControllerID'
  56. (e.g. 'admin/user').
  57. If the controller belongs to a module (different from the module
  58. that the model belongs to), please specify the controller ID
  59. as 'ModuleID/ControllerID' or 'ModuleID/path/to/Controller'.
  60. EXAMPLES
  61. * Generates CRUD for the Post model:
  62. crud Post
  63. * Generates CRUD for the Post model which belongs to module 'admin':
  64. crud admin.models.Post
  65. * Generates CRUD for the Post model. The generated controller should
  66. belong to module 'admin', but not the model class:
  67. crud Post admin/post
  68. EOD;
  69. }
  70. /**
  71. * Execute the action.
  72. * @param array $args command line parameters specific for this command
  73. * @return integer|null non zero application exit code for help or null on success
  74. */
  75. public function run($args)
  76. {
  77. if(!isset($args[0]))
  78. {
  79. echo "Error: data model class is required.\n";
  80. echo $this->getHelp();
  81. return 1;
  82. }
  83. $module=Yii::app();
  84. $modelClass=$args[0];
  85. if(($pos=strpos($modelClass,'.'))===false)
  86. $modelClass='application.models.'.$modelClass;
  87. else
  88. {
  89. $id=substr($modelClass,0,$pos);
  90. if(($m=Yii::app()->getModule($id))!==null)
  91. $module=$m;
  92. }
  93. $modelClass=Yii::import($modelClass);
  94. if(isset($args[1]))
  95. {
  96. $controllerID=$args[1];
  97. if(($pos=strrpos($controllerID,'/'))===false)
  98. {
  99. $controllerClass=ucfirst($controllerID).'Controller';
  100. $controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.$controllerClass.'.php';
  101. $controllerID[0]=strtolower($controllerID[0]);
  102. }
  103. else
  104. {
  105. $last=substr($controllerID,$pos+1);
  106. $last[0]=strtolower($last);
  107. $pos2=strpos($controllerID,'/');
  108. $first=substr($controllerID,0,$pos2);
  109. $middle=$pos===$pos2?'':substr($controllerID,$pos2+1,$pos-$pos2);
  110. $controllerClass=ucfirst($last).'Controller';
  111. $controllerFile=($middle===''?'':$middle.'/').$controllerClass.'.php';
  112. $controllerID=$middle===''?$last:$middle.'/'.$last;
  113. if(($m=Yii::app()->getModule($first))!==null)
  114. $module=$m;
  115. else
  116. {
  117. $controllerFile=$first.'/'.$controllerFile;
  118. $controllerID=$first.'/'.$controllerID;
  119. }
  120. $controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.str_replace('/',DIRECTORY_SEPARATOR,$controllerFile);
  121. }
  122. }
  123. else
  124. {
  125. $controllerID=$modelClass;
  126. $controllerClass=ucfirst($controllerID).'Controller';
  127. $controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.$controllerClass.'.php';
  128. $controllerID[0]=strtolower($controllerID[0]);
  129. }
  130. $templatePath=$this->templatePath===null?YII_PATH.'/cli/views/shell/crud':$this->templatePath;
  131. $functionalTestPath=$this->functionalTestPath===null?Yii::getPathOfAlias('application.tests.functional'):$this->functionalTestPath;
  132. $viewPath=$module->viewPath.DIRECTORY_SEPARATOR.str_replace('.',DIRECTORY_SEPARATOR,$controllerID);
  133. $fixtureName=$this->pluralize($modelClass);
  134. $fixtureName[0]=strtolower($fixtureName);
  135. $list=array(
  136. basename($controllerFile)=>array(
  137. 'source'=>$templatePath.'/controller.php',
  138. 'target'=>$controllerFile,
  139. 'callback'=>array($this,'generateController'),
  140. 'params'=>array($controllerClass,$modelClass),
  141. ),
  142. );
  143. if($functionalTestPath!==false)
  144. {
  145. $list[$modelClass.'Test.php']=array(
  146. 'source'=>$templatePath.'/test.php',
  147. 'target'=>$functionalTestPath.DIRECTORY_SEPARATOR.$modelClass.'Test.php',
  148. 'callback'=>array($this,'generateTest'),
  149. 'params'=>array($controllerID,$fixtureName,$modelClass),
  150. );
  151. }
  152. foreach($this->actions as $action)
  153. {
  154. $list[$action.'.php']=array(
  155. 'source'=>$templatePath.'/'.$action.'.php',
  156. 'target'=>$viewPath.'/'.$action.'.php',
  157. 'callback'=>array($this,'generateView'),
  158. 'params'=>$modelClass,
  159. );
  160. }
  161. $this->copyFiles($list);
  162. if($module instanceof CWebModule)
  163. $moduleID=$module->id.'/';
  164. else
  165. $moduleID='';
  166. echo "\nCrud '{$controllerID}' has been successfully created. You may access it via:\n";
  167. echo "http://hostname/path/to/index.php?r={$moduleID}{$controllerID}\n";
  168. }
  169. public function generateController($source,$params)
  170. {
  171. list($controllerClass,$modelClass)=$params;
  172. $model=CActiveRecord::model($modelClass);
  173. $id=$model->tableSchema->primaryKey;
  174. if($id===null)
  175. throw new ShellException(Yii::t('yii','Error: Table "{table}" does not have a primary key.',array('{table}'=>$model->tableName())));
  176. elseif(is_array($id))
  177. throw new ShellException(Yii::t('yii','Error: Table "{table}" has a composite primary key which is not supported by crud command.',array('{table}'=>$model->tableName())));
  178. if(!is_file($source)) // fall back to default ones
  179. $source=YII_PATH.'/cli/views/shell/crud/'.basename($source);
  180. return $this->renderFile($source,array(
  181. 'ID'=>$id,
  182. 'controllerClass'=>$controllerClass,
  183. 'modelClass'=>$modelClass,
  184. ),true);
  185. }
  186. public function generateView($source,$modelClass)
  187. {
  188. $model=CActiveRecord::model($modelClass);
  189. $table=$model->getTableSchema();
  190. $columns=$table->columns;
  191. if(!is_file($source)) // fall back to default ones
  192. $source=YII_PATH.'/cli/views/shell/crud/'.basename($source);
  193. return $this->renderFile($source,array(
  194. 'ID'=>$table->primaryKey,
  195. 'modelClass'=>$modelClass,
  196. 'columns'=>$columns),true);
  197. }
  198. public function generateTest($source,$params)
  199. {
  200. list($controllerID,$fixtureName,$modelClass)=$params;
  201. if(!is_file($source)) // fall back to default ones
  202. $source=YII_PATH.'/cli/views/shell/crud/'.basename($source);
  203. return $this->renderFile($source, array(
  204. 'controllerID'=>$controllerID,
  205. 'fixtureName'=>$fixtureName,
  206. 'modelClass'=>$modelClass,
  207. ),true);
  208. }
  209. public function generateInputLabel($modelClass,$column)
  210. {
  211. return "CHtml::activeLabelEx(\$model,'{$column->name}')";
  212. }
  213. public function generateInputField($modelClass,$column)
  214. {
  215. if($column->type==='boolean')
  216. return "CHtml::activeCheckBox(\$model,'{$column->name}')";
  217. elseif(stripos($column->dbType,'text')!==false)
  218. return "CHtml::activeTextArea(\$model,'{$column->name}',array('rows'=>6, 'cols'=>50))";
  219. else
  220. {
  221. if(preg_match('/^(password|pass|passwd|passcode)$/i',$column->name))
  222. $inputField='activePasswordField';
  223. else
  224. $inputField='activeTextField';
  225. if($column->type!=='string' || $column->size===null)
  226. return "CHtml::{$inputField}(\$model,'{$column->name}')";
  227. else
  228. {
  229. if(($size=$maxLength=$column->size)>60)
  230. $size=60;
  231. return "CHtml::{$inputField}(\$model,'{$column->name}',array('size'=>$size,'maxlength'=>$maxLength))";
  232. }
  233. }
  234. }
  235. public function generateActiveLabel($modelClass,$column)
  236. {
  237. return "\$form->labelEx(\$model,'{$column->name}')";
  238. }
  239. public function generateActiveField($modelClass,$column)
  240. {
  241. if($column->type==='boolean')
  242. return "\$form->checkBox(\$model,'{$column->name}')";
  243. elseif(stripos($column->dbType,'text')!==false)
  244. return "\$form->textArea(\$model,'{$column->name}',array('rows'=>6, 'cols'=>50))";
  245. else
  246. {
  247. if(preg_match('/^(password|pass|passwd|passcode)$/i',$column->name))
  248. $inputField='passwordField';
  249. else
  250. $inputField='textField';
  251. if($column->type!=='string' || $column->size===null)
  252. return "\$form->{$inputField}(\$model,'{$column->name}')";
  253. else
  254. {
  255. if(($size=$maxLength=$column->size)>60)
  256. $size=60;
  257. return "\$form->{$inputField}(\$model,'{$column->name}',array('size'=>$size,'maxlength'=>$maxLength))";
  258. }
  259. }
  260. }
  261. public function guessNameColumn($columns)
  262. {
  263. foreach($columns as $column)
  264. {
  265. if(!strcasecmp($column->name,'name'))
  266. return $column->name;
  267. }
  268. foreach($columns as $column)
  269. {
  270. if(!strcasecmp($column->name,'title'))
  271. return $column->name;
  272. }
  273. foreach($columns as $column)
  274. {
  275. if($column->isPrimaryKey)
  276. return $column->name;
  277. }
  278. return 'id';
  279. }
  280. public function class2id($className)
  281. {
  282. return trim(strtolower(str_replace('_','-',preg_replace('/(?<![A-Z])[A-Z]/', '-\0', $className))),'-');
  283. }
  284. public function class2name($className,$pluralize=false)
  285. {
  286. if($pluralize)
  287. $className=$this->pluralize($className);
  288. return ucwords(trim(strtolower(str_replace(array('-','_'),' ',preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $className)))));
  289. }
  290. }