connection = Config::get('database.default', 'mysql'); } public function generate(string $tableName, string $className = null): string { $prefix = Config::get('database.connections.' . $this->connection . '.prefix', ''); $fullTableName = $tableName; // 如果表名不包含前缀,且配置了前缀,则添加 if ($prefix && !str_starts_with($tableName, $prefix)) { $fullTableName = $prefix . $tableName; } // 尝试获取表信息 try { // 获取列信息,TP通用方法 $columns = Db::connect($this->connection)->getFields($fullTableName); } catch (\Exception $e) { // 尝试直接使用tableName $fullTableName = $tableName; $columns = Db::connect($this->connection)->getFields($fullTableName); } // 获取表注释 $tableComment = $this->getTableComment($fullTableName); // 获取索引信息 $indices = $this->getTableIndices($fullTableName); // 生成类名 if (empty($className)) { // 去除前缀 $shortName = $tableName; if ($prefix && str_starts_with($tableName, $prefix)) { $shortName = substr($tableName, strlen($prefix)); } $className = Str::studly($shortName); } // 构建类内容 return $this->buildClass($className, $fullTableName, $tableComment, $columns, $indices); } protected function getTableComment($tableName): string { $type = strtolower((string)Config::get('database.connections.' . $this->connection . '.type', '')); if ($type !== 'mysql') { return ''; } try { $escaped = addcslashes($tableName, "\\_%"); $rows = Db::connect($this->connection)->query("SHOW TABLE STATUS LIKE '$escaped'"); if (!empty($rows)) { return isset($rows[0]['Comment']) ? (string)$rows[0]['Comment'] : ''; } } catch (\Exception $e) { } try { $rows = Db::connect($this->connection)->query("SHOW CREATE TABLE `$tableName`"); if (!empty($rows)) { $create = $rows[0]['Create Table'] ?? ''; if ($create !== '' && preg_match("/COMMENT='(.*?)'/s", $create, $matches)) { return stripcslashes($matches[1]); } } } catch (\Exception $e) { } return ''; } protected function getTableIndices($tableName): array { try { // SHOW KEYS FROM table $keys = Db::connect($this->connection)->query("SHOW KEYS FROM `$tableName`"); $indices = []; foreach ($keys as $key) { $name = $key['Key_name']; if ($name === 'PRIMARY') continue; // 主键在 Field 中处理 if (!isset($indices[$name])) { $indices[$name] = [ 'name' => $name, 'columns' => [], 'type' => $key['Non_unique'] == 0 ? 'UNIQUE' : ($key['Index_type'] == 'FULLTEXT' ? 'FULLTEXT' : 'NORMAL') ]; } $indices[$name]['columns'][] = $key['Column_name']; } return array_values($indices); } catch (\Exception $e) { return []; } } protected function buildClass($className, $tableName, $tableComment, $columns, $indices): string { $fieldsCode = []; foreach ($columns as $field) { $fieldName = $field['name']; $type = $field['type']; // varchar(255) $comment = $field['comment'] ?? ''; $default = $field['default'] ?? null; $notNull = $field['notnull'] ?? false; $primary = $field['primary'] ?? false; $autoinc = $field['autoinc'] ?? false; // 解析类型和长度 preg_match('/(\w+)(?:\((\d+)(?:,(\d+))?\))?/', $type, $matches); $dbType = $matches[1] ?? 'varchar'; $length = isset($matches[2]) ? (int)$matches[2] : null; $precision = isset($matches[2]) ? (int)$matches[2] : 0; // For decimal $scale = isset($matches[3]) ? (int)$matches[3] : 0; // 解析 Ulthon 组件语法 $componentAttr = ''; $cleanComment = $comment; // 匹配 {type} (options) // 改进:允许 options 中含有冒号,且 key:value 之间可能有空格,或者没有括号 // 例子:{radio} (0:禁用,1:启用) 或 {image} 或 {relation} (table:mall_cate,relationBindSelect:title) if (preg_match('/\{(.*?)\}\s*(\((.*?)\))?/', $comment, $cmatch)) { $compType = $cmatch[1]; $compOptionsStr = $cmatch[3] ?? ''; $cleanComment = trim(str_replace($cmatch[0], '', $comment)); // 解析选项 (1:A, 2:B) 或 (table:mall_cate,relationBindSelect:title) $optionsCode = '[]'; if ($compOptionsStr) { $options = []; // 简单的 explode 可能有问题,如果值里面有逗号。但目前假设选项格式比较简单。 $parts = explode(',', $compOptionsStr); foreach ($parts as $p) { // 尝试分割 key:value $kv = explode(':', trim($p), 2); // Limit to 2 parts if (count($kv) >= 2) { $k = trim($kv[0]); $v = trim($kv[1]); $options[$k] = $v; } else { $options[] = trim($p); } } $optionsCode = $this->exportArray($options); } $componentAttr = "\n #[Component(type: '$compType', options: $optionsCode)]"; } // 如果是整型且未指定长度,默认11 (兼容旧习俗) if (str_contains($dbType, 'int') && !$length) { $length = 11; } // 构建 Field 注解参数 $fieldParams = []; // type (default: varchar) if ($dbType !== 'varchar') { $fieldParams[] = "type: '$dbType'"; } // length (default: null) if ($length !== null) { $fieldParams[] = "length: $length"; } // precision (default: 0) if ($precision !== 0) { $fieldParams[] = "precision: $precision"; } // scale (default: 0) if ($scale !== 0) { $fieldParams[] = "scale: $scale"; } // nullable (default: true) if ($notNull) { $fieldParams[] = "nullable: false"; } // default (default: null) if (!is_null($default)) { $defaultVal = is_string($default) ? "'$default'" : $default; $fieldParams[] = "default: $defaultVal"; } // comment (default: '') if ($cleanComment !== '') { $fieldParams[] = "comment: '$cleanComment'"; } // unsigned (default: false) if (str_contains($type, 'unsigned')) { $fieldParams[] = "unsigned: true"; } // autoIncrement (default: false) if ($autoinc) { $fieldParams[] = "autoIncrement: true"; } // primary (default: false) if ($primary) { $fieldParams[] = "primary: true"; } $fieldAttrStr = implode(', ', $fieldParams); $fieldsCode[] = <<exportArray($idx['columns']); $indexAttrs .= "\n#[Index(columns: $cols, name: '{$idx['name']}', type: '{$idx['type']}')]"; } return <<module}\scheme; use app\common\scheme\BaseScheme; use app\common\scheme\attribute\Table; use app\common\scheme\attribute\Field; use app\common\scheme\attribute\Component; use app\common\scheme\attribute\Index; #[Table(name: '$tableName', comment: '$tableComment')]$indexAttrs class $className extends BaseScheme { $fieldsBody } PHP; } protected function exportArray(array $array): string { // 简易数组导出 $items = []; // 判断是否是关联数组 $isAssoc = array_keys($array) !== range(0, count($array) - 1); foreach ($array as $k => $v) { $val = is_string($v) ? "'$v'" : $v; if ($isAssoc) { $key = is_int($k) ? $k : "'$k'"; $items[] = "$key => $val"; } else { $items[] = $val; } } return '[' . implode(', ', $items) . ']'; } }