* @link http://www.yiiframework.com/ * @copyright 2008-2013 Yii Software LLC * @license http://www.yiiframework.com/license/ */ /** * CrudCommand generates code implementing CRUD operations. * * @author Qiang Xue * @package system.cli.commands.shell * @since 1.0 */ class CrudCommand extends CConsoleCommand { /** * @var string the directory that contains templates for crud commands. * Defaults to null, meaning using 'framework/cli/views/shell/crud'. * If you set this path and some views are missing in the directory, * the default views will be used. */ public $templatePath; /** * @var string the directory that contains functional test classes. * Defaults to null, meaning using 'protected/tests/functional'. * If this is false, it means functional test file should NOT be generated. */ public $functionalTestPath; /** * @var array list of actions to be created. Each action must be associated with a template file with the same name. */ public $actions=array('create','update','index','view','admin','_form','_view','_search'); public function getHelp() { return << [controller-ID] ... DESCRIPTION This command generates a controller and views that accomplish CRUD operations for the specified data model. PARAMETERS * model-class: required, the name of the data model class. This can also be specified as a path alias (e.g. application.models.Post). If the model class belongs to a module, it should be specified as 'ModuleID.models.ClassName'. * controller-ID: optional, the controller ID (e.g. 'post'). If this is not specified, the model class name will be used as the controller ID. In this case, if the model belongs to a module, the controller will also be created under the same module. If the controller should be located under a subdirectory, please specify the controller ID as 'path/to/ControllerID' (e.g. 'admin/user'). If the controller belongs to a module (different from the module that the model belongs to), please specify the controller ID as 'ModuleID/ControllerID' or 'ModuleID/path/to/Controller'. EXAMPLES * Generates CRUD for the Post model: crud Post * Generates CRUD for the Post model which belongs to module 'admin': crud admin.models.Post * Generates CRUD for the Post model. The generated controller should belong to module 'admin', but not the model class: crud Post admin/post EOD; } /** * Execute the action. * @param array $args command line parameters specific for this command * @return integer|null non zero application exit code for help or null on success */ public function run($args) { if(!isset($args[0])) { echo "Error: data model class is required.\n"; echo $this->getHelp(); return 1; } $module=Yii::app(); $modelClass=$args[0]; if(($pos=strpos($modelClass,'.'))===false) $modelClass='application.models.'.$modelClass; else { $id=substr($modelClass,0,$pos); if(($m=Yii::app()->getModule($id))!==null) $module=$m; } $modelClass=Yii::import($modelClass); if(isset($args[1])) { $controllerID=$args[1]; if(($pos=strrpos($controllerID,'/'))===false) { $controllerClass=ucfirst($controllerID).'Controller'; $controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.$controllerClass.'.php'; $controllerID[0]=strtolower($controllerID[0]); } else { $last=substr($controllerID,$pos+1); $last[0]=strtolower($last); $pos2=strpos($controllerID,'/'); $first=substr($controllerID,0,$pos2); $middle=$pos===$pos2?'':substr($controllerID,$pos2+1,$pos-$pos2); $controllerClass=ucfirst($last).'Controller'; $controllerFile=($middle===''?'':$middle.'/').$controllerClass.'.php'; $controllerID=$middle===''?$last:$middle.'/'.$last; if(($m=Yii::app()->getModule($first))!==null) $module=$m; else { $controllerFile=$first.'/'.$controllerFile; $controllerID=$first.'/'.$controllerID; } $controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.str_replace('/',DIRECTORY_SEPARATOR,$controllerFile); } } else { $controllerID=$modelClass; $controllerClass=ucfirst($controllerID).'Controller'; $controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.$controllerClass.'.php'; $controllerID[0]=strtolower($controllerID[0]); } $templatePath=$this->templatePath===null?YII_PATH.'/cli/views/shell/crud':$this->templatePath; $functionalTestPath=$this->functionalTestPath===null?Yii::getPathOfAlias('application.tests.functional'):$this->functionalTestPath; $viewPath=$module->viewPath.DIRECTORY_SEPARATOR.str_replace('.',DIRECTORY_SEPARATOR,$controllerID); $fixtureName=$this->pluralize($modelClass); $fixtureName[0]=strtolower($fixtureName); $list=array( basename($controllerFile)=>array( 'source'=>$templatePath.'/controller.php', 'target'=>$controllerFile, 'callback'=>array($this,'generateController'), 'params'=>array($controllerClass,$modelClass), ), ); if($functionalTestPath!==false) { $list[$modelClass.'Test.php']=array( 'source'=>$templatePath.'/test.php', 'target'=>$functionalTestPath.DIRECTORY_SEPARATOR.$modelClass.'Test.php', 'callback'=>array($this,'generateTest'), 'params'=>array($controllerID,$fixtureName,$modelClass), ); } foreach($this->actions as $action) { $list[$action.'.php']=array( 'source'=>$templatePath.'/'.$action.'.php', 'target'=>$viewPath.'/'.$action.'.php', 'callback'=>array($this,'generateView'), 'params'=>$modelClass, ); } $this->copyFiles($list); if($module instanceof CWebModule) $moduleID=$module->id.'/'; else $moduleID=''; echo "\nCrud '{$controllerID}' has been successfully created. You may access it via:\n"; echo "http://hostname/path/to/index.php?r={$moduleID}{$controllerID}\n"; } public function generateController($source,$params) { list($controllerClass,$modelClass)=$params; $model=CActiveRecord::model($modelClass); $id=$model->tableSchema->primaryKey; if($id===null) throw new ShellException(Yii::t('yii','Error: Table "{table}" does not have a primary key.',array('{table}'=>$model->tableName()))); elseif(is_array($id)) 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()))); if(!is_file($source)) // fall back to default ones $source=YII_PATH.'/cli/views/shell/crud/'.basename($source); return $this->renderFile($source,array( 'ID'=>$id, 'controllerClass'=>$controllerClass, 'modelClass'=>$modelClass, ),true); } public function generateView($source,$modelClass) { $model=CActiveRecord::model($modelClass); $table=$model->getTableSchema(); $columns=$table->columns; if(!is_file($source)) // fall back to default ones $source=YII_PATH.'/cli/views/shell/crud/'.basename($source); return $this->renderFile($source,array( 'ID'=>$table->primaryKey, 'modelClass'=>$modelClass, 'columns'=>$columns),true); } public function generateTest($source,$params) { list($controllerID,$fixtureName,$modelClass)=$params; if(!is_file($source)) // fall back to default ones $source=YII_PATH.'/cli/views/shell/crud/'.basename($source); return $this->renderFile($source, array( 'controllerID'=>$controllerID, 'fixtureName'=>$fixtureName, 'modelClass'=>$modelClass, ),true); } public function generateInputLabel($modelClass,$column) { return "CHtml::activeLabelEx(\$model,'{$column->name}')"; } public function generateInputField($modelClass,$column) { if($column->type==='boolean') return "CHtml::activeCheckBox(\$model,'{$column->name}')"; elseif(stripos($column->dbType,'text')!==false) return "CHtml::activeTextArea(\$model,'{$column->name}',array('rows'=>6, 'cols'=>50))"; else { if(preg_match('/^(password|pass|passwd|passcode)$/i',$column->name)) $inputField='activePasswordField'; else $inputField='activeTextField'; if($column->type!=='string' || $column->size===null) return "CHtml::{$inputField}(\$model,'{$column->name}')"; else { if(($size=$maxLength=$column->size)>60) $size=60; return "CHtml::{$inputField}(\$model,'{$column->name}',array('size'=>$size,'maxlength'=>$maxLength))"; } } } public function generateActiveLabel($modelClass,$column) { return "\$form->labelEx(\$model,'{$column->name}')"; } public function generateActiveField($modelClass,$column) { if($column->type==='boolean') return "\$form->checkBox(\$model,'{$column->name}')"; elseif(stripos($column->dbType,'text')!==false) return "\$form->textArea(\$model,'{$column->name}',array('rows'=>6, 'cols'=>50))"; else { if(preg_match('/^(password|pass|passwd|passcode)$/i',$column->name)) $inputField='passwordField'; else $inputField='textField'; if($column->type!=='string' || $column->size===null) return "\$form->{$inputField}(\$model,'{$column->name}')"; else { if(($size=$maxLength=$column->size)>60) $size=60; return "\$form->{$inputField}(\$model,'{$column->name}',array('size'=>$size,'maxlength'=>$maxLength))"; } } } public function guessNameColumn($columns) { foreach($columns as $column) { if(!strcasecmp($column->name,'name')) return $column->name; } foreach($columns as $column) { if(!strcasecmp($column->name,'title')) return $column->name; } foreach($columns as $column) { if($column->isPrimaryKey) return $column->name; } return 'id'; } public function class2id($className) { return trim(strtolower(str_replace('_','-',preg_replace('/(?pluralize($className); return ucwords(trim(strtolower(str_replace(array('-','_'),' ',preg_replace('/(?