You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
436 lines
12 KiB
436 lines
12 KiB
<?php |
|
/** |
|
* @link http://www.yiiframework.com/ |
|
* @copyright Copyright (c) 2008 Yii Software LLC |
|
* @license http://www.yiiframework.com/license/ |
|
*/ |
|
|
|
namespace yii\gii\generators\crud; |
|
|
|
use Yii; |
|
use yii\db\ActiveRecord; |
|
use yii\db\BaseActiveRecord; |
|
use yii\db\Schema; |
|
use yii\gii\CodeFile; |
|
use yii\helpers\Inflector; |
|
use yii\web\Controller; |
|
|
|
/** |
|
* |
|
* @author Qiang Xue <qiang.xue@gmail.com> |
|
* @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., <code>app\models\Post</code>.', |
|
'controllerClass' => 'This is the name of the controller class to be generated. You should |
|
provide a fully qualified namespaced class, .e.g, <code>app\controllers\PostController</code>.', |
|
'baseControllerClass' => 'This is the class that the new CRUD controller class will extend from. |
|
You should provide a fully qualified class name, e.g., <code>yii\web\Controller</code>.', |
|
'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 <code>GridView</code> or <code>ListView</code>', |
|
'searchModelClass' => 'This is the class representing the data being collected in the search form. |
|
A fully qualified namespaced class name is required, e.g., <code>app\models\search\PostSearch</code>.', |
|
]; |
|
} |
|
|
|
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() |
|
{ |
|
$labels = []; |
|
foreach ($this->getColumnNames() as $name) { |
|
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(); |
|
} |
|
} |
|
|
|
}
|
|
|