* @since 2.0 */ class Generator extends \yii\gii\Generator { public $modelClass; public $moduleID; public $controllerClass; public $baseControllerClass = 'yii\web\Controller'; public $indexWidgetType = 'grid'; public $searchModelClass; public function getName() { return 'CRUD Generator'; } public function getDescription() { return 'This generator generates a controller and views that implement CRUD (Create, Read, Update, Delete) operations for the specified data model.'; } public function rules() { return array_merge(parent::rules(), [ [['moduleID', 'controllerClass', 'modelClass', 'searchModelClass', 'baseControllerClass'], 'filter', 'filter' => 'trim'], [['modelClass', 'searchModelClass', 'controllerClass', 'baseControllerClass', 'indexWidgetType'], 'required'], [['searchModelClass'], 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'], [['modelClass', 'controllerClass', 'baseControllerClass', 'searchModelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], [['modelClass'], 'validateClass', 'params' => ['extends' => BaseActiveRecord::className()]], [['baseControllerClass'], 'validateClass', 'params' => ['extends' => Controller::className()]], [['controllerClass'], 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'], [['controllerClass', 'searchModelClass'], 'validateNewClass'], [['indexWidgetType'], 'in', 'range' => ['grid', 'list']], [['modelClass'], 'validateModelClass'], [['moduleID'], 'validateModuleID'], ]); } public function attributeLabels() { return array_merge(parent::attributeLabels(), [ 'modelClass' => 'Model Class', 'moduleID' => 'Module ID', 'controllerClass' => 'Controller Class', 'baseControllerClass' => 'Base Controller Class', 'indexWidgetType' => 'Widget Used in Index Page', 'searchModelClass' => 'Search Model Class', ]); } /** * @inheritdoc */ public function hints() { return [ '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.', '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', 'searchModelClass' => 'This is the class representing the data being collected in the search form. A fully qualified namespaced class name is required, e.g., app\models\search\PostSearch.', ]; } public function requiredTemplates() { return ['controller.php']; } /** * @inheritdoc */ public function stickyAttributes() { return ['baseControllerClass', 'moduleID', 'indexWidgetType']; } 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 validateModuleID() { if (!empty($this->moduleID)) { $module = Yii::$app->getModule($this->moduleID); if ($module === null) { $this->addError('moduleID', "Module '{$this->moduleID}' does not exist."); } } } /** * @inheritdoc */ public function generate() { $controllerFile = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->controllerClass, '\\')) . '.php'); $searchModel = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->searchModelClass, '\\') . '.php')); $files = [ new CodeFile($controllerFile, $this->render('controller.php')), new CodeFile($searchModel, $this->render('search.php')), ]; $viewPath = $this->getViewPath(); $templatePath = $this->getTemplatePath() . '/views'; foreach (scandir($templatePath) as $file) { if (is_file($templatePath . '/' . $file) && pathinfo($file, PATHINFO_EXTENSION) === 'php') { $files[] = new CodeFile("$viewPath/$file", $this->render("views/$file")); } } return $files; } /** * @return string the controller ID (without the module ID prefix) */ public function getControllerID() { $pos = strrpos($this->controllerClass, '\\'); $class = substr(substr($this->controllerClass, $pos + 1), 0, -10); return Inflector::camel2id($class); } /** * @return string the action view file path */ public function getViewPath() { $module = empty($this->moduleID) ? Yii::$app : Yii::$app->getModule($this->moduleID); return $module->getViewPath() . '/' . $this->getControllerID() ; } public function getNameAttribute() { foreach ($this->getColumnNames() as $name) { if (!strcasecmp($name, 'name') || !strcasecmp($name, 'title')) { return $name; } } /** @var \yii\db\ActiveRecord $class */ $class = $this->modelClass; $pk = $class::primaryKey(); return $pk[0]; } /** * @param string $attribute * @return string */ public function generateActiveField($attribute) { $tableSchema = $this->getTableSchema(); if ($tableSchema === false || !isset($tableSchema->columns[$attribute])) { if (preg_match('/^(password|pass|passwd|passcode)$/i', $attribute)) { return "\$form->field(\$model, '$attribute')->passwordInput()"; } else { return "\$form->field(\$model, '$attribute')"; } } $column = $tableSchema->columns[$attribute]; if ($column->phpType === 'boolean') { return "\$form->field(\$model, '$attribute')->checkbox()"; } elseif ($column->type === 'text') { return "\$form->field(\$model, '$attribute')->textarea(['rows' => 6])"; } else { if (preg_match('/^(password|pass|passwd|passcode)$/i', $column->name)) { $input = 'passwordInput'; } else { $input = 'textInput'; } if ($column->phpType !== 'string' || $column->size === null) { return "\$form->field(\$model, '$attribute')->$input()"; } else { return "\$form->field(\$model, '$attribute')->$input(['maxlength' => $column->size])"; } } } /** * @param string $attribute * @return string */ public function generateActiveSearchField($attribute) { $tableSchema = $this->getTableSchema(); if ($tableSchema === false) { return "\$form->field(\$model, '$attribute')"; } $column = $tableSchema->columns[$attribute]; if ($column->phpType === 'boolean') { return "\$form->field(\$model, '$attribute')->checkbox()"; } else { return "\$form->field(\$model, '$attribute')"; } } /** * @param \yii\db\ColumnSchema $column * @return string */ public function generateColumnFormat($column) { if ($column->phpType === 'boolean') { return 'boolean'; } elseif ($column->type === 'text') { return 'ntext'; } elseif (stripos($column->name, 'time') !== false && $column->phpType === 'integer') { return 'datetime'; } elseif (stripos($column->name, 'email') !== false) { return 'email'; } elseif (stripos($column->name, 'url') !== false) { return 'url'; } else { return 'text'; } } /** * Generates validation rules for the search model. * @return array the generated validation rules */ public function generateSearchRules() { if (($table = $this->getTableSchema()) === false) { return ["[['" . implode("', '", $this->getColumnNames()) . "'], 'safe']"]; } $types = []; foreach ($table->columns as $column) { switch ($column->type) { case Schema::TYPE_SMALLINT: case Schema::TYPE_INTEGER: case Schema::TYPE_BIGINT: $types['integer'][] = $column->name; break; case Schema::TYPE_BOOLEAN: $types['boolean'][] = $column->name; break; case Schema::TYPE_FLOAT: case Schema::TYPE_DECIMAL: case Schema::TYPE_MONEY: $types['number'][] = $column->name; break; case Schema::TYPE_DATE: case Schema::TYPE_TIME: case Schema::TYPE_DATETIME: case Schema::TYPE_TIMESTAMP: default: $types['safe'][] = $column->name; break; } } $rules = []; foreach ($types as $type => $columns) { $rules[] = "[['" . implode("', '", $columns) . "'], '$type']"; } return $rules; } public function getSearchAttributes() { return $this->getColumnNames(); } /** * Generates the attribute labels for the search model. * @return array the generated attribute labels (name => label) */ public function generateSearchLabels() { $model = new $this->modelClass(); $attributeLabels = $model->attributeLabels(); $labels = []; foreach ($this->getColumnNames() as $name) { if (isset($attributeLabels[$name])) { $labels[$name] = $attributeLabels[$name]; } else { if (!strcasecmp($name, 'id')) { $labels[$name] = 'ID'; } else { $label = Inflector::camel2words($name); if (strcasecmp(substr($label, -3), ' id') === 0) { $label = substr($label, 0, -3) . ' ID'; } $labels[$name] = $label; } } } return $labels; } public function generateSearchConditions() { $columns = []; if (($table = $this->getTableSchema()) === false) { $class = $this->modelClass; $model = new $class(); foreach ($model->attributes() as $attribute) { $columns[$attribute] = 'unknown'; } } else { foreach ($table->columns as $column) { $columns[$column->name] = $column->type; } } $conditions = []; foreach ($columns as $column => $type) { switch ($type) { case Schema::TYPE_SMALLINT: case Schema::TYPE_INTEGER: case Schema::TYPE_BIGINT: case Schema::TYPE_BOOLEAN: case Schema::TYPE_FLOAT: case Schema::TYPE_DECIMAL: case Schema::TYPE_MONEY: case Schema::TYPE_DATE: case Schema::TYPE_TIME: case Schema::TYPE_DATETIME: case Schema::TYPE_TIMESTAMP: $conditions[] = "\$this->addCondition(\$query, '{$column}');"; break; default: $conditions[] = "\$this->addCondition(\$query, '{$column}', true);"; break; } } return $conditions; } public function generateUrlParams() { /** @var ActiveRecord $class */ $class = $this->modelClass; $pks = $class::primaryKey(); if (count($pks) === 1) { return "'id' => \$model->{$pks[0]}"; } else { $params = []; foreach ($pks as $pk) { $params[] = "'$pk' => \$model->$pk"; } return implode(', ', $params); } } public function generateActionParams() { /** @var ActiveRecord $class */ $class = $this->modelClass; $pks = $class::primaryKey(); if (count($pks) === 1) { return '$id'; } else { return '$' . implode(', $', $pks); } } public function generateActionParamComments() { /** @var ActiveRecord $class */ $class = $this->modelClass; $pks = $class::primaryKey(); if (($table = $this->getTableSchema()) === false) { $params = []; foreach ($pks as $pk) { $params[] = '@param ' . (substr(strtolower($pk), -2) == 'id' ? 'integer' : 'string') . ' $' . $pk; } return $params; } if (count($pks) === 1) { return ['@param ' . $table->columns[$pks[0]]->phpType . ' $id']; } else { $params = []; foreach ($pks as $pk) { $params[] = '@param ' . $table->columns[$pk]->phpType . ' $' . $pk; } return $params; } } public function getTableSchema() { /** @var ActiveRecord $class */ $class = $this->modelClass; if (is_subclass_of($class, 'yii\db\ActiveRecord')) { return $class::getTableSchema(); } else { return false; } } public function getColumnNames() { /** @var ActiveRecord $class */ $class = $this->modelClass; if (is_subclass_of($class, 'yii\db\ActiveRecord')) { return $class::getTableSchema()->getColumnNames(); } else { $model = new $class(); return $model->attributes(); } } }