Files
ulthon_admin/extend/base/common/service/scheme/DbToSchemeService.php

282 lines
9.5 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace base\common\service\scheme;
use think\facade\Db;
use think\facade\Config;
use think\helper\Str;
class DbToSchemeService
{
protected string $module = 'admin';
protected string $connection;
public function __construct()
{
$this->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[] = <<<PHP
#[Field($fieldAttrStr)]$componentAttr
public \$$fieldName;
PHP;
}
$fieldsBody = implode("\n\n", $fieldsCode);
// 构建 Index 注解
$indexAttrs = '';
foreach ($indices as $idx) {
$cols = $this->exportArray($idx['columns']);
$indexAttrs .= "\n#[Index(columns: $cols, name: '{$idx['name']}', type: '{$idx['type']}')]";
}
return <<<PHP
<?php
namespace app\\{$this->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) . ']';
}
}