|
|
@@ -0,0 +1,228 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace UCore\Commands;
|
|
|
+
|
|
|
+use Illuminate\Console\Command;
|
|
|
+use Illuminate\Database\Eloquent\Model;
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
+use UCore\ModelCore;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 自动生成Eloquent模型属性注释
|
|
|
+ *
|
|
|
+ * 通过分析数据库表结构,自动生成模型属性的PHPDoc注释,
|
|
|
+ * 并提供fillable字段自动维护功能。
|
|
|
+ *
|
|
|
+ * 功能特性:
|
|
|
+ * 1. 自动识别字段类型并生成对应@property注释
|
|
|
+ * 2. 特殊时间字段自动识别为Carbon类型
|
|
|
+ * 3. 自动维护模型$attrlist属性包含所有字段
|
|
|
+ *
|
|
|
+ * 使用说明:
|
|
|
+ * 1. 在模型中添加标记注释:
|
|
|
+ * - 属性注释区域标记:在模型文件中添加 field start 和 field end 注释块
|
|
|
+ * - fillable字段标记:在模型文件中添加 attrlist start 和 attrlist end 注释块
|
|
|
+ *
|
|
|
+ * @package UCore\Commands
|
|
|
+
|
|
|
+ * @example php artisan ucore:generate-model-annotation
|
|
|
+ */
|
|
|
+class GenerateModelAnnotation extends Command
|
|
|
+{
|
|
|
+ /**
|
|
|
+ * The name and signature of the console command.
|
|
|
+ *
|
|
|
+ * @var string
|
|
|
+ */
|
|
|
+ protected $signature = 'ucore:generate-model-annotation';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The console command description.
|
|
|
+ *
|
|
|
+ * @var string
|
|
|
+ */
|
|
|
+ protected $description = '生成模型property注释,使得IDE有模型属性提示!';
|
|
|
+
|
|
|
+ private $fillable = [];
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 主执行方法
|
|
|
+ *
|
|
|
+ * 扫描指定目录下的模型文件并生成注释
|
|
|
+ *
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ public function handle()
|
|
|
+ {
|
|
|
+ // 扫描核心模型目录
|
|
|
+ $this->call1(app_path('../ucore'), '\App\Models\\');
|
|
|
+
|
|
|
+ // 扫描模块下的Models目录
|
|
|
+ $modulesPath = app_path('Module');
|
|
|
+ if (is_dir($modulesPath)) {
|
|
|
+ $modules = scandir($modulesPath);
|
|
|
+ foreach ($modules as $module) {
|
|
|
+ if ($module === '.' || $module === '..') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $modelsDir = "$modulesPath/$module/Models";
|
|
|
+ if (is_dir($modelsDir)) {
|
|
|
+ // 添加模块扫描日志
|
|
|
+ $this->info("扫描模块目录: $modelsDir");
|
|
|
+ // 修复模块命名空间格式
|
|
|
+ $namespace = "App\\Module\\$module\\Models\\";
|
|
|
+ // 统一目录分隔符
|
|
|
+ $namespace = str_replace('/', '\\', $namespace);
|
|
|
+ // 添加自动加载提示
|
|
|
+ $this->warn("请确保已配置composer自动加载: \"App\\Module\\$module\\Models\\\": \"app/Module/$module/Models/\"");
|
|
|
+ $this->call1($modelsDir, $namespace);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ... 保持原有call1、call2、getAnnotation方法不变 ...
|
|
|
+ /**
|
|
|
+ * 扫描模型目录
|
|
|
+ *
|
|
|
+ * @param string $dir 模型文件目录路径
|
|
|
+ * @param string $ns 模型类命名空间
|
|
|
+ */
|
|
|
+ public function call1($dir, $ns)
|
|
|
+ {
|
|
|
+ $this->info("扫描目录: $dir");
|
|
|
+ $list = scandir($dir);
|
|
|
+ foreach ($list as $item) {
|
|
|
+ if ($item === '.' || $item === '..') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $fullPath = $dir . '/' . $item;
|
|
|
+
|
|
|
+ // 递归处理子目录
|
|
|
+ if (is_dir($fullPath)) {
|
|
|
+ $this->call1($fullPath, $ns . $item . '\\');
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $p = strpos($item, '.php');
|
|
|
+ if ($p !== false) {
|
|
|
+ $model = substr($item, 0, $p);
|
|
|
+ // 修复命名空间拼接逻辑
|
|
|
+ $modelClass = rtrim($ns, '\\') . '\\' . $model;
|
|
|
+ // 修复文件路径拼接
|
|
|
+ $file = $fullPath;
|
|
|
+ $this->call2($model, $modelClass, $file);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理单个模型文件
|
|
|
+ *
|
|
|
+ * @param string $model 模型类名
|
|
|
+ * @param string $modelClass 完整模型类名
|
|
|
+ * @param string $file 模型文件路径
|
|
|
+ */
|
|
|
+ public function call2($model,$modelClass,$file)
|
|
|
+ {
|
|
|
+ if(class_exists($modelClass)){
|
|
|
+ /**
|
|
|
+ * @var ModelCore $model
|
|
|
+ */
|
|
|
+ $model = new $modelClass();
|
|
|
+ if($model instanceof Model){
|
|
|
+ $co = $model->getConnection();
|
|
|
+ $tTablePrefix = $co->getTablePrefix();
|
|
|
+ $table =$tTablePrefix.$model->getTable();
|
|
|
+ $this->info("table $table ");
|
|
|
+
|
|
|
+ $annotation = $this->getAnnotation($table,$co);
|
|
|
+
|
|
|
+ $pattern = '/field start[\s\S]+field end/';
|
|
|
+ $replacement = "field start ".$annotation." * field end";
|
|
|
+ $string = file_get_contents($file);
|
|
|
+ $result = preg_replace($pattern, $replacement, $string);
|
|
|
+
|
|
|
+ // 过滤系统默认字段
|
|
|
+ $filteredFillable = array_filter($this->fillable, function($field) {
|
|
|
+ return !in_array($field, ['created_at', 'updated_at', 'deleted_at']);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 格式化数组输出
|
|
|
+ $fillableContent = " protected \$fillable = [\n";
|
|
|
+ foreach ($filteredFillable as $field) {
|
|
|
+ $fillableContent .= " '{$field}',\n";
|
|
|
+ }
|
|
|
+ $fillableContent .= " ];";
|
|
|
+
|
|
|
+ $pattern2 = '/attrlist start[\s\S]+attrlist end/';
|
|
|
+ $replacement2 = "attrlist start \n{$fillableContent}\n // attrlist end";
|
|
|
+ $result = preg_replace($pattern2, $replacement2, $result);
|
|
|
+
|
|
|
+ if($result != $string){
|
|
|
+ $this->output->info(" model $modelClass file :$file annotation 成功 ");
|
|
|
+ file_put_contents($file,$result);
|
|
|
+ }else{
|
|
|
+ $this->output->warning(" model $modelClass file :$file table $table - 没有标识符 ");
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ $this->output->warning(" model $modelClass 不是继承 ModelBase");
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ $this->output->warning(" model $model 不存在");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成属性注释字符串
|
|
|
+ *
|
|
|
+ * @param string $tableName 数据库表名
|
|
|
+ * @param \Illuminate\Database\Connection $con 数据库连接
|
|
|
+ * @return string 生成的注释字符串
|
|
|
+ * @throws \Exception 当数据库查询失败时
|
|
|
+ */
|
|
|
+ public function getAnnotation($tableName,\Illuminate\Database\Connection $con)
|
|
|
+ {
|
|
|
+ $db = $con->getDatabaseName();
|
|
|
+ $fillable = [];
|
|
|
+ $sql = "SELECT * FROM INFORMATION_SCHEMA.COLUMNS
|
|
|
+ WHERE table_name = '{$tableName}' AND TABLE_SCHEMA = '{$db}'
|
|
|
+ ORDER BY ORDINAL_POSITION ASC";
|
|
|
+ $columns = $con->select($sql);
|
|
|
+ $annotation = "";
|
|
|
+
|
|
|
+ foreach ($columns as $column) {
|
|
|
+ $type = $this->getColumnType($column->DATA_TYPE);
|
|
|
+ $columnName = $column->COLUMN_NAME;
|
|
|
+ $fillable[] = $columnName;
|
|
|
+ $type = $this->handleSpecialColumns($columnName, $type);
|
|
|
+ $annotation .= sprintf("\n * @property %s \$%s %s",
|
|
|
+ $type,
|
|
|
+ $columnName,
|
|
|
+ $column->COLUMN_COMMENT);
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->fillable = $fillable;
|
|
|
+ return $annotation."\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ private function getColumnType($dataType)
|
|
|
+ {
|
|
|
+ return match($dataType) {
|
|
|
+ 'int', 'tinyint', 'smallint', 'mediumint', 'bigint' => 'int',
|
|
|
+ 'float', 'double', 'decimal' => 'float',
|
|
|
+ 'json' => 'object|array',
|
|
|
+ default => 'string'
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private function handleSpecialColumns($columnName, $type)
|
|
|
+ {
|
|
|
+ if (in_array($columnName, ['created_at', 'updated_at', 'deleted_at'])) {
|
|
|
+ return '\\Carbon\\Carbon';
|
|
|
+ }
|
|
|
+ return $type;
|
|
|
+ }
|
|
|
+}
|