<?php declare(strict_types=1); namespace Meibuyu\Micro\Command; use Hyperf\Command\Annotation\Command; use Hyperf\Command\Command as HyperfCommand; use Hyperf\Database\ConnectionResolverInterface; use Hyperf\Database\Schema\MySqlBuilder; use Hyperf\DbConnection\Db; use Hyperf\Utils\Str; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; /** * @Command */ class MakeModelCommand extends HyperfCommand { /** * @var ContainerInterface */ protected $container; private $path = ''; private $appPath = ''; private $builder = null; private $table = ""; private $tableIndex = 0; private $tables = []; private $allTableStructures = []; private $currentTableStructure = []; private $cc = [ 'bigint' => 'integer', 'int' => 'integer', 'tinyint' => 'integer', 'smallint' => 'integer', 'mediumint' => 'integer', 'integer' => 'integer', 'numeric' => 'integer', 'float' => 'float', 'real' => 'real', 'double' => 'double', 'decimal' => 'decimal', 'bool' => 'bool', 'boolean' => 'boolean', 'char' => 'string', 'tinytext' => 'string', 'text' => 'string', 'mediumtext' => 'string', 'longtext' => 'string', 'year' => 'string', 'varchar' => 'string', 'string' => 'string', 'object' => 'object', 'enum' => 'object', 'set' => 'object', 'date' => 'date', 'datetime' => 'datetime', 'custom_datetime' => 'custom_datetime', 'timestamp' => 'timestamp', 'collection' => 'collection', 'array' => 'array', 'json' => 'json', ]; public function __construct(ContainerInterface $container) { $this->container = $container; $this->path = __DIR__ . "/stubs/"; $this->appPath = BASE_PATH . '/app/'; $this->builder = $this->getSchemaBuilder(); parent::__construct('mm'); } /** * 获取数据库创建者 * @return MySqlBuilder */ protected function getSchemaBuilder(): MySqlBuilder { $resolver = $this->container->get(ConnectionResolverInterface::class); $connection = $resolver->connection(); return $connection->getSchemaBuilder(); } public function handle() { $this->initTableStructures(); /*$this->info(print_r($this->allTableStructures, true)); return false;*/ $tables = []; $isAll = $this->input->getOption('database'); $table = $this->input->getArgument('name'); if (!$table && !$isAll) { $this->error("请输入表名进行单表生成 或者 -d 参数 进行全库生成,或使用 --help查看帮助"); return false; } if ($this->input->getOption('database')) { $tables = $this->tables; } else { $tables[] = $this->getTable(); } foreach ($tables as $k => $v) { if ($v == 'migrations') { continue; } $this->tableIndex = $k; $this->input->setArgument("name", $v); $this->table = $v; $this->currentTableStructure = $this->allTableStructures[$v]; $this->info("开始生成:" . $v); if (!Str::contains($v, "_to_") && ($this->input->getOption('model') || $this->input->getOption('all'))) { $this->makeModel(); } if (!Str::contains($v, "_to_") && ($this->input->getOption('controller') || $this->input->getOption('all'))) { $this->makeRepositoryInterface(); $this->makeRepository(); $this->makeController(); } if (!Str::contains($v, "_to_") && ($this->input->getOption('validator') || $this->input->getOption('all'))) { $this->makeValidator(); } if (!Str::contains($v, "_to_") && $this->input->getOption('seeder')) { $this->makeSeeder(); } if ($this->input->getOption('migrate') || $this->input->getOption('all')) { $this->makeMigrate(); } } } /** *获取表结构 */ private function initTableStructures() { //tables $tables = Db::select("select table_name,`engine`,table_comment from information_schema.tables where table_schema=database()"); $tables = array_map('get_object_vars', $tables); $tables = collect($tables)->keyBy("table_name")->toArray(); //fields $fields = Db::select("select table_name,column_name,column_default,is_nullable,data_type,collation_name,column_type,column_key,extra,column_comment from information_schema.columns where table_schema=database()"); $fieldList = array_map('get_object_vars', $fields); //对字段数据进行处理 foreach ($fieldList as $fk => $fv) { $fieldList[$fk]['length'] = trim(str_replace([$fv['data_type'], "(", ")"], "", $fv['column_type'])); //针对层级处理 if ($fv['column_name'] == 'parent_id' || $fv['column_name'] == 'pid') { $relation = []; $relation['function'] = "parent"; $relation['relation_model_name'] = Str::studly(Str::singular($fv['table_name'])); $relation['relation_table'] = $fv['table_name']; $relation['relation_table_key'] = "id"; $relation['local_table'] = $fv['table_name']; $relation['local_table_key'] = $fv['column_name']; $tables[$relation['local_table']]['relations']['belongsTo'][] = $relation; $reverseRelation = []; $reverseRelation['function'] = 'children'; $reverseRelation['relation_model_name'] = $relation['relation_model_name']; $reverseRelation['relation_table'] = $relation['local_table']; $reverseRelation['relation_table_key'] = $relation['local_table_key']; $reverseRelation['local_table'] = $relation['relation_table']; $reverseRelation['local_table_key'] = $relation['relation_table_key']; $tables[$reverseRelation['relation_table']]['relations']['hasMany'][] = $reverseRelation; } else if (Str::endsWith($fv['column_name'], '_id')) { //关联表处理 if (Str::contains($fv['table_name'], "_to_")) { $ts = explode("_to_", $fv['table_name']); $ts = collect($ts)->map(function ($item) { return Str::plural($item); })->all(); //$this->info(print_r($ts, true)); $relation = []; if (Str::singular($ts[0]) . "_id" == $fv['column_name']) { $relation['constraint_table'] = $ts[1]; $relation['constraint_table_key'] = Str::singular($ts[1]) . "_id"; $relation['local_table'] = $ts[0]; $relation['local_table_key'] = $fv['column_name']; } else { $relation['constraint_table'] = $ts[0]; $relation['constraint_table_key'] = Str::singular($ts[0]) . "_id"; $relation['local_table'] = $ts[1]; $relation['local_table_key'] = $fv['column_name']; } $relation['relation_table'] = $fv['table_name']; $relation['function'] = Str::snake($relation['constraint_table']); $relation['relation_model_name'] = Str::studly(Str::singular($relation['constraint_table'])); $tables[$relation['local_table']]['relations']['belongsToMany'][] = $relation; } else { $relation = []; $relation['relation_table'] = Str::plural(Str::replaceLast("_id", "", $fv['column_name'])); if (!isset($tables[$relation['relation_table']])) { continue; } $relation['function'] = Str::snake(Str::singular($relation['relation_table'])); $relation['relation_model_name'] = Str::studly(Str::singular($relation['relation_table'])); $relation['relation_table_key'] = "id"; $relation['local_table'] = $fv['table_name']; $relation['local_table_key'] = $fv['column_name']; $tables[$relation['local_table']]['relations']['belongsTo'][] = $relation; $reverseRelation = []; $reverseRelation['relation_table'] = $relation['local_table']; $reverseRelation['relation_table_key'] = $relation['local_table_key']; $reverseRelation['local_table'] = $relation['relation_table']; $reverseRelation['local_table_key'] = $relation['relation_table_key']; $reverseRelation['function'] = Str::snake($reverseRelation['relation_table']); $reverseRelation['relation_model_name'] = Str::studly(Str::singular($reverseRelation['relation_table'])); $tables[$reverseRelation['local_table']]['relations']['hasMany'][] = $reverseRelation; } } } $fields = collect($fieldList)->groupBy("table_name")->toArray(); //constraints $constraints = Db::select("select a.constraint_name,a.table_name,b.column_name,a.referenced_table_name, b.referenced_column_name,a.update_rule,a.delete_rule from information_schema.referential_constraints a,information_schema.key_column_usage b where a.constraint_schema=database() and a.constraint_schema=b.constraint_schema and a.table_name=b.table_name and a.constraint_name=b.constraint_name"); $constraints = array_map('get_object_vars', $constraints); $constraints = collect($constraints)->groupBy("table_name")->toArray(); //$indexes $indexes = Db::select("select table_name,non_unique,index_name,column_name from information_schema.statistics where table_schema=database()"); $indexes = array_map('get_object_vars', $indexes); $indexes = collect($indexes)->groupBy("table_name")->toArray(); foreach ($tables as $k => $v) { $v['fields'] = isset($fields[$k]) ? $fields[$k] : []; $v['constraints'] = isset($constraints[$k]) ? $constraints[$k] : []; if (isset($indexes[$k])) { $v['indexes'] = collect($indexes[$k])->groupBy("index_name")->toArray(); } else { $v['indexes'] = []; } $tables[$k] = $v; } $this->resortTable($tables); $this->allTableStructures = $tables; } private function resortTable($tables) { $done = false; $tables = $tables; while (!$done) { foreach ($tables as $k => $v) { if (!$v['constraints']) { $this->tables[] = $k; unset($tables[$k]); } else { $p = true; foreach ($v['constraints'] as $cs) { if (!in_array($cs['referenced_table_name'], $this->tables) && $cs['referenced_table_name'] != $k) { $p = false; break; } } if ($p) { $this->tables[] = $k; unset($tables[$k]); } } } if (empty($tables)) { $done = true; } } } /** * 获取当前数据库表名 * @return string */ private function getTable(): string { return Str::lower(trim($this->input->getArgument('name'))); } private function makeModel() { $stubFile = $this->path . 'model.stub'; $folder = $this->appPath . 'Model'; $this->makeFolder($folder); $table = $this->table; $modelName = Str::studly(Str::singular($table)); $file = $folder . "/" . $modelName . ".php"; $content = file_get_contents($stubFile); $info = $this->currentTableStructure; $filterFields = ["id", "created_at", "updated_at", "deleted_at"]; $fillAble = ''; $properties = ''; $casts = ''; $timestamps = 0; $softDelete = false; $list = $info['fields']; foreach ($list as $k => $v) { $name = $v['column_name']; $pc = [ 'bigint' => 'integer', 'int' => 'integer', 'tinyint' => 'integer', 'smallint' => 'integer', 'mediumint' => 'integer', 'integer' => 'integer', 'numeric' => 'integer', 'float' => 'float', 'real' => 'double', 'double' => 'double', 'decimal' => 'double', 'bool' => 'bool', 'boolean' => 'bool', 'char' => 'string', 'tinytext' => 'string', 'text' => 'string', 'mediumtext' => 'string', 'longtext' => 'string', 'year' => 'string', 'varchar' => 'string', 'string' => 'string', 'enum' => 'array', 'set' => 'array', 'date' => '\Carbon\Carbon', 'datetime' => '\Carbon\Carbon', 'custom_datetime' => '\Carbon\Carbon', 'timestamp' => '\Carbon\Carbon', 'collection' => 'collection', 'array' => 'array', 'json' => 'string', ]; $properties .= " * @property " . (isset($pc[$v['data_type']]) ? $pc[$v['data_type']] : "string") . " $" . $name . ($v['column_comment'] ? " " . $v['column_comment'] : "") . "\n"; if ($name == 'created_at' || $name == 'updated_at') { $casts .= "\t\t'" . $name . "'=>'datetime',\n"; $timestamps++; } if ($name == 'deleted_at') { $casts .= "\t\t'" . $name . "'=>'datetime',\n"; $softDelete = true; } if (in_array($name, $filterFields)) { continue; } $fillAble .= "\t\t'" . $name . "'," . "\n"; if (isset($this->cc[$v['data_type']]) && $this->cc[$v['data_type']] != 'string') { if ($this->cc[$v['data_type']] == 'timestamp') { $casts .= "\t\t'" . $name . "'=>'datetime',\n"; } else if ($this->cc[$v['data_type']] == 'decimal') { $casts .= "\t\t'" . $name . "'=>'float',\n"; } else { $casts .= "\t\t'" . $name . "'=>'" . $this->cc[$v['data_type']] . "',\n"; } } } $relation = ''; if (isset($info['relations']) && $info['relations']) { $relation .= "\n"; if (isset($info['relations']['belongsTo'])) { foreach ($info['relations']['belongsTo'] as $v) { $relation .= "\n\t/**\n\t* 属于" . $v['relation_model_name'] . "的关联\n\t*/"; $relation .= "\n\tpublic function " . $v['function'] . "()"; $relation .= "\n\t{"; $relation .= "\n\t\t" . 'return $this->belongsTo(' . $v['relation_model_name'] . "::class,'" . $v['local_table_key'] . "','{$v['relation_table_key']}' );"; $relation .= "\n\t}"; $properties .= " * @property " . $v['relation_model_name'] . " $" . $v['function'] . "\n"; } } if (isset($info['relations']['belongsToMany'])) { foreach ($info['relations']['belongsToMany'] as $v) { $relation .= "\n\t/**\n\t* 属于很多" . $v['relation_model_name'] . "的关联"; $relation .= "\n\t* @return \Hyperf\Database\Model\Relations\BelongsToMany"; $relation .= "\n\t**/"; $relation .= "\n\tpublic function " . $v['function'] . "()"; $relation .= "\n\t{"; $relation .= "\n\t\t" . 'return $this->belongsToMany(' . $v['relation_model_name'] . "::class,'" . $v['relation_table'] . "','{$v['constraint_table_key']}','{$v['local_table_key']}');"; $relation .= "\n\t}"; $properties .= " * @property " . $v['relation_model_name'] . "[] $" . $v['function'] . "\n"; } } if (isset($info['relations']['hasMany'])) { foreach ($info['relations']['hasMany'] as $v) { $relation .= "\n\t/**\n\t* 有很多" . $v['relation_model_name'] . "的关联"; $relation .= "\n\t* @return \Hyperf\Database\Model\Relations\HasMany"; $relation .= "\n\t**/"; $relation .= "\n\tpublic function " . $v['function'] . "()"; $relation .= "\n\t{"; $relation .= "\n\t\t" . 'return $this->hasMany(' . $v['relation_model_name'] . "::class,'" . $v['relation_table_key'] . "','" . $v['local_table_key'] . "' );"; $relation .= "\n\t}"; $properties .= " * @property " . $v['relation_model_name'] . "[] $" . $v['function'] . "\n"; } } } $sd = ''; $sdn = ''; if ($softDelete) { $sd = "use SoftDeletes;\n"; $sdn = "use Hyperf\Database\Model\SoftDeletes;\n"; } $patterns = ['%namespace%', "%ClassName%", "%fillAble%", "%casts%", '%relations%', '%timestamps%', '%properties%', '%SoftDelete%']; $replacements = [$sdn, $modelName, $fillAble, $casts, $relation, ($timestamps == 2 ? 'true' : 'false'), $properties, $sd]; $content = $this->buildField($patterns, $replacements, $content); $this->writeToFile($file, $content); } /** * 创建目录 * @param $folder */ private function makeFolder($folder) { if (!file_exists($folder)) { @mkdir($folder, 0777, true); } } /** * 替换文件内容 * @param array $patterns 被替换的字符数组 * @param array $replacements 替换的字符数组 * @param string $content 文件原始内容 * @return string */ private function buildField(array $patterns, array $replacements, string $content): string { $author = $this->input->getOption("author"); $patterns = array_merge($patterns, ['%user%', '%date%', '%time%']); $replacements = array_merge($replacements, [$author ? $author : "Auto generated.", date("Y-m-d"), date("h:i:s")]); return str_replace($patterns, $replacements, $content); } /** * 把内容写入文件 * @param string $file 文件路径和文件名 * @param string $content 文件内容 * @return bool 创建是否成功 */ private function writeToFile(string $file, string $content): bool { $force = $this->input->getOption("force"); if (!$force && file_exists($file)) { return false; } file_put_contents($file, $content); $file = pathinfo($file, PATHINFO_FILENAME); $this->info("<info>[INFO] Created File:</info> {$file}"); return true; } private function makeRepositoryInterface() { $stubFile = $this->path . 'repositoryInterface.stub'; $folder = $this->appPath . '/Repository/Interfaces'; $this->makeFolder($folder); $table = $this->table; $className = Str::studly(Str::singular($table)) . "Repository"; $file = $folder . "/" . $className . ".php"; $content = file_get_contents($stubFile); $patterns = ["%ClassName%"]; $replacements = [$className]; $content = $this->buildField($patterns, $replacements, $content); $this->writeToFile($file, $content); } private function makeRepository() { $stubFile = $this->path . 'repository.stub'; $folder = $this->appPath . '/Repository/Eloquent'; $this->makeFolder($folder); $table = $this->table; $modelClass = Str::studly(Str::singular($table)); $className = $modelClass . "RepositoryEloquent"; $file = $folder . "/" . $className . ".php"; $content = file_get_contents($stubFile); $info = $this->currentTableStructure; //列表 $list = "\$conditions = \$this->request->all();\n"; $list .= "\t\t\$list = \$this->model->where(function (\$q) use (\$conditions) {\n"; foreach ($info['fields'] as $v) { if (Str::endsWith($v['column_name'], "_id")) { $list .= "\t\t\tif(\$conditions['" . $v['column_name'] . "'] !== '') {\n"; $list .= "\t\t\t\t\$q->where('" . $v['column_name'] . "', \$conditions['" . $v['column_name'] . "']);\n"; $list .= "\t\t\t}\n"; } else if ($v['column_name'] == 'name' || Str::contains($v['column_name'], "_name")) { $list .= "\t\t\tif(\$conditions['keyword'] !== '') {\n"; $list .= "\t\t\t\t\$q->where('" . $v['column_name'] . "', \$conditions['keyword']);\n"; $list .= "\t\t\t}\n"; } } $list .= "\t\t})"; //显示 $show = "\$info = \$this->model\n"; //新增 $create = "/** @var {$modelClass} \$model */\n \t\t\t\$model = parent::create(\$attributes);\n"; //新增 $update = "/** @var {$modelClass} \$model */\n \t\t\t\$model = parent::update(\$attributes, \$id);\n"; //删除 $delete = ''; //关联查询 $rs = ""; if (isset($info['relations']) && $info['relations']) { if (isset($info['relations']) && $info['relations']) { if (isset($info['relations']['belongsTo'])) { $list .= "\n\t\t->with(["; $show .= "\n\t\t->with(["; $t = []; foreach ($info['relations']['belongsTo'] as $k => $v) { $x = "\n\t\t\t'" . $v['function'] . "' => function (\$q) {\n"; $fields = $this->listColumns($v['relation_table']); $fields = collect($fields['fields'])->keyBy('column_name') ->forget(['created_at', 'updated_at', 'deleted_at'])->pluck('column_name')->toArray(); $fields = join("','", $fields); $x .= "\t\t\t\t\$q->select(['" . $fields . "']);\n"; $x .= "\t\t\t}"; $t[] = $x; } $t = join(",", $t) . "])"; $list .= $t; $show .= $t; } if (isset($info['relations']['belongsToMany'])) { $list .= "\n\t\t->with(["; $show .= "\n\t\t->with(["; $t = []; foreach ($info['relations']['belongsToMany'] as $v) { $x = "\n\t\t\t'" . $v['function'] . "' => function (\$q) {\n"; $fields = $this->listColumns($v['constraint_table']); $fields = collect($fields['fields'])->keyBy('column_name') ->forget(['created_at', 'updated_at', 'deleted_at'])->pluck('column_name')->toArray(); $fields = join("','", $fields); $x .= "\t\t\t\t\$q->select(['" . $fields . "'])->orderByDesc('id')->limit(20);\n"; $x .= "\t\t\t}"; $t[] = $x; $create .= "\t\t\tisset(\$attributes['" . $v['constraint_table_key'] . "s']) && \$model->{$v['function']}()->sync(\$attributes['" . $v['constraint_table_key'] . "s']);\n"; $update .= "\t\t\tisset(\$attributes['" . $v['constraint_table_key'] . "s']) && \$model->{$v['function']}()->sync(\$attributes['" . $v['constraint_table_key'] . "s']);\n"; } $t = join(",", $t) . "])"; $list .= $t; $show .= $t; } if (isset($info['relations']['hasMany'])) { foreach ($info['relations']['hasMany'] as $v) { $f = Str::camel($v['function']); $rs .= "\n\tpublic function {$f}(\$id): array\n"; $rs .= "\t{\n"; $rs .= "\t\t\$pageSize = (int)\$this->request->input('page_size', DEFAULT_PAGE_SIZE);\n"; $rs .= "\t\treturn \$this->find(\$id)->{$v['function']}()->orderByDesc('id')->paginate(\$pageSize)->toArray();\n"; $rs .= "\t}\n"; } } } } $list .= "\n\t\t->paginate(\$pageSize)\n"; $list .= "\t\t->toArray();\n"; $list .= "\t\treturn \$list;"; $show .= "\n\t\t->find(\$id)\n\t\t->toArray();\n"; $show .= "\t\treturn \$info;"; $patterns = ["%ModelClass%", "%list%", "%show%", "%create%", "%update%", "%delete%", "%rs%"]; $replacements = [$modelClass, $list, $show, $create, $update, $delete, $rs]; $content = $this->buildField($patterns, $replacements, $content); $this->writeToFile($file, $content); $this->addDepends($modelClass); } private function listColumns($table) { $table = trim($table); $table = isset($this->allTableStructures[$table]) ? $this->allTableStructures[$table] : []; return $table; } /** * 添加类到依赖注入配置文件 * @param $modelClass * @return bool */ private function addDepends($modelClass) { $file = BASE_PATH . '/config/autoload/dependencies.php'; if (file_exists($file)) { $content = file_get_contents($file); if (strpos($content, "\App\Repository\Interfaces\\" . $modelClass . "Repository::class") !== false) { return true; } $content = str_replace("]", " \\App\\Repository\\Interfaces\\" . $modelClass . "Repository::class => \\App\\Repository\\Eloquent\\" . $modelClass . "RepositoryEloquent::class,\n]", $content); $this->writeToFile($file, $content); } } private function makeController() { $stubFile = $this->path . 'controller.stub'; $folder = $this->appPath . '/Controller'; $this->makeFolder($folder); $table = $this->table; $modelClass = Str::studly(Str::singular($table)); $className = $modelClass . "Controller"; $file = $folder . "/" . $className . ".php"; $content = file_get_contents($stubFile); $info = $this->currentTableStructure; //关联查询 $rs = ""; $routes = []; if (isset($info['relations']) && $info['relations']) { if (isset($info['relations']) && $info['relations']) { if (isset($info['relations']['hasMany'])) { foreach ($info['relations']['hasMany'] as $v) { $f = Str::camel($v['function']); $rs .= "\n\t/**"; $rs .= "\n\t * 获取{$v['relation_model_name']}关联列表数据"; $rs .= "\n\t * @Perm(\"index\")"; $rs .= "\n\t * @param; \$id id编号"; $rs .= "\n\t * @return mixed"; $rs .= "\n\t */"; $rs .= "\n\tpublic function {$f}(\$id)\n"; $rs .= "\t{\n"; $rs .= "\t\t\$data = \$this->repository->{$f}(\$id);\n"; $rs .= "\t\treturn success('获取成功', \$data);\n"; $rs .= "\t}\n"; $routes[] = $v['function']; } } } } $patterns = ["%ModelClass%", "%rs%"]; $replacements = [$modelClass, $rs]; $content = $this->buildField($patterns, $replacements, $content); $this->writeToFile($file, $content); $this->addRoutes($modelClass, $routes); } /**添加Controller到路由类 * @param $modelClass * @return bool */ private function addRoutes($modelClass, $routes = []) { $file = BASE_PATH . '/config/routes.php'; if (file_exists($file)) { $table = $this->table; $content = file_get_contents($file); $group = str_replace("_", "/", Str::snake($table)); if (strpos($content, "Router::addGroup('" . $group . "', function () {") !== false) { return true; } $info = $this->currentTableStructure; $tableComment = $info['table_comment'] ? $info['table_comment'] : $table; $content .= "\n\t// " . $tableComment; $content .= "\n\tRouter::addGroup('" . $group . "', function () {"; $content .= "\n\t\tRouter::get('', 'App\Controller\\" . $modelClass . "Controller@index');"; $content .= "\n\t\tRouter::get('/{id}', 'App\Controller\\" . $modelClass . "Controller@show');"; $content .= "\n\t\tRouter::post('', 'App\Controller\\" . $modelClass . "Controller@create');"; $content .= "\n\t\tRouter::patch('/{id}', 'App\Controller\\" . $modelClass . "Controller@update');"; $content .= "\n\t\tRouter::delete('/{id}', 'App\Controller\\" . $modelClass . "Controller@delete');"; if ($routes) { foreach ($routes as $v) { $content .= "\n\t\tRouter::get('/$r/\{id\}', 'App\Controller\\" . $modelClass . "Controller@$v');"; } } $content .= "\n\t});"; $this->writeToFile($file, $content); } } /** * 创建验证文件 */ private function makeValidator() { $stubFile = $this->path . 'validator.stub'; $folder = $this->appPath . '/Validators'; $this->makeFolder($folder); $table = $this->table; $modelClass = Str::studly(Str::singular($table)); $className = $modelClass . "Validator"; $file = $folder . "/" . $className . ".php"; $content = file_get_contents($stubFile); $info = $this->currentTableStructure; $filterFields = ["id", "created_at", "updated_at", "deleted_at"]; $rules = ''; $attributes = ''; $messages = []; $list = $info['fields']; foreach ($list as $v) { $name = $v['column_name']; $default = $v['column_default']; $type = $v['data_type']; $key = $v['column_key']; $null = $v['is_nullable']; // $extra = $v['extra']; $comment = $v['column_comment']; $length = $v['length']; $msgName = ($comment ? $comment : $name); if (in_array($name, $filterFields)) { continue; } $rs = []; $required = "nullable"; if ($null !== 'YES') { if (!$default) { $required = "required"; $messages[] = "\t\t'{$name}.{$required}' => '{$msgName}不能为空!'"; } } $rs[] = $required; switch ($type) { case "bigint": case "smallint": case "tinyint": case "mediumint": case "int": case "integer": $rs[] = 'integer'; $messages[] = "\t\t'{$name}.integer' => '{$msgName}只能是整数!'"; break; case "decimal": case "double": case "float": case "numeric": case "real": $rs[] = 'numeric'; $messages[] = "\t\t'{$name}.numeric' => '{$msgName}只能是数字支持小数!'"; break; case "char": case "varchar": case "tinytext": case "mediumtext": case "longtext": case "text": $rs[] = 'string'; if ($length) { $rs[] = 'max:' . $length; $messages[] = "\t\t'{$name}.max' => '{$msgName}字符长度不能超过{$length}!'"; } break; case "date": case "datetime": case "time": case "timestamp": case "year": $rs[] = 'date'; $messages[] = "\t\t'{$name}.date' => '{$msgName}不符合日期时间格式!'"; break; case "enum": case "set": $rs[] = 'in:[' . $length . "]"; $messages[] = "\t\t'{$name}.in' => '{$msgName}的值只能在[{$length}]列表中!'"; break; default: if (Str::contains($name, "email") || Str::contains($name, "e-mail") || Str::contains($name, "e_mail")) { $rs[] = 'email'; $messages[] = "\t\t'{$name}.email' => '{$msgName}只支持邮箱格式!'"; } elseif ($name == 'url' || Str::contains($name, "_url") || Str::contains($name, "url_")) { $rs[] = 'url'; $messages[] = "\t\t'{$name}.email' => '{$msgName}只支持url格式!'"; } elseif ($name == 'date' || Str::contains($name, "_date") || Str::contains($name, "date_")) { $rs[] = 'date'; $messages[] = "\t\t'{$name}.email' => '{$msgName}不符合日期时间格式!'"; } break; } if ($key == 'uni') { $rs[] = "unique:$table," . $name; $messages[] = "\t\t'{$name}.unique' => '{$msgName}的值在数据库中已经存在!'"; } if ($comment) { $attributes .= "\t\t'" . $name . "' => '" . $comment . "'," . "\n"; } $rules .= "\t\t\t'" . $name . "' => '" . implode("|", $rs) . "'," . ($comment ? "// " . $comment . "-" . $type : "//" . $type) . "\n"; } $messages = join(",\n", $messages); $patterns = ["%ModelClass%", '%createRules%', '%updateRules%', '%attributes%', '%messages%']; $createRules = $rules; $updateRules = str_replace("nullable", "sometimes|nullable", $rules); $updateRules = str_replace("required", "sometimes|required", $updateRules); $replacements = [$modelClass, $createRules, $updateRules, $attributes, $messages]; $content = $this->buildField($patterns, $replacements, $content); $this->writeToFile($file, $content); } private function makeSeeder() { $stubFile = $this->path . 'seeder.stub'; $folder = BASE_PATH . '/seeders/seeders/'; $this->makeFolder($folder); $table = $this->table; $modelClass = Str::studly(Str::singular($table)); $className = Str::studly($table) . "TableSeeder"; $file = $folder . "/" . $table . "_table_seeder.php"; $content = file_get_contents($stubFile); $info = $this->currentTableStructure; $filterFields = ["id"]; $fields = []; $otherModel = []; $generateCount = $this->input->getOption("seeder"); $generateCount = $generateCount ? $generateCount : 30; $otherProcess = ""; $list = $info['fields']; $maxNumber = 4; foreach ($list as $v) { $name = $v['column_name']; $type = $v['data_type']; $nullAble = ($v['is_nullable'] !== 'YES'); $length = explode(" ", $v['length']); if (in_array($name, $filterFields)) { continue; } switch ($type) { case "bigint": case "smallint": case "tinyint": case "mediumint": case "int": case "integer": if ($name == "sex") { $fields[] = "\t\t\t\t'{$name}' => \$faker->randomElement([0,1]),"; } else if (Str::contains($name, "status")) { $fields[] = "\t\t\t\t'{$name}' => \$faker->randomDigit,"; } else if (Str::endsWith($name, "_id")) { $o = str_replace("_id", "", $name); $os = Str::plural($o); if (in_array($os, $this->tables)) { $o = Str::studly($o); $otherModel[] = "\nuse App\Model\\" . $o . ";"; $fields[] = "\t\t\t\t'{$name}' => $o::orderBy(Db::raw('rand()'))->first()->id,"; } else { $n = ((isset($length[0]) && $length[0] && $length[0] < $maxNumber) ? $length[0] : $maxNumber); $n = rand(1, $n); $fields[] = "\t\t\t\t'{$name}' => \$faker->randomNumber($n),"; } } else { $n = ((isset($length[0]) && $length[0] < $maxNumber) ? $length[0] : $maxNumber); $n = rand(1, $n); $fields[] = "\t\t\t\t'{$name}' => \$faker->randomNumber($n),"; } break; case "decimal": case "double": case "float": case "numeric": case "real": $n = ((isset($length[0]) && $length[0] && $length[0] < $maxNumber) ? $length[0] : $maxNumber); $n = rand(1, $n); $n2 = ((isset($length[1]) && $length[1] < $maxNumber) ? $length[1] : 2); $fields[] = "\t\t\t\t'{$name}' => \$faker->randomFloat($n,$n2),"; break; case "char": case "varchar": $n = ((isset($length[0]) && $length[0]) ? $length[0] : 255); if ($name == "ip" || $name == "ip" || $name == "ip_address" || $name == "ip_addr") { $fields[] = "\t\t\t\t'{$name}' => \$faker->ipv4,"; } else if (Str::contains($name, "email")) { $fields[] = "\t\t\t\t'{$name}' => \$faker->email,"; } else if ($name == "userName" || $name == "user_name" || $name == "uname") { $fields[] = "\t\t\t\t'{$name}' => \$faker->userName,"; } else if ($name == "url" || $name == "domain" || Str::endsWith($name, "_url") || Str::startsWith($name, "url_")) { $fields[] = "\t\t\t\t'{$name}' => \$faker->url,"; } else if ($name == "company" || $name == "company_name") { $fields[] = "\t\t\t\t'{$name}' => \$faker->company,"; } else if ($name == "gender") { $fields[] = "\t\t\t\t'{$name}' => \$faker->title(),"; } else if ($name == "name") { $fields[] = "\t\t\t\t'{$name}' => \$faker->name(),"; } else if (Str::contains($name, "city")) { $fields[] = "\t\t\t\t'{$name}' => \$faker->city,"; } else if (Str::contains($name, "street") || Str::contains($name, "address")) { $fields[] = "\t\t\t\t'{$name}' => \$faker->streetAddress,"; } else if (Str::contains($name, "postcode")) { $fields[] = "\t\t\t\t'{$name}' => \$faker->postcode,"; } else if (Str::contains($name, "country")) { $fields[] = "\t\t\t\t'{$name}' => \$faker->country,"; } else if (Str::contains($name, "phoneNumber") || $name == "tel" || $name == "mobile" || Str::contains($name, "phone")) { $fields[] = "\t\t\t\t'{$name}' => \$faker->phoneNumber,"; } else if (Str::contains($name, "color")) { $fields[] = "\t\t\t\t'{$name}' => \$faker->colorName,"; } else if (Str::contains($name, "image") || Str::contains($name, "path")) { $fields[] = "\t\t\t\t'{$name}' => \$faker->imageUrl(640, 480),"; } else if ($name == "ean" || $name == "bar_code") { $fields[] = "\t\t\t\t'{$name}' => \$faker->ean13,"; } else if ($n < 10) { $fields[] = "\t\t\t\t'{$name}' => \$faker->word,"; } elseif ($n < 100) { $fields[] = "\t\t\t\t'{$name}' => \$faker->sentence(6),"; } else { $n = rand(2, 5); $fields[] = "\t\t\t\t'{$name}' => \$faker->paragraph($n, true),"; } break; case "tinytext": case "mediumtext": case "longtext": case "text": $fields[] = "\t\t\t\t'{$name}' => \$faker->text,"; break; case "date": $fields[] = "\t\t\t\t'{$name}' => \$faker->date('Y-m-d'),"; break; case "datetime": $fields[] = "\t\t\t\t'{$name}' => \$faker->date('Y-m-d').' '.\$faker->time('H:i:s'),"; break; case "time": $fields[] = "\t\t\t\t'{$name}' => \$faker->time('H:i:s'),"; break; case "timestamp": if ($name == 'created_at' || $name == 'updated_at' || $name == 'deleted_at') { $fields[] = "\t\t\t\t'{$name}' => \$faker->date('Y-m-d').' '.\$faker->time('H:i:s'),"; } else { $fields[] = "\t\t\t\t'{$name}' => \$faker->unixTime(),"; } break; case "year": $fields[] = "\t\t\t\t'{$name}' => \$faker->year(),"; break; case "enum": case "set": $n = implode(",", $length); $fields[] = "\t\t\t\t'{$name}' => \$faker->randomElement([$n]),"; break; default: $fields[] = "\t\t\t\t'{$name}' => \$faker->word,"; break; } } $fields = join("\n", $fields); $otherModel = join("", $otherModel); //if(isset($info[''])) $patterns = ["%modelClass%", '%className%', '%otherModel%', '%generateCount%', '%fields%', '%otherProcess%']; $replacements = [$modelClass, $className, $otherModel, $generateCount, $fields, $otherProcess]; $content = $this->buildField($patterns, $replacements, $content); $this->writeToFile($file, $content); $this->addSeeder(); } private function addSeeder() { $stubFile = $this->path . 'databaseSeeder.stub'; $folder = BASE_PATH . '/seeders/'; $file = $folder . "/DatabaseSeeder.php"; if (!file_exists($file)) { $content = file_get_contents($stubFile); $this->writeToFile($file, $content); } $content = file_get_contents($file); if (strpos($content, Str::studly($this->table) . "TableSeeder::class") !== false) { return true; } $content = str_replace("];", "\t\t\t" . Str::studly($this->table) . "TableSeeder::class,\n\t\t];", $content); $this->writeToFile($file, $content); } private function makeMigrate() { $stubFile = $this->path . 'migration.stub'; $folder = BASE_PATH . '/migrations'; $this->makeFolder($folder); $table = $this->table; $className = "Create" . Str::studly($table) . "Table"; $file = $folder . "/" . $this->getDatePrefix() . "_" . $this->tableIndex . "_create_" . $table . "_table.php"; $content = file_get_contents($stubFile); $info = $this->currentTableStructure; $attributes = []; $timestamps = 0; //$b = new Blueprint('test'); $softDelete = false; $pri = false; //生成字段 foreach ($info['fields'] as $v) { $name = $v['column_name']; $default = $v['column_default']; $type = $v['data_type']; $collation = $v['collation_name']; $key = $v['column_key']; $null = $v['is_nullable']; $extra = $v['extra']; $comment = $v['column_comment']; $length = $v['length']; if ($name == 'updated_at' || $name == 'created_at') { $timestamps++; } elseif ($name == 'deleted_at') { $softDelete = true; } else { $t = "\t\t\t\$table->"; switch ($type) { case "bigint": case "smallint": case "tinyint": case "mediumint": case "int": case "integer": if ($type == 'int' || $type == 'integer') { $t .= "integer('" . $name . "'"; } else { $t .= str_replace("int", "", $type) . "Integer('" . $name . "'"; } if ($extra == 'auto_increment') { $pri = true; $t .= ", true"; } else { $t .= ", false"; } if ($length && strpos($length, "unsigned") !== false) { $t .= ", true"; } $t .= ")"; break; case "decimal": case "double": case "float": case "numeric": case "real": $tc = [ 'numeric' => 'decimal', 'real' => 'double', ]; $t .= (isset($tc[$type]) ? $tc[$type] : $type) . "('" . $name . "'"; if ($length) { $length = explode(" ", $length); $length = explode(",", $length[0]); $t .= ", " . $length[0] . ", " . $length[1]; } $t .= ")"; break; case "char": case "varchar": case "tinytext": case "mediumtext": case "longtext": case "desc": case "bit": case "boolean": case "text": $tc = [ 'char' => 'char', 'varchar' => 'string', 'desc' => 'text', 'tinytext' => 'text', 'text' => 'text', 'bit' => 'boolean', 'boolean' => 'boolean', 'longtext' => 'longText', 'mediumtext' => 'mediumText', ]; $t .= $tc[$type] . "('" . $name . "'"; if ($length) { $t .= ", " . $length; } $t .= ")"; break; case "date": case "datetime": case "time": case "timestamp": case "year": $tc = [ 'datetime' => 'dateTime', ]; $t .= (isset($tc[$type]) ? $tc[$type] : $type) . "('" . $name . "')"; break; case "binary": case "varbinary": case "longblob": case "blob": case "mediumblob": case "tinyblob": $t .= "binary('" . $name . "')"; break; case "enum": case "set": $tc = [ 'set' => 'enum', ]; $t .= (isset($tc[$type]) ? $tc[$type] : $type) . "('" . $name . "'"; $t .= ", [{ $length}])"; break; case "geometry": case "geometrycollection": case "json": case "jsonb": case "point": case "polygon": case "linestring": case "multipoint": case "multipolygon": case "multilinestring": $tc = [ 'geometrycollection' => 'geometryCollection', 'linestring' => 'lineString', 'multipoint' => 'multiPoint', 'multipolygon' => 'multiPolygon', 'multilinestring' => 'multiLineString', ]; $t .= (isset($tc[$type]) ? $tc[$type] : $type) . "('" . $name . "'"; if ($length) { $t .= ", " . $length; } $t .= ")"; break; default: $t = ''; break; } if ($t) { if ($null == 'YES') { $t .= "->nullable()"; } if ($default || $default === '0') { $t .= "->default('$default')"; } if ($collation) { $t .= "->collation('$collation')"; } if ($comment) { $t .= "->comment('$comment')"; } $t .= ";"; $attributes[] = $t; } } } if ($timestamps == 2) { $attributes[] = "\t\t\t\$table->timestamps();"; } if ($softDelete) { $attributes[] = "\t\t\t\$table->softDeletes();"; } //主键及索引 if ($info['indexes']) { foreach ($info['indexes'] as $k => $v) { $fields = collect($v)->pluck("column_name")->all(); $fields = implode("','", $fields); if ($k == 'PRIMARY') { if (!$pri) { $attributes[] = "\t\t\t\$table->primary(['{$fields}']);"; } } else { if ($v[0]['non_unique'] === 0) { $attributes[] = "\t\t\t\$table->unique(['{$fields}'], '{$k}');"; } else { $attributes[] = "\t\t\t\$table->index(['{$fields}'], '{$k}');"; } } } } //外键 if ($info['constraints']) { foreach ($info['constraints'] as $k => $v) { $t = "\t\t\t\$table->foreign('{$v['column_name']}','{$v['constraint_name']}')->references('{$v['referenced_column_name']}')->on('{$v['referenced_table_name']}')"; if ($v['delete_rule']) { $t .= "->onDelete('{$v['delete_rule']}')"; } /*if ($v['update_rule']) { $t .= "->onUpdate('{$v['update_rule']}')"; }*/ $attributes[] = $t . ";"; } } $attributes = implode("\n", $attributes); $tableComment = ""; if ($info['table_comment']) { $tableComment = 'Db::statement("alter table `' . $table . '` comment \'' . $info['table_comment'] . '\'");'; } $patterns = ["%ClassName%", '%tablename%', '%attributes%', '%tableComment%']; $replacements = [$className, $table, $attributes, $tableComment]; $content = $this->buildField($patterns, $replacements, $content); $this->writeToFile($file, $content); } /**生成迁移文件的日期格式 * @return string */ private function getDatePrefix(): string { return date('Y_m_dHis'); } public function configure() { //$this->call() parent::configure(); $this->addOption('all', 'a', InputOption::VALUE_NONE, '生成所有文件'); $this->addOption('model', 'm', InputOption::VALUE_NONE, '生成model文件'); $this->addOption('controller', 'c', InputOption::VALUE_NONE, '生成controller文件'); $this->addOption('migrate', 'i', InputOption::VALUE_NONE, '生成迁移文件'); $this->addOption('validator', 'l', InputOption::VALUE_NONE, '生成验证文件'); $this->addOption('author', 'r', InputOption::VALUE_OPTIONAL, '文件作者,后面可跟空格名字,表示生成的文件作者'); $this->addOption('seeder', 's', InputOption::VALUE_OPTIONAL, '生成数据填充文件,后面可跟空格数字,表示生成的数据量'); $this->addOption('force', 'f', InputOption::VALUE_NONE, '文件存在是否覆盖'); $this->addOption('database', 'd', InputOption::VALUE_NONE, '全数据库索引自动生成全站文件'); $this->setDescription('根据数据表生成model文件和迁移文件和控制器'); } /** * 配置文件内容 * @return array */ protected function getArguments() { return [ ['name', InputArgument::OPTIONAL, '数据库表名'], ]; } }