From a9991d4e916e23360195b1a091c33d4cf0ab7e96 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 7 Sep 2013 08:18:41 -0400 Subject: [PATCH] CRUD WIP --- framework/yii/base/Module.php | 5 +- framework/yii/gii/Generator.php | 22 ++ framework/yii/gii/assets/gii.js | 5 + framework/yii/gii/generators/crud/Generator.php | 103 +++++---- framework/yii/gii/generators/crud/form.php | 7 +- .../gii/generators/crud/templates/controller.php | 236 +++++++++------------ .../gii/generators/crud/templates/views/create.php | 40 ++-- .../gii/generators/crud/templates/views/update.php | 44 ++-- .../gii/generators/crud/templates/views/view.php | 48 ++--- 9 files changed, 256 insertions(+), 254 deletions(-) diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index 1b7bf5b..de75ce7 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -338,10 +338,9 @@ abstract class Module extends Component /** * Retrieves the named module. - * @param string $id module ID (case-sensitive) + * @param string $id module ID (case-sensitive). * @param boolean $load whether to load the module if it is not yet loaded. - * @return Module|null the module instance, null if the module - * does not exist. + * @return Module|null the module instance, null if the module does not exist. * @see hasModule() */ public function getModule($id, $load = true) diff --git a/framework/yii/gii/Generator.php b/framework/yii/gii/Generator.php index f79b428..eb5b8b4 100644 --- a/framework/yii/gii/Generator.php +++ b/framework/yii/gii/Generator.php @@ -325,6 +325,28 @@ abstract class Generator extends Model } /** + * An inline validator that checks if the attribute value refers to a valid namespaced class name. + * The validator will check if the directory containing the new class file exist or not. + * @param string $attribute the attribute being validated + * @param array $params the validation options + */ + public function validateNewClass($attribute, $params) + { + $class = ltrim($this->$attribute, '\\'); + if (($pos = strrpos($class, '\\')) === false) { + $this->addError($attribute, "The class name must contain fully qualified namespace name."); + } else { + $ns = substr($class, 0, $pos); + $path = Yii::getAlias('@' . str_replace('\\', '/', $ns), false); + if ($path === false) { + $this->addError($attribute, "The class namespace is invalid: $ns"); + } elseif (!is_dir($path)) { + $this->addError($attribute, "Please make sure the directory containing this class exists: $path"); + } + } + } + + /** * @param string $value the attribute to be validated * @return boolean whether the value is a reserved PHP keyword. */ diff --git a/framework/yii/gii/assets/gii.js b/framework/yii/gii/assets/gii.js index b581d3b..45951a1 100644 --- a/framework/yii/gii/assets/gii.js +++ b/framework/yii/gii/assets/gii.js @@ -81,6 +81,11 @@ yii.gii = (function ($) { $('#model-generator .field-generator-modelclass').toggle($(this).val().indexOf('*') == -1); }).change(); + // crud generator: hide Search Model Class input if search is not enabled + $('#crud-generator #generator-enablesearch').on('change', function () { + $('#crud-generator .field-generator-searchmodelclass').toggle(this.checked); + }).change(); + // hide Generate button if any input is changed $('.default-view .form-group input,select,textarea').change(function () { $('.default-view-results,.default-view-files').hide(); diff --git a/framework/yii/gii/generators/crud/Generator.php b/framework/yii/gii/generators/crud/Generator.php index 983dbc7..3ed51c1 100644 --- a/framework/yii/gii/generators/crud/Generator.php +++ b/framework/yii/gii/generators/crud/Generator.php @@ -22,7 +22,8 @@ use yii\web\Controller; class Generator extends \yii\gii\Generator { public $modelClass; - public $controllerID; + public $moduleID; + public $controllerClass; public $baseControllerClass = 'yii\web\Controller'; public $indexWidgetType = 'grid'; public $enableSearch = true; @@ -42,16 +43,18 @@ class Generator extends \yii\gii\Generator public function rules() { return array_merge(parent::rules(), array( - array('modelClass, searchModelClass, controllerID, baseControllerClass', 'filter', 'filter' => 'trim'), - array('modelClass, controllerID, baseControllerClass', 'required'), - array('modelClass, searchModelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), + array('moduleID, controllerClass, modelClass, searchModelClass, baseControllerClass', 'filter', 'filter' => 'trim'), + array('modelClass, controllerClass, baseControllerClass, indexWidgetType', 'required'), + array('modelClass, controllerClass, baseControllerClass, searchModelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), array('modelClass', 'validateClass', 'params' => array('extends' => ActiveRecord::className())), - array('controllerID', 'match', 'pattern' => '/^[a-z\\-\\/]*$/', 'message' => 'Only a-z, dashes (-) and slashes (/) are allowed.'), - array('baseControllerClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), array('baseControllerClass', 'validateClass', 'params' => array('extends' => Controller::className())), + array('controllerClass', 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'), + array('controllerClass, searchModelClass', 'validateNewClass'), array('enableSearch', 'boolean'), array('indexWidgetType', 'in', 'range' => array('grid', 'list')), + array('modelClass', 'validateModelClass'), array('searchModelClass', 'validateSearchModelClass'), + array('moduleID', 'validateModuleID'), )); } @@ -59,7 +62,8 @@ class Generator extends \yii\gii\Generator { return array_merge(parent::attributeLabels(), array( 'modelClass' => 'Model Class', - 'controllerID' => 'Controller ID', + 'moduleID' => 'Module ID', + 'controllerClass' => 'Controller Class', 'baseControllerClass' => 'Base Controller Class', 'indexWidgetType' => 'Widget Used in Index Page', 'enableSearch' => 'Enable Search', @@ -75,15 +79,12 @@ class Generator extends \yii\gii\Generator return array( 'modelClass' => 'This is the ActiveRecord class associated with the table that CRUD will be built upon. You should provide a fully qualified class name, e.g., app\models\Post.', - 'controllerID' => 'CRUD controllers are often named after the model class name that they are dealing with. - Controller ID should be in lower case and may contain module ID(s) separated by slashes. For example: - ', + 'controllerClass' => 'This is the name of the controller class to be generated. You should + provide a fully qualified namespaced class, .e.g, app\controllers\PostController.', 'baseControllerClass' => 'This is the class that the new CRUD controller class will extend from. You should provide a fully qualified class name, e.g., yii\web\Controller.', + 'moduleID' => 'This is the ID of the module that the generated controller will belong to. + If not set, it means the controller will belong to the application.', 'indexWidgetType' => 'This is the widget type to be used in the index page to display list of the models. You may choose either GridView or ListView', 'enableSearch' => 'Whether to enable the search functionality on the index page. When search is enabled, @@ -106,7 +107,17 @@ class Generator extends \yii\gii\Generator */ public function stickyAttributes() { - return array('baseControllerClass', 'indexWidgetType', 'enableSearch'); + return array('baseControllerClass', 'moduleID', 'indexWidgetType', 'enableSearch'); + } + + public function validateModelClass() + { + /** @var ActiveRecord $class */ + $class = $this->modelClass; + $pk = $class::primaryKey(); + if (empty($pk)) { + $this->addError('modelClass', "The table associated with $class must have primary key(s)."); + } } public function validateSearchModelClass() @@ -116,6 +127,16 @@ class Generator extends \yii\gii\Generator } } + public function validateModuleID() + { + if (!empty($this->moduleID)) { + $module = Yii::$app->getModule($this->moduleID); + if ($module === null) { + $this->addError('moduleID', "Module '{$this->moduleID}' does not exist."); + } + } + } + /** * @inheritdoc */ @@ -130,6 +151,9 @@ class Generator extends \yii\gii\Generator $templatePath = $this->getTemplatePath() . '/views'; foreach (scandir($templatePath) as $file) { + if (!in_array($file, array('create.php', 'update.php', 'view.php'))) { + continue; + } if (is_file($templatePath . '/' . $file) && pathinfo($file, PATHINFO_EXTENSION) === 'php') { $files[] = new CodeFile("$viewPath/$file", $this->render("views/$file")); } @@ -142,39 +166,14 @@ class Generator extends \yii\gii\Generator return $files; } - - /** - * @return string the controller class name without the namespace part. - */ - public function getControllerClass() - { - return Inflector::id2camel($this->getControllerID()) . 'Controller'; - } - /** * @return string the controller ID (without the module ID prefix) */ public function getControllerID() { - if (($pos = strrpos($this->controllerID, '/')) !== false) { - return substr($this->controllerID, $pos + 1); - } else { - return $this->controllerID; - } - } - - /** - * @return \yii\base\Module the module that the new controller belongs to - */ - public function getModule() - { - if (($pos = strpos($this->controllerID, '/')) !== false) { - $id = substr($this->controllerID, 0, $pos); - if (($module = Yii::$app->getModule($id)) !== null) { - return $module; - } - } - return Yii::$app; + $pos = strrpos($this->controllerClass, '\\'); + $class = substr(substr($this->controllerClass, $pos + 1), 0, -10); + return Inflector::camel2id($class); } /** @@ -182,8 +181,7 @@ class Generator extends \yii\gii\Generator */ public function getControllerFile() { - $module = $this->getModule(); - return $module->getControllerPath() . '/' . $this->getControllerClass() . '.php'; + return Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->controllerClass, '\\')) . '.php'); } /** @@ -191,7 +189,20 @@ class Generator extends \yii\gii\Generator */ public function getViewPath() { - $module = $this->getModule(); + $module = empty($this->moduleID) ? Yii::$app : Yii::$app->getModule($this->moduleID); return $module->getViewPath() . '/' . $this->getControllerID() ; } + + public function getNameAttribute() + { + /** @var \yii\db\ActiveRecord $class */ + $class = $this->modelClass; + foreach ($class::getTableSchema()->columnNames as $name) { + if (!strcasecmp($name, 'name') || !strcasecmp($name, 'title')) { + return $name; + } + } + $pk = $class::primaryKey(); + return $pk[0]; + } } diff --git a/framework/yii/gii/generators/crud/form.php b/framework/yii/gii/generators/crud/form.php index 095791f..ee4771b 100644 --- a/framework/yii/gii/generators/crud/form.php +++ b/framework/yii/gii/generators/crud/form.php @@ -6,11 +6,12 @@ */ echo $form->field($generator, 'modelClass'); -echo $form->field($generator, 'controllerID'); +echo $form->field($generator, 'controllerClass'); echo $form->field($generator, 'baseControllerClass'); +echo $form->field($generator, 'moduleID'); +echo $form->field($generator, 'enableSearch')->checkbox(); +echo $form->field($generator, 'searchModelClass'); echo $form->field($generator, 'indexWidgetType')->dropDownList(array( 'grid' => 'GridView', 'list' => 'ListView', )); -echo $form->field($generator, 'enableSearch')->checkbox(); -echo $form->field($generator, 'searchModelClass'); diff --git a/framework/yii/gii/generators/crud/templates/controller.php b/framework/yii/gii/generators/crud/templates/controller.php index 47c193f..7b3cce8 100644 --- a/framework/yii/gii/generators/crud/templates/controller.php +++ b/framework/yii/gii/generators/crud/templates/controller.php @@ -1,180 +1,152 @@ - - -class controllerClass; ?> extends baseControllerClass."\n"; ?> -{ - /** - * @var string the default layout for the views. Defaults to '//layouts/column2', meaning - * using two-column layout. See 'protected/views/layouts/column2.php'. - */ - public $layout='//layouts/column2'; - /** - * @return array action filters - */ - public function filters() - { - return array( - 'accessControl', // perform access control for CRUD operations - 'postOnly + delete', // we only allow deletion via POST request - ); +$pos = strrpos($generator->controllerClass, '\\'); +$ns = ltrim(substr($generator->controllerClass, 0, $pos), '\\'); +$controllerClass = substr($generator->controllerClass, $pos + 1); +$pos = strrpos($generator->modelClass, '\\'); +$modelClass = $pos === false ? $generator->modelClass : substr($generator->modelClass, $pos + 1); + +/** @var \yii\db\ActiveRecord $class */ +$class = $generator->modelClass; +$pks = $class::primaryKey(); +$schema = $class::getTableSchema(); +if (count($pks) === 1) { + $ids = '$id'; + $params = "array('id' => \$model->{$pks[0]})"; + $paramComments = '@param ' . $schema->columns[$pks[0]]->phpType . ' $id'; +} else { + $ids = '$' . implode(', $', $pks); + $params = array(); + $paramComments = array(); + foreach ($pks as $pk) { + $paramComments[] = '@param ' . $schema->columns[$pk]->phpType . ' $' . $pk; + $params[] = "'$pk' => \$model->$pk"; } + $params = implode(', ', $params); + $paramComments = implode("\n\t * ", $paramComments); +} - /** - * Specifies the access control rules. - * This method is used by the 'accessControl' filter. - * @return array access control rules - */ - public function accessRules() - { - return array( - array('allow', // allow all users to perform 'index' and 'view' actions - 'actions'=>array('index','view'), - 'users'=>array('*'), - ), - array('allow', // allow authenticated user to perform 'create' and 'update' actions - 'actions'=>array('create','update'), - 'users'=>array('@'), - ), - array('allow', // allow admin user to perform 'admin' and 'delete' actions - 'actions'=>array('admin','delete'), - 'users'=>array('admin'), - ), - array('deny', // deny all users - 'users'=>array('*'), - ), - ); - } +echo " + +namespace ; + +use modelClass, '\\'); ?>; +use yii\data\ActiveDataProvider; +use baseControllerClass, '\\'); ?>; +use yii\web\HttpException; +/** + * implements the CRUD actions for model. + */ +class extends baseControllerClass) . "\n"; ?> +{ /** - * Displays a particular model. - * @param integer $id the ID of the model to be displayed + * Displays a single model. + * + * @return mixed */ - public function actionView($id) + public function actionView() { - $this->render('view',array( - 'model'=>$this->loadModel($id), + return $this->render('view', array( + 'model' => $this->findModel(), )); } /** - * Creates a new model. + * Creates a new model. * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed */ public function actionCreate() { - $model=new modelClass; ?>; - - // Uncomment the following line if AJAX validation is needed - // $this->performAjaxValidation($model); - - if(isset($_POST['modelClass; ?>'])) - { - $model->attributes=$_POST['modelClass; ?>']; - if($model->save()) - $this->redirect(array('view','id'=>$model->tableSchema->primaryKey; ?>)); + $model = new ; + + if ($model->load($_POST) && $model->save()) { + return $this->redirect(array('view', )); + } else { + return $this->render('create', array( + 'model' => $model, + )); } - - $this->render('create',array( - 'model'=>$model, - )); } /** - * Updates a particular model. + * Updates an existing model. * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id the ID of the model to be updated + * + * @return mixed */ - public function actionUpdate($id) + public function actionUpdate() { - $model=$this->loadModel($id); - - // Uncomment the following line if AJAX validation is needed - // $this->performAjaxValidation($model); - - if(isset($_POST['modelClass; ?>'])) - { - $model->attributes=$_POST['modelClass; ?>']; - if($model->save()) - $this->redirect(array('view','id'=>$model->tableSchema->primaryKey; ?>)); + $model = $this->findModel(); + + if ($model->load($_POST) && $model->save()) { + return $this->redirect(array('view', )); + } else { + return $this->render('update', array( + 'model' => $model, + )); } - - $this->render('update',array( - 'model'=>$model, - )); } /** - * Deletes a particular model. - * If deletion is successful, the browser will be redirected to the 'admin' page. - * @param integer $id the ID of the model to be deleted + * Deletes an existing model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * + * @return mixed */ - public function actionDelete($id) + public function actionDelete() { - $this->loadModel($id)->delete(); - - // if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser - if(!isset($_GET['ajax'])) - $this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin')); + $this->findModel()->delete(); + return $this->redirect(array('index')); } /** * Lists all models. + * @return mixed */ public function actionIndex() { - $dataProvider=new CActiveDataProvider('modelClass; ?>'); - $this->render('index',array( - 'dataProvider'=>$dataProvider, + $dataProvider = new ActiveDataProvider(''); + return $this->render('index', array( + 'dataProvider' => $dataProvider, )); } /** - * Manages all models. + * Returns the data model based on its primary key value. + * If the data model is not found, a 404 HTTP exception will be thrown. + * + * @return the loaded model + * @throws HttpException if the model cannot be found */ - public function actionAdmin() + protected function findModel() { - $model=new modelClass; ?>('search'); - $model->unsetAttributes(); // clear any default values - if(isset($_GET['modelClass; ?>'])) - $model->attributes=$_GET['modelClass; ?>']; - - $this->render('admin',array( - 'model'=>$model, - )); - } - - /** - * Returns the data model based on the primary key given in the GET variable. - * If the data model is not found, an HTTP exception will be raised. - * @param integer $id the ID of the model to be loaded - * @return modelClass; ?> the loaded model - * @throws CHttpException - */ - public function loadModel($id) - { - $model=modelClass; ?>::model()->findByPk($id); - if($model===null) - throw new CHttpException(404,'The requested page does not exist.'); - return $model; + \$$pk"; } - - /** - * Performs the AJAX validation. - * @param modelClass; ?> $model the model to be validated - */ - protected function performAjaxValidation($model) - { - if(isset($_POST['ajax']) && $_POST['ajax']==='class2id($this->modelClass); ?>-form') - { - echo CActiveForm::validate($model); - Yii::app()->end(); + $condition = 'array(' . implode(', ', $condition) . ')'; +} +?> + $model = ::find(); + if ($model === null) { + throw new HttpException(404, 'The requested page does not exist.'); } + return $model; } } diff --git a/framework/yii/gii/generators/crud/templates/views/create.php b/framework/yii/gii/generators/crud/templates/views/create.php index 9fd3ccf..48a2318 100644 --- a/framework/yii/gii/generators/crud/templates/views/create.php +++ b/framework/yii/gii/generators/crud/templates/views/create.php @@ -1,27 +1,31 @@ - -/* @var $this getControllerClass(); ?> */ -/* @var $model getModelClass(); ?> */ -pluralize($this->class2name($this->modelClass)); -echo "\$this->breadcrumbs=array( - '$label'=>array('index'), - 'Create', -);\n"; +echo " -$this->menu=array( - array('label'=>'List modelClass; ?>', 'url'=>array('index')), - array('label'=>'Manage modelClass; ?>', 'url'=>array('admin')), -); +use yii\helpers\Html; + +/** + * @var yii\base\View $this + * @var modelClass, '\\'); ?> $model + */ + +$this->title = 'Create modelClass)); ?>'; ?> +
+ +

echo Html::encode($this->title); ?>

-

Create modelClass; ?>

+ echo $this->render('_form', array( + 'model' => $model, + )); ?> -renderPartial('_form', array('model'=>\$model)); ?>"; ?> +
diff --git a/framework/yii/gii/generators/crud/templates/views/update.php b/framework/yii/gii/generators/crud/templates/views/update.php index 4adc72d..8dc3c73 100644 --- a/framework/yii/gii/generators/crud/templates/views/update.php +++ b/framework/yii/gii/generators/crud/templates/views/update.php @@ -1,31 +1,31 @@ - -/* @var $this getControllerClass(); ?> */ -/* @var $model getModelClass(); ?> */ -guessNameColumn($this->tableSchema->columns); -$label=$this->pluralize($this->class2name($this->modelClass)); -echo "\$this->breadcrumbs=array( - '$label'=>array('index'), - \$model->{$nameColumn}=>array('view','id'=>\$model->{$this->tableSchema->primaryKey}), - 'Update', -);\n"; +echo " -$this->menu=array( - array('label'=>'List modelClass; ?>', 'url'=>array('index')), - array('label'=>'Create modelClass; ?>', 'url'=>array('create')), - array('label'=>'View modelClass; ?>', 'url'=>array('view', 'id'=>$model->tableSchema->primaryKey; ?>)), - array('label'=>'Manage modelClass; ?>', 'url'=>array('admin')), -); +use yii\helpers\Html; + +/** +* @var yii\base\View $this +* @var modelClass, '\\'); ?> $model +*/ + +$this->title = 'Modify modelClass)); ?>: ' . $model->getNameAttribute(); ?>; ?> +
+ +

echo Html::encode($this->title); ?>

-

Update modelClass." {$this->tableSchema->primaryKey}; ?>"; ?>

+ echo $this->render('_form', array( + 'model' => $model, + )); ?> -renderPartial('_form', array('model'=>\$model)); ?>"; ?> \ No newline at end of file +
diff --git a/framework/yii/gii/generators/crud/templates/views/view.php b/framework/yii/gii/generators/crud/templates/views/view.php index 3363130..baca46f 100644 --- a/framework/yii/gii/generators/crud/templates/views/view.php +++ b/framework/yii/gii/generators/crud/templates/views/view.php @@ -1,39 +1,27 @@ - -/* @var $this getControllerClass(); ?> */ -/* @var $model getModelClass(); ?> */ -guessNameColumn($this->tableSchema->columns); -$label=$this->pluralize($this->class2name($this->modelClass)); -echo "\$this->breadcrumbs=array( - '$label'=>array('index'), - \$model->{$nameColumn}, -);\n"; +echo " -$this->menu=array( - array('label'=>'List modelClass; ?>', 'url'=>array('index')), - array('label'=>'Create modelClass; ?>', 'url'=>array('create')), - array('label'=>'Update modelClass; ?>', 'url'=>array('update', 'id'=>$model->tableSchema->primaryKey; ?>)), - array('label'=>'Delete modelClass; ?>', 'url'=>'#', 'linkOptions'=>array('submit'=>array('delete','id'=>$model->tableSchema->primaryKey; ?>),'confirm'=>'Are you sure you want to delete this item?')), - array('label'=>'Manage modelClass; ?>', 'url'=>array('admin')), -); -?> +use yii\helpers\Html; -

View modelClass." #{$this->tableSchema->primaryKey}; ?>"; ?>

+/** +* @var yii\base\View $this +* @var modelClass, '\\'); ?> $model +*/ - $this->widget('zii.widgets.CDetailView', array( - 'data'=>$model, - 'attributes'=>array( -tableSchema->columns as $column) - echo "\t\t'".$column->name."',\n"; +$this->title = $model->getNameAttribute(); ?>; ?> - ), -)); ?> +
+ +

echo Html::encode($this->title); ?>

+ +