unquote_id((string) $id)).'`'; } /** * 执行 MySQL 源 DDL(_query_batch → dr_format_create_sql) * * @param string $sql * @return bool */ protected function _ddl_exec_mysql($sql) { $sql = trim((string) $sql); if ($sql === '') { return false; } $rt = $this->_query_batch($sql); if (!empty($rt['code'])) { return true; } log_message('error', 'DDL compat: '.($rt['msg'] ?? $sql)); return false; } /** * 修改字段(优先驱动 editField) * * @param string $table * @param string $name * @param string $type * @param string $info * @param string $note * @return mixed */ public function edit_field($table, $name, $type, $info, $note) { if (method_exists($this->db, 'editField')) { return $this->db->editField($table, $name, $type, $info, $note); } return $this->_edit_field_compat($table, $name, $type, $info, $note); } /** * @param string $table * @param string $name * @param string $type * @param string $info * @param string $note * @return bool */ protected function _edit_field_compat($table, $name, $type, $info, $note) { $table = $this->unquote_id((string) $table); $name = $this->unquote_id((string) $name); if ($table === '' || $name === '') { return false; } $qi = $this->_mysql_qi($name); $sql = 'ALTER TABLE '.$this->_mysql_qi($table).' CHANGE '.$qi.' '.$qi .' '.$type.' '.$info; $note = (string) $note; if ($note !== '') { $sql .= " COMMENT '".str_replace("'", "''", $note)."'"; } return $this->_ddl_exec_mysql($sql); } /** * 添加字段(优先驱动 addField) * * @param string $table * @param string $name * @param string $type * @param string $info * @param string $note * @return mixed */ public function add_field($table, $name, $type, $info, $note) { if (method_exists($this->db, 'addField')) { return $this->db->addField($table, $name, $type, $info, $note); } return $this->_add_field_compat($table, $name, $type, $info, $note); } /** * @param string $table * @param string $name * @param string $type * @param string $info * @param string $note * @return bool */ protected function _add_field_compat($table, $name, $type, $info, $note) { $table = $this->unquote_id((string) $table); $name = $this->unquote_id((string) $name); if ($table === '' || $name === '') { return false; } $sql = 'ALTER TABLE '.$this->_mysql_qi($table).' ADD '.$this->_mysql_qi($name) .' '.$type.' '.$info; $note = (string) $note; if ($note !== '') { $sql .= " COMMENT '".str_replace("'", "''", $note)."'"; } return $this->_ddl_exec_mysql($sql); } /** * 删除字段(优先驱动 dropField) * * @param string $table * @param string $name * @return mixed */ public function drop_field($table, $name) { if (method_exists($this->db, 'dropField')) { return $this->db->dropField($table, $name); } return $this->_drop_field_compat($table, $name); } /** * @param string $table * @param string $name * @return bool */ protected function _drop_field_compat($table, $name) { $table = $this->unquote_id((string) $table); $name = $this->unquote_id((string) $name); if ($table === '' || $name === '') { return false; } return $this->_ddl_exec_mysql( 'ALTER TABLE '.$this->_mysql_qi($table).' DROP '.$this->_mysql_qi($name) ); } // 根据配置创建字段(字段类 create_sql 经驱动 sqlAddField 生成) public function create_field($table, $config) { if (!$config || !$table) { return; } $tablename = $this->dbprefix($table); foreach ($config as $field) { if ($this->db->fieldExists($field['fieldname'], $tablename)) { continue; } $obj = \Phpcmf\Service::L('field')->get($field['fieldtype']); if (!$obj) { continue; } $sql = $obj->create_sql($field['fieldname'], $field['setting']['option'], dr_safe_filename($field['name'])); if (!$sql) { continue; } // create_sql 为 MySQL 源(含 {tablename}),走兼容执行 $sql = str_replace('{tablename}', $this->unquote_id($tablename), $sql); $this->_ddl_exec_mysql($sql); } } /** * 创建表(优先驱动 createTable;未实现时拼 MySQL 源 SQL 经 formatCreateSql 兼容执行) * * @param string $table 表名(可含前缀) * @param array $fields ['字段名'=>'类型属性'] 或 [0=>'完整列定义'] * @param array $indexs ['PRIMARY KEY (`id`)', 'KEY `name` (`name`)', ...] * @param string $note 表注释 * @return array */ public function create_table($table, $fields, $indexs, $note) { try { if (method_exists($this->db, 'createTable')) { $rt = $this->db->createTable($table, $fields, $indexs, $note); if ($rt === false) { $error = $this->db->error(); return dr_return_data(0, $error['message'] ?? 'createTable failed'); } return dr_return_data(1); } return $this->_create_table_compat($table, $fields, $indexs, $note); } catch (\Throwable $e) { log_message('error', 'createTable: '.$e->getMessage()); return dr_return_data(0, $e->getMessage()); } } /** * 驱动未实现 createTable 时的兼容建表 * * @param string $table * @param array $fields * @param array $indexs * @param string $note * @return array */ protected function _create_table_compat($table, $fields, $indexs, $note) { $table = $this->unquote_id((string) $table); if ($table === '' || !is_array($fields) || !$fields) { return dr_return_data(0, 'createTable 参数无效'); } $lines = []; foreach ($fields as $name => $def) { if (is_int($name)) { $lines[] = (string) $def; } else { $lines[] = $this->_mysql_qi($name).' '.(string) $def; } } if (is_array($indexs)) { foreach ($indexs as $idx) { $idx = trim((string) $idx); if ($idx !== '') { $lines[] = $idx; } } } if (!$lines) { return dr_return_data(0, 'createTable 无字段定义'); } $sql = 'CREATE TABLE IF NOT EXISTS '.$this->_mysql_qi($table).' ( '.implode(",\n", $lines).' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT=\'' .str_replace("'", "''", (string) $note).'\''; $rt = $this->_query_batch($sql); if (empty($rt['code'])) { return dr_return_data(0, $rt['msg'] ?? 'createTable compat failed'); } return dr_return_data(1); } /** * 删除表(优先驱动 dropTable;未实现时走兼容 SQL) * * @param string $table * @param bool $ifExists * @return array */ public function drop_table($table, $ifExists = true) { try { if (method_exists($this->db, 'dropTable')) { $rt = $this->db->dropTable($table, $ifExists); } else { $rt = $this->_drop_table_compat($table, $ifExists); } if ($rt === false) { $error = $this->db->error(); return dr_return_data(0, $error['message'] ?? 'dropTable failed'); } return dr_return_data(1); } catch (\Throwable $e) { log_message('error', 'dropTable: '.$e->getMessage()); return dr_return_data(0, $e->getMessage()); } } /** * 驱动未实现 dropTable 时的兼容删除 * * @param string $table * @param bool $ifExists * @return bool */ protected function _drop_table_compat($table, $ifExists = true) { $table = $this->unquote_id((string) $table); if ($table === '') { return false; } if ($ifExists && method_exists($this->db, 'tableExists') && !$this->db->tableExists($table)) { return true; } $sql = ($ifExists ? 'DROP TABLE IF EXISTS ' : 'DROP TABLE ').$this->_mysql_qi($table); if ($this->_ddl_exec_mysql($sql)) { return true; } // IF EXISTS 不被支持时退化 if ($ifExists) { return $this->_ddl_exec_mysql('DROP TABLE '.$this->_mysql_qi($table)); } return false; } /** * 按 Schema 安装表结构与种子数据(Config/Install.php) * * @param array $schema ['tables'=>[name=>['fields','indexes','comment','drop']], 'seeds'=>[['table','data']]] * @return array */ public function install_schema(array $schema) { $tables = isset($schema['tables']) && is_array($schema['tables']) ? $schema['tables'] : []; $seeds = isset($schema['seeds']) && is_array($schema['seeds']) ? $schema['seeds'] : []; foreach ($tables as $name => $def) { if (!$name || !is_array($def) || empty($def['fields'])) { continue; } $table = $this->dbprefix($name); if (!empty($def['drop'])) { $rt = $this->drop_table($table, true); if (!$rt['code']) { return $rt; } } $rt = $this->create_table( $table, $def['fields'], isset($def['indexes']) ? $def['indexes'] : [], isset($def['comment']) ? $def['comment'] : '' ); if (!$rt['code']) { return dr_return_data(0, dr_lang('创建表失败(%s):%s', $table, $rt['msg'])); } } foreach ($seeds as $seed) { if (empty($seed['table']) || empty($seed['data']) || !is_array($seed['data'])) { continue; } $rt = \Phpcmf\Service::M()->table($seed['table'])->replace($seed['data']); if (is_array($rt) && isset($rt['code']) && !$rt['code']) { return dr_return_data(0, dr_lang('写入初始数据失败(%s):%s', $seed['table'], $rt['msg'])); } } return dr_return_data(1, '', ['tables' => dr_count($tables), 'seeds' => dr_count($seeds)]); } /** * 获取建表 SQL(优先驱动 createTableSql) * * @param string $table * @return array [createSql, fieldFragments, table] */ public function create_table_sql($table) { if (method_exists($this->db, 'createTableSql')) { $create = $this->db->createTableSql($table); } else { $create = $this->_create_table_sql_compat($table); } if (!$create) { return ['', [], $table]; } $char = '`'; if (isset($this->db->escapeChar) && is_string($this->db->escapeChar) && $this->db->escapeChar !== '') { $char = $this->db->escapeChar; } $quote = preg_quote($char, '/'); $arr = explode(PHP_EOL, $create); $sql = []; foreach ($arr as $t) { if (preg_match('/'.$quote.'(.+)'.$quote.'/U', $t, $mt) && strpos($t, ' KEY ') === false && strpos($t, $char.$this->dbprefix()) === false ) { $sql[$mt[1]] = trim($t, ','); } } return [$create, $sql, $table]; } /** * 驱动未实现 createTableSql:先试 SHOW CREATE TABLE,再按字段元数据拼装 * * @param string $table * @return string */ protected function _create_table_sql_compat($table) { $table = $this->unquote_id((string) $table); if ($table === '') { return ''; } try { $query = $this->db->query('SHOW CREATE TABLE '.$this->_mysql_qi($table)); $row = $query ? $query->getRowArray() : null; if ($row) { foreach ($row as $k => $v) { if (stripos((string) $k, 'create') !== false && $v) { return str_replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS', (string) $v); } } } } catch (\Throwable $e) { // 非 MySQL 忽略 } if (!method_exists($this->db, 'getFieldData')) { return ''; } $fds = $this->db->getFieldData($table); if (!$fds) { return ''; } $lines = []; $pk = []; foreach ($fds as $f) { $name = isset($f->name) ? $f->name : ''; if ($name === '') { continue; } $type = isset($f->type) ? $f->type : 'TEXT'; if (!empty($f->max_length) && !preg_match('/\(/', $type) && !preg_match('/(TEXT|BLOB|DATE|TIME|INT|FLOAT|DOUBLE|REAL|BOOL)/i', $type) ) { $type .= '('.$f->max_length.')'; } $null = (isset($f->nullable) ? $f->nullable : true) ? 'NULL' : 'NOT NULL'; $def = ''; if (isset($f->default) && $f->default !== null) { $def = " DEFAULT '".str_replace("'", "''", (string) $f->default)."'"; } $extra = ''; if (!empty($f->primary_key)) { $pk[] = $this->_mysql_qi($name); } if (!empty($f->auto_increment)) { $extra = ' AUTO_INCREMENT'; } $lines[] = $this->_mysql_qi($name).' '.$type.' '.$null.$def.$extra; } if ($pk) { $lines[] = 'PRIMARY KEY ('.implode(',', $pk).')'; } return 'CREATE TABLE IF NOT EXISTS '.$this->_mysql_qi($table).' ( '.implode(",\n", $lines).' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci'; } /** * 从表结构导出规范化 Schema(fields / indexes / comment) * 供 Dever 导出 Config/Content.php、Form.php 等使用 */ public function create_table_schema($table) { list($create) = $this->create_table_sql($table); if (!$create) { return [ 'comment' => '', 'fields' => [], 'indexes' => [], ]; } $comment = ''; if (preg_match("/COMMENT='([^']*)'/", $create, $cm)) { $comment = $cm[1]; } elseif (preg_match('/COMMENT="([^"]*)"/', $create, $cm)) { $comment = $cm[1]; } $inner = $create; if (preg_match('/\((.*)\)\s*ENGINE/is', $create, $m)) { $inner = $m[1]; } $fields = []; $indexes = []; $parts = preg_split("/,\s*\n/", trim($inner)); if (dr_count($parts) < 2) { $parts = []; $buf = ''; $depth = 0; $len = strlen($inner); for ($i = 0; $i < $len; $i++) { $ch = $inner[$i]; if ($ch === '(') { $depth++; $buf .= $ch; } elseif ($ch === ')') { $depth = max(0, $depth - 1); $buf .= $ch; } elseif ($ch === ',' && $depth === 0) { $parts[] = trim($buf); $buf = ''; } else { $buf .= $ch; } } if (trim($buf) !== '') { $parts[] = trim($buf); } } foreach ($parts as $line) { $line = trim($line); $line = rtrim($line, ','); if ($line === '') { continue; } if (preg_match('/^(PRIMARY KEY|UNIQUE KEY|KEY|FULLTEXT KEY)\s/i', $line)) { $indexes[] = $line; continue; } if (preg_match('/^`([^`]+)`\s+(.+)$/s', $line, $fm)) { $fields[$fm[1]] = trim($fm[2]); } } return [ 'comment' => $comment, 'fields' => $fields, 'indexes' => $indexes, ]; } /** * 表完整字段信息(优先驱动 showFullColunms) * * @param string $table * @return array */ public function show_full_colunms($table) { if (method_exists($this->db, 'showFullColunms')) { return $this->db->showFullColunms($table); } return $this->_show_full_colunms_compat($table); } /** * @param string $table * @return array */ protected function _show_full_colunms_compat($table) { $table = $this->unquote_id((string) $table); if ($table === '') { return []; } try { $query = $this->db->query('SHOW FULL COLUMNS FROM '.$this->_mysql_qi($table)); if ($query) { $rows = $query->getResultArray(); if ($rows) { return $rows; } } } catch (\Throwable $e) { // 非 MySQL } if (!method_exists($this->db, 'getFieldData')) { return []; } $out = []; foreach ($this->db->getFieldData($table) as $f) { $out[] = [ 'Field' => isset($f->name) ? $f->name : '', 'Type' => isset($f->type) ? $f->type : '', 'Collation' => null, 'Null' => (isset($f->nullable) && !$f->nullable) ? 'NO' : 'YES', 'Key' => !empty($f->primary_key) ? 'PRI' : '', 'Default' => isset($f->default) ? $f->default : null, 'Extra' => !empty($f->auto_increment) ? 'auto_increment' : '', 'Privileges' => '', 'Comment' => '', ]; } return $out; } /** * 全部表状态(优先驱动 showTableStatus) * * @return array */ public function show_table_status() { if (method_exists($this->db, 'showTableStatus')) { return $this->db->showTableStatus(); } return $this->_show_table_status_compat(); } /** * @return array */ protected function _show_table_status_compat() { try { $query = $this->db->query('SHOW TABLE STATUS'); if ($query) { $rows = $query->getResultArray(); if ($rows) { return $rows; } } } catch (\Throwable $e) { // 非 MySQL } $out = []; if (method_exists($this->db, 'listTables')) { foreach ($this->db->listTables() as $name) { $out[] = [ 'Name' => $name, 'Engine' => '', 'Version' => '', 'Row_format' => '', 'Rows' => 0, 'Avg_row_length' => 0, 'Data_length' => 0, 'Max_data_length' => 0, 'Index_length' => 0, 'Data_free' => 0, 'Auto_increment' => null, 'Create_time' => null, 'Update_time' => null, 'Check_time' => null, 'Collation' => '', 'Checksum' => null, 'Create_options' => '', 'Comment' => '', ]; } } return $out; } /** * 修复表(优先驱动;兼容执行 MySQL REPAIR TABLE,失败则视为跳过成功) * * @param string $table * @return mixed */ public function repair_table($table) { if (method_exists($this->db, 'repairTable')) { return $this->db->repairTable($table); } return $this->_maintenance_table_compat('REPAIR TABLE', $table); } /** * 优化表 * * @param string $table * @return mixed */ public function optimize_table($table) { if (method_exists($this->db, 'optimizeTable')) { return $this->db->optimizeTable($table); } return $this->_maintenance_table_compat('OPTIMIZE TABLE', $table); } /** * 刷新表 * * @param string $table * @return mixed */ public function flush_table($table) { if (method_exists($this->db, 'flushTable')) { return $this->db->flushTable($table); } return $this->_maintenance_table_compat('FLUSH TABLE', $table); } /** * 检查表 * * @param string $table * @return array */ public function check_table($table) { if (method_exists($this->db, 'checkTable')) { return $this->db->checkTable($table); } $table = $this->unquote_id((string) $table); if ($table === '') { return []; } try { $query = $this->db->query('CHECK TABLE '.$this->_mysql_qi($table)); $row = $query ? $query->getRowArray() : null; if ($row) { return $row; } } catch (\Throwable $e) { // 非 MySQL:返回通过状态,避免后台报错 } return ['Msg_text' => 'OK', 'Msg_type' => 'status']; } /** * MySQL 维护类语句兼容(不支持的库跳过并返回 true) * * @param string $verb REPAIR TABLE / OPTIMIZE TABLE / FLUSH TABLE * @param string $table * @return bool */ protected function _maintenance_table_compat($verb, $table) { $table = $this->unquote_id((string) $table); if ($table === '') { return false; } try { $rt = $this->db->query($verb.' '.$this->_mysql_qi($table)); if ($rt !== false) { return $rt; } } catch (\Throwable $e) { // ignore } return true; } /** * 修改表默认字符集(优先驱动 setTableCharset) * * @param string $table * @param string $charset * @param string $collate * @return mixed */ public function set_table_charset($table, $charset = 'utf8mb4', $collate = 'utf8mb4_unicode_ci') { if (method_exists($this->db, 'setTableCharset')) { return $this->db->setTableCharset($table, $charset, $collate); } return $this->_set_table_charset_compat($table, $charset, $collate); } /** * @param string $table * @param string $charset * @param string $collate * @return bool */ protected function _set_table_charset_compat($table, $charset = 'utf8mb4', $collate = 'utf8mb4_unicode_ci') { $table = $this->unquote_id((string) $table); if ($table === '') { return false; } $charset = preg_replace('/[^a-z0-9_]/i', '', (string) $charset); $collate = preg_replace('/[^a-z0-9_]/i', '', (string) $collate); $sql = 'ALTER TABLE '.$this->_mysql_qi($table).' DEFAULT CHARSET='.$charset.' COLLATE '.$collate; if ($this->_ddl_exec_mysql($sql)) { return true; } // 不支持字符集的库视为跳过 return true; } /** * 创建索引(优先驱动;否则 MySQL CREATE INDEX 经 formatCreateSql) * * @param string $table * @param string $name * @param string|array $columns * @return mixed */ public function add_index($table, $name, $columns) { $table = $this->unquote_id((string) $table); $name = $this->unquote_id((string) $name); if ($table === '' || $name === '') { return false; } if (is_array($columns)) { $cols = []; foreach ($columns as $c) { $cols[] = $this->_mysql_qi($c); } $colsql = implode(', ', $cols); } else { $colsql = (string) $columns; if (!$this->has_id_quotes($colsql)) { $parts = array_map('trim', explode(',', $colsql)); $tmp = []; foreach ($parts as $p) { $p !== '' && $tmp[] = $this->_mysql_qi($p); } $colsql = implode(', ', $tmp); } } if (method_exists($this->db, 'addIndex')) { return $this->db->addIndex($table, $name, $columns); } return $this->_ddl_exec_mysql( 'CREATE INDEX '.$this->_mysql_qi($name).' ON '.$this->_mysql_qi($table).'('.$colsql.')' ); } // 表结构缓存 public function cache($siteid = SITE_ID, $module = null) { $cache = []; $paytable = []; // 付款表名 支持模块和表单 后期淘汰 // 生成模块表结构 if (dr_is_use_module()) { $obj = \Phpcmf\Service::M('module', 'cms'); if (method_exists($obj, 'paytable')) { list($cache, $paytable) = $obj->paytable($cache, $paytable, $module, $siteid); } } // 网站表单 if (dr_is_app('form') && $this->is_table_exists($siteid.'_form')) { $obj = \Phpcmf\Service::M('form', 'form'); if (method_exists($obj, 'paytable')) { list($cache, $paytable) = $obj->paytable($cache, $paytable, $siteid); } } // 会员表 $table = $this->dbprefix('member'); $cache[$table] = $this->db->getFieldNames($table); // 会员附表 $table = $this->dbprefix('member_data'); $cache[$table] = $this->db->getFieldNames($table); // 缓存表结构 \Phpcmf\Service::L('cache')->set_file('table-'.$siteid, $cache); // 缓存的字段类型 //$type = ['Select', 'Checkbox', 'Radio', 'Pay', 'Pays', 'File', 'Files', 'Image', 'Images', 'Ftable']; $cache = []; $field = $this->db->table('field')->where('disabled', 0)->orderBy('id ASC')->get()->getResultArray(); if ($field) { foreach ($field as $f) { $f['setting'] = dr_string2array($f['setting']); $cache[$f['id']] = $f; } } \Phpcmf\Service::L('cache')->set_file('table-field', $cache); // 缓存付款表 \Phpcmf\Service::L('cache')->set_file('table-pay-'.$siteid, $paytable); /* * $paytable字段主键为 自定义字段rname-rid * */ } // 获取字段结构 public function get_field($table) { return $this->db->getFieldNames($this->dbprefix($table)); } // 获取缓存的字段结构 public function get_cache_field($table) { $tableinfo = \Phpcmf\Service::L('cache')->get('table-'.SITE_ID); if (!$tableinfo) { // 没有表结构缓存时返回空 return []; } return isset($tableinfo[$this->dbprefix($table)]) ? $tableinfo[$this->dbprefix($table)] : []; } // 执行批量sql public function _query($sql, $replace = []) { return $this->_query_batch($sql, $replace); } // 网站表单-------------------------------------------------------------------- // 创建 public function create_form($data) { if (dr_is_app('form')) { \Phpcmf\Service::M('form', 'form')->create_form($data); } } // 删除表单 public function delete_form($data) { if (dr_is_app('form')) { \Phpcmf\Service::M('form', 'form')->delete_form_table($data); } } // 模块表单-------------------------------------------------------------------- // 创建 public function create_module_form($data) { if (dr_is_app('mform')) { \Phpcmf\Service::M('mform', 'mform')->create_module_form($data); } } // 删除模块表单 public function delete_module_form($data) { if (dr_is_app('mform')) { \Phpcmf\Service::M('mform', 'mform')->delete_module_form($data); } } // 项目-------------------------------------------------------------------- // 创建项目 public function create_site($siteid) { } }