| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- <?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()
- {
- // 扫描核心模型目录
- $ucoreModelsDir = app_path('../UCore/Models');
- if (is_dir($ucoreModelsDir)) {
- $this->call1($ucoreModelsDir, '\UCore\Models\\');
- } else {
- $this->warn("UCore Models 目录不存在: $ucoreModelsDir");
- }
- // 扫描模块下的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)){
- // 检查类是否是抽象类
- $reflectionClass = new \ReflectionClass($modelClass);
- if ($reflectionClass->isAbstract()) {
- $this->output->warning(" model $modelClass 是抽象类,跳过");
- return;
- }
- // 检查类是否是模型类
- if (!$reflectionClass->isSubclassOf(\Illuminate\Database\Eloquent\Model::class)) {
- $this->output->warning(" model $modelClass 不是模型类,跳过");
- return;
- }
- // 检查构造函数是否需要参数
- $constructor = $reflectionClass->getConstructor();
- if ($constructor && $constructor->getNumberOfRequiredParameters() > 0) {
- $this->output->warning(" model $modelClass 需要构造函数参数,跳过");
- return;
- }
- /**
- * @var ModelCore $model
- */
- try {
- $model = new $modelClass();
- } catch (\Throwable $e) {
- $this->output->error(" model $modelClass 实例化失败: " . $e->getMessage());
- return;
- }
- 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;
- }
- }
|