- 感觉很麻烦
- 迁移还得自己写过程
- 建表字段用数组配置表达,还不如原生sql直观呢
- 太重了,简单封装自建数据表,一两个文件就够了,还更好控制
- 还有权限控制,学习起来太麻烦
备份一下,使用 codex-5.6-sol,极高推理,1.5x速度 完成的,几乎用了一下午时间,才最终确定自己想要的结构和注释说明
<?php
use BerlinDB\Database\Kern\Query;
use BerlinDB\Database\Kern\Row;
use BerlinDB\Database\Kern\Schema;
use BerlinDB\Database\Kern\Table;
defined('ABSPATH') || exit;
if (!class_exists(Query::class)) return;
/**
* 2026.08.10
* 手写继承Row类,显式传入字段时:
* 1.防止 PHP 8.2 的“弃用警告”( BerlinDB 查出数据后 new Row($数据库记录),把字段写进对象$row->id = 1.... 没有提前声明 $id、$code 等字段,动态写入时会触发弃用警告)。
* 2.IDE 能自动提示 $row->code
* 3.即使查询结果缺少某个字段,该属性也依然存在。
* 4.可以明确字段类型和应用层结构。
* ----------------
* 现在封装成通用方法,为了继承者不写继承Row类,这里提供一个默认的通用继承Row类,继承Row类,让 BerlinDB 动态创建属性吗,缺点就是上面显式传入字段的功能就没有了,IDE也不会提示$row->code了。
* 当然,你可以在继承 WU_DB 时,传入 自己手写的 row_class (必须继承自WU_DB_Row,修复to_array出现内部状态) ,就可以实现上面显式传入字段的优点了。
* ----------------
* #[\AllowDynamicProperties]:
* PHP 8.2 开始,不推荐给对象添加未声明的属性。
* 因为通用 Row 并没有提前声明 $id、$code 等字段,BerlinDB 动态写入时可能产生弃用警告。这个属性表示:
* 允许 BerlinDB 根据查询结果动态创建 $row->id、$row->code 等属性。
*/
#[\AllowDynamicProperties]
class WU_DB_Row extends Row {
/**
* 只把公开声明或动态生成的业务字段转换为数组。
* BerlinDB 原始实现会在父类作用域内读取对象变量,导致 args、logs、casts
* 等受保护的内部状态也出现在结果中。数组强制转换会标记非公开属性,借此
* 可以稳定排除这些框架内部字段。
*
* @return array<string, mixed> 当前 Row 的业务字段。
*/
public function to_array() {
$data = [];
foreach ((array)$this as $key => $value) {
/**
* PHP 把对象强制转换为数组时 (array)$this :
* 公开属性的键保持正常:'id' 'code'
* 受保护或私有属性的键会被 PHP 加上包含空字符 "\0" 的内部标记,例如概念上类似:
* "\0*\0args"
* "\0*\0logs"
* 因此:if (strpos($key, "\0") !== false) continue;
* 意思是:键名包含 PHP 内部标记,就跳过,不放进返回数组。
* 最终只保留公开声明和动态生成的业务字段。
*/
if (strpos($key, "\0") !== false) continue;
$data[$key] = $value;
}
return $data;
}
}
/**
* WPunion BerlinDB 数据表基础类。
* 子类只需要在 config 中声明数据表、字段、索引和权限配置。基础类负责:
* 1. 动态创建并缓存 Schema;
* 2. 注册 Table 以及结构升级钩子;
* 3. 配置 Query 的表名、缓存组和 Row 类型;
* 4. 根据 Schema 默认值创建未持久化 Row。
* -------------------------
* 建议优先使用 WU_DB 提供的包裹方法
* @see WU_DB::select
* @see WU_DB::insert
* @see WU_DB::delete
* @see WU_DB::update
* @see WU_DB::get
* -------------------------
* abstract 抽象类,只能被继承,不能直接被实例化
* @see _SAMPLE_WU_DB
*/
abstract class WU_DB extends Query {
# 当前数据表及查询对象的配置。
protected $config = [];
# 当前数据表及查询对象的默认配置;子类仅必须配置 name、version、columns。
private $default_config = [
'name' => '',
'version' => '',
'indexes' => [],
'columns' => [],
'column_caps' => [
'select' => 'manage_options',
'insert' => 'manage_options',
'update' => 'manage_options',
'delete' => 'manage_options',
],
'prefix' => '',
'description' => '',
'global' => false,
'db_version_key' => '',
'comment' => '',
'upgrades' => [],
'table_alias' => '',
'item_name' => '',
'item_name_plural' => '',
'row_class' => WU_DB_Row::class,
'cache_group' => '',
];
/** ------------------------------------------------------------- */
/**
* 查询数据库并获取数据项或数量。
* @param array<string, mixed>|string $query 查询参数数组或 URL 查询字符串。
* @return array<int|string, mixed>|int 数据项数组,count=true 时返回数量。
* @since 1.0.0
* @since 3.0.0 使用生命周期管理,并由查询解析和数据获取流程完成查询。
*/
public function select($query = []) {
return parent::query($query);
}
/**
* 按任意 Schema 列和值获取单条数据库记录,结果可能来自缓存。
* 注意事项:
* - 只应查询值唯一的列;父类 SQL 使用 LIMIT 1,非唯一列会得到不确定的一条;
* - 列名必须存在于 Schema,值必须是标量;
* - 空字符串、0、'0'、null 都会被 empty() 判定为空并直接返回 false;如果确实需要查询这些值,应使用 query();
* - 成功返回 Row,查不到、列无效或参数无效都返回 false;
* - 对外接口仍要自行完成业务权限判断。
*/
public function get($column_value = '', $column_name = 'id') {
return parent::get_item_by($column_name, $column_value);
}
/**
* 向数据库新增一条记录。
* @param array<string, mixed> $data 要新增的数据。
* @return int|false 成功返回新数据项 ID,失败返回 false。
* @since 1.0.0
*/
public function insert($data = []) {
return parent::add_item($data);
}
/**
* 按主键 ID 更新一条数据库记录。
* @param int|string $item_id 要更新的数据项 ID。
* @param array<string, mixed> $data 需要更新的数据。
* @return bool 更新成功返回 true,否则返回 false。
* @since 1.0.0
*/
public function update($item_id = 0, $data = []) {
return parent::update_item($item_id, $data);
}
/**
* 按主键 ID 从数据库删除一条记录。
* @param int|string $item_id 要删除的数据项 ID。
* @return bool 删除成功返回 true,否则返回 false。
* @since 1.0.0
*/
public function delete($item_id = 0) {
return parent::delete_item($item_id);
}
/** ------------------------------------------------------------- */
/**
* 继承:返回包含 WordPress 站点前缀和插件前缀的完整物理表名。
* @return string 完整物理表名。
*/
public function get_table_name() {
return parent::get_table_name();
}
/**
* 继承:返回最近一次 select() 查询最终执行的 SQL 字符串。
* @return string 最近一次查询 SQL;尚未查询时返回空字符串。
*/
public function get_request() {
return parent::get_request();
}
/**
* 继承:返回当前查询对象收集的诊断日志,并可按日志字段过滤。
* @param array<string, mixed> $args 可选的日志字段和值。
* @param string $operator 字段匹配方式,可选 and、or,默认 and。
* @return array<int, array<string, mixed>> 符合条件的诊断日志。
*/
public function get_logs(array $args = [], string $operator = 'and') {
return parent::get_logs($args, $operator);
}
/** ------------------------------------------------------------- */
/**
* 新增:在文件加载阶段注册数据表,确保 admin_init 能执行安装和升级检查。
* @return Table 当前数据表的 BerlinDB 管理对象。
*/
final public static function register() {
$database = new static();
return $database->get_table_manager();
}
/**
* WU_DB 自有:返回当前实例使用的 Schema 对象。
* @return Schema 当前数据表结构。
*/
final public function get_schema() {
return $this->table_schema;
}
/**
* WU_DB 自有:返回当前实例使用的 Table 管理对象。
* @return Table 当前数据表管理对象。
*/
final public function get_table_manager() {
return $this->table_manager;
}
/**
* WU_DB 自有:根据 Schema 的 columns.default 动态生成 Row 默认值。
* BerlinDB 使用 false 表示数据库列没有默认值。Row 仍然保留这个占位值,
* 因而未持久化的数据行也始终具有 Schema 声明的全部字段。
*
* @return array<string, mixed> 以列名为键的 Row 默认值。
*/
final public function get_row_defaults() {
$defaults = [];
foreach ($this->get_columns() as $column) {
if (empty($column->name)) continue;
$defaults[$column->name] = $column->default;
}
return $defaults;
}
/**
* 新增:根据默认值创建一个未写入数据库的 Row
* @param array|object $data 要覆盖到默认值上的字段数据。
* @return Row 当前数据表配置的 Row 对象。
*/
final public function new_row($data = []) {
if (is_object($data)) $data = (array)$data;
if (!is_array($data)) throw new InvalidArgumentException('WU_DB Row 数据必须是数组或对象。');
$row_class = $this->item_shape;
return new $row_class(array_replace($this->get_row_defaults(), $data));
}
/** ------------------------------------------------------------- */
# 当前实例使用的 Table 管理对象。
private $table_manager;
# 按具体 WU_DB 子类缓存的 Schema 对象。
private static $schema_cache = [];
# 按物理数据表缓存的 Table 管理对象,避免重复注册钩子。
private static $table_instances = [];
# 已注册物理数据表的结构指纹,用于发现同名表配置冲突。
private static $table_fingerprints = [];
/**
* 配置数据表并初始化 BerlinDB Query。
* @param array|string $query 可选查询参数;传入时沿用 BerlinDB 的构造即查询行为。
* @return void
*/
final public function __construct($query = []) {
if (!is_array($query) && !is_string($query)) throw new InvalidArgumentException('WU_DB 查询参数必须是数组或查询字符串。');
if (!is_array($this->config)) throw new UnexpectedValueException('WU_DB config 必须是数组。');
// 合并默认配置并统一清理标识符,让错误配置在进入 BerlinDB 前尽早暴露。
$config = wp_parse_args($this->config, $this->default_config);
$config['prefix'] = sanitize_key((string)$config['prefix']);
$config['name'] = sanitize_key((string)$config['name']);
$config['version'] = is_scalar($config['version']) ? trim((string)$config['version']) : '';
$config['description'] = is_scalar($config['description']) ? wp_kses_data((string)$config['description']) : '';
$config['global'] = wp_validate_boolean($config['global']);
$config['db_version_key'] = sanitize_key((string)$config['db_version_key']);
$config['comment'] = is_scalar($config['comment']) ? (string)$config['comment'] : '';
$config['upgrades'] = is_array($config['upgrades']) ? $config['upgrades'] : [];
$config['table_alias'] = sanitize_key((string)$config['table_alias']);
$config['item_name'] = sanitize_key((string)$config['item_name']);
$config['item_name_plural'] = sanitize_key((string)$config['item_name_plural']);
$config['cache_group'] = sanitize_key((string)$config['cache_group']);
if (!$config['name']) throw new UnexpectedValueException('WU_DB config.name 不能为空。');
if (!$config['version']) throw new UnexpectedValueException('WU_DB config.version 不能为空。');
if (!is_array($config['columns']) || !$config['columns']) throw new UnexpectedValueException('WU_DB config.columns 必须声明至少一个字段。');
if (!is_array($config['indexes'])) throw new UnexpectedValueException('WU_DB config.indexes 必须是数组。');
if (!is_array($config['column_caps'])) throw new UnexpectedValueException('WU_DB config.column_caps 必须是数组。');
if (!$config['item_name']) $config['item_name'] = $config['name'];
if (!$config['item_name_plural']) $config['item_name_plural'] = $config['name'];
if (!$config['cache_group']) $config['cache_group'] = $config['name'];
/*
* 通用 Table 不认识 WU_DB 子类方法。把方法名包装为绑定当前实例的闭包后,
* public/protected 升级方法都能由 BerlinDB Table 正常执行。
*/
$upgrades = [];
foreach ($config['upgrades'] as $version => $callback) {
$version = is_scalar($version) ? trim((string)$version) : '';
if (!$version) throw new UnexpectedValueException('WU_DB config.upgrades 的版本号不能为空。');
if (is_string($callback) && method_exists($this, $callback)) {
$method = $callback;
$callback = function () use ($method) {
return $this->{$method}();
};
}
if (!is_callable($callback)) throw new UnexpectedValueException("WU_DB config.upgrades.{$version} 不是有效回调。");
$upgrades[$version] = $callback;
}
$config['upgrades'] = $upgrades;
$row_class = $config['row_class'];
if (!is_string($row_class) || !class_exists($row_class) || !is_a($row_class, WU_DB_Row::class, true)) throw new UnexpectedValueException('WU_DB config.row_class 必须是有效的 WU_DB_Row 子类。');
// 每个 WU_DB 子类只解析一次字段和索引配置,后续实例直接复用 Schema。
$database_class = static::class;
if (isset(self::$schema_cache[$database_class])) {
$schema = self::$schema_cache[$database_class];
} else {
$columns = [];
foreach ($config['columns'] as $column) {
if (is_array($column) && $config['column_caps']) {
$column['caps'] = array_merge(
$config['column_caps'],
isset($column['caps']) && is_array($column['caps']) ? $column['caps'] : []
);
}
$columns[] = $column;
}
$schema = new Schema([
// 数据表的字段定义。
'columns' => $columns,
// 数据表的索引定义。
'indexes' => $config['indexes'],
]);
$errors = $schema->get_validation_errors();
if ($errors) throw new UnexpectedValueException("{$database_class} Schema 无效:" . implode(';', $errors));
self::$schema_cache[$database_class] = $schema;
}
// Query 和 Table 共用同一个 Schema,避免同一份字段配置被重复解析。
$this->prefix = $config['prefix'];
$this->table_name = $config['name'];
$this->table_alias = $config['table_alias'];
$this->table_schema = $schema;
$this->item_name = $config['item_name'];
$this->item_name_plural = $config['item_name_plural'];
$this->item_shape = $config['row_class'];
$this->cache_group = $config['cache_group'];
// 同一物理表只创建一个 Table 管理对象,避免重复注册升级钩子。
$scope = $config['global'] ? 'network' : 'site';
$table_name = $config['prefix'] ? "{$config['prefix']}_{$config['name']}" : $config['name'];
$table_key = "{$scope}:{$table_name}";
$fingerprint = hash('sha256', $schema->get_create_table_string() . '|' . $config['version']);
$new_table_manager = false;
if (isset(self::$table_instances[$table_key])) {
if (self::$table_fingerprints[$table_key] !== $fingerprint) throw new LogicException("物理数据表 {$table_key} 被配置了不同的 Schema 或版本。");
$table = self::$table_instances[$table_key];
} else {
$table = new Table([
// 不含任何前缀的数据表名。
'name' => $config['name'],
// 插件或模块的数据表前缀。
'prefix' => $config['prefix'],
// 当前目标结构版本。
'version' => $config['version'],
// 数据表用途说明。
'description' => $config['description'],
// 是否注册为多站点全局表。
'global' => $config['global'],
// 保存当前结构版本的 option 键。
'db_version_key' => $config['db_version_key'],
// 数据表注释。
'comment' => $config['comment'],
// 后续结构升级回调。
'upgrades' => $config['upgrades'],
// Query 与 Table 共用的 Schema 对象。
'schema' => $schema,
]);
self::$table_instances[$table_key] = $table;
self::$table_fingerprints[$table_key] = $fingerprint;
$new_table_manager = true;
}
$this->table_manager = $table;
/*
* 先完成 Query 初始化,让升级回调能取得带 WordPress 前缀的物理表名;
* 如果调用者在构造时传入查询,则在安装或升级检查完成后再执行该查询。
*/
parent::__construct();
if ($new_table_manager && did_action('admin_init')) $table->maybe_upgrade();
if (!empty($query)) $this->query($query);
}
}
/**
* WU_DB 完整配置示例。
* config 顶层只有 name、version、columns 必填,其余键都有默认值并可省略。
* columns 中每个字段至少需要 name、type;indexes 可省略,但声明后每个索引
* 必须有 columns,非 primary 索引还必须有 name。
* -------------------
* 在插件主文件加载基础类和具体表类后立即注册。Table 会在 admin_init 自动
* 安装或升级;新站点直接按最新 Schema 建表,不执行历史 upgrades。
* _SAMPLE_WU_DB::register();
*/
class _SAMPLE_WU_DB extends WU_DB {
# 示例表配置;实际项目只保留业务需要的字段和索引。
protected $config = [
// 必填。数据表基础名;默认 '';只能使用可被 sanitize_key() 保留的字符,不含 $wpdb->prefix 和插件前缀。
'name' => 'sample_items',
// 必填。目标结构版本;默认 '';可使用 version_compare() 能比较的版本号,Schema 变化时必须递增。
'version' => '2026081002',
// 必填。字段定义;默认 [];至少一个字段,每项至少声明 name、type。
'columns' => [
[
# 必填。字段名;默认 '';应使用小写字母、数字和下划线,且在当前 Schema 内唯一。
'name' => 'id',
# 必填。MySQL 类型;默认 '';
# 可选整数、浮点数、char/varchar、各级 text/blob、日期时间、json、bool、binary/varbinary。
'type' => 'bigint',
# 可选。字段长度;默认 false;ID 常用 20,varchar 索引字段常控制在 191 以内。
'length' => 20,
# 可选。整数是否无符号;整数默认 true;false 允许负数。
'unsigned' => true,
# 可选。数值是否以零填充;默认 false;通常不建议开启。
'zerofill' => false,
# 可选。是否允许 NULL;默认 false;true 时 default 可以为 null。
'allow_null' => false,
# 可选。数据库默认值;默认 '';false 表示不生成 DEFAULT 子句。
'default' => false,
# 可选。字段附加属性;默认 '';
# 可选 AUTO_INCREMENT、ON UPDATE CURRENT_TIMESTAMP、SERIAL、SERIAL DEFAULT VALUE。
'extra' => 'AUTO_INCREMENT',
# 可选。是否标记为 Query 主字段;默认 false;它不会生成主键 SQL,且与 primary 索引并用会被判为双主键,本例依靠约定的 id 字段回退。
'primary' => false,
# 可选。是否建立独立对象缓存键;默认 false;主字段或唯一字段适合开启。
'cache_key' => true,
# 可选。是否允许按当前字段排序;默认 false;有索引的数字、短文本和日期字段适合开启。
'sortable' => true,
# 可选。wpdb 写入占位符;默认 %s;可选 %s、%d、%f,整数应显式使用 %d。
'pattern' => '%d',
# 可选。字段注释;默认 '';只写入数据库结构,不参与业务逻辑。
'comment' => '自增主键',
],
[
'name' => 'uuid',
'type' => 'varchar',
# 可选。是否启用 UUID 特殊字段;默认 false;true 时新增记录会自动生成 UUID。
'uuid' => true,
'default' => '',
'cache_key' => true,
],
[
'name' => 'code',
'type' => 'varchar',
'length' => 191,
# 可选。字符串是否使用二进制排序规则;默认 false;true 可让编码比较区分大小写。
'binary' => true,
# 可选。字段字符集;默认 '',继承数据库字符集;高级场景可填 ascii、utf8mb4 等。
'encoding' => '',
# 可选。字段排序规则;默认 '',继承数据库排序规则;高级场景可填 ascii_bin、utf8mb4_bin 等。
'collation' => '',
'default' => '',
# 可选。是否加入 Query 的 search 模糊搜索字段;默认 false。
'searchable' => true,
'sortable' => true,
# 可选。是否允许 code__in;默认 true;长文本字段建议关闭。
'in' => true,
# 可选。是否允许 code__not_in;默认 true;长文本字段建议关闭。
'not_in' => true,
'cache_key' => true,
# 可选。字段值改变时是否触发 BerlinDB transition 动作;默认 false。
'transition' => true,
# 可选。写入前验证回调;默认按类型推断;可传任意 callable,这里把编码清理为键名格式。
'validate' => 'sanitize_key',
# 可选。旧字段别名;默认 [];可在重命名查询字段时保留旧名称入口,但不会自动改数据库字段。
'aliases' => [
'invite_code',
'item_code',
],
# 可选。字段级 CRUD 权限;默认 [] 并继承 column_caps;这里仅覆盖 update。
'caps' => [
# 更新 code 需要的 WordPress capability;覆盖 column_caps.update。
'update' => 'manage_options',
],
],
[
'name' => 'valid_days',
'type' => 'int',
'length' => 10,
'unsigned' => true,
'default' => 365,
# 可选。读取数据库结果后的类型转换;默认按类型推断;可传任意 callable。
'cast' => 'absint',
'validate' => 'absint',
'pattern' => '%d',
'sortable' => true,
],
[
'name' => 'used_by',
'type' => 'bigint',
'length' => 20,
'unsigned' => true,
'allow_null' => true,
'default' => null,
'pattern' => '%d',
# relationships 默认 [],用于描述跨表关系,但当前 BerlinDB 源码仍标记为待实现,不建议依赖。
'relationships' => [],
],
[
'name' => 'used_at',
'type' => 'datetime',
'allow_null' => true,
'default' => null,
# 可选。是否允许在 date_query 中使用该字段;默认 false。
'date_query' => true,
'sortable' => true,
],
[
'name' => 'status',
'type' => 'varchar',
'length' => 20,
'default' => 'normal',
'in' => true,
'not_in' => true,
'sortable' => true,
'transition' => true,
'validate' => 'sanitize_key',
],
[
'name' => 'note',
'type' => 'text',
'allow_null' => true,
'default' => null,
'searchable' => true,
'in' => false,
'not_in' => false,
'validate' => 'wp_kses_post',
],
[
'name' => 'meta',
'type' => 'json',
'default' => '{}',
],
[
'name' => 'created_at',
'type' => 'datetime',
'allow_null' => true,
'default' => null,
# 可选。是否在新增记录时自动写入当前时间;默认 false;一个 Schema 通常只设一个 created 字段。
'created' => true,
'date_query' => true,
'sortable' => true,
],
[
'name' => 'updated_at',
'type' => 'datetime',
'allow_null' => true,
'default' => null,
# 可选。是否在新增和更新记录时自动写入当前时间;默认 false;一个 Schema 通常只设一个 modified 字段。
'modified' => true,
'date_query' => true,
'sortable' => true,
],
],
// 可选。索引定义;默认 [];支持 primary、unique、key、fulltext,索引字段必须存在于 columns。
'indexes' => [
[
# 可选。索引类型;默认 key;可选 primary、unique、key、fulltext,primary 不需要 name 且整个 Schema 只能有一个主键。
'type' => 'primary',
# 必填。索引字段列表;联合索引按数组顺序生成。
'columns' => ['id'],
],
[
# 非主键索引必填。索引名称;默认 '';在当前 Schema 内必须唯一。
'name' => 'code',
'type' => 'unique',
'columns' => ['code'],
# 可选。索引算法;默认 BTREE;常见可选 BTREE、HASH,实际支持取决于数据库引擎。
'method' => 'BTREE',
# 可选。索引注释;默认 ''。
'comment' => '业务编码唯一索引',
],
[
'name' => 'uuid',
'type' => 'key',
'columns' => ['uuid'],
# 可选。是否强制唯一;默认 false;true 与 type=unique 效果相同。
'unique' => true,
# 可选。显式 USING 算法;默认 '';非空时优先于 method,primary 和 fulltext 会忽略它。
'using' => 'BTREE',
],
[
'name' => 'status_created_at',
'type' => 'key',
'columns' => [
'status',
'created_at',
],
'method' => 'BTREE',
],
[
'name' => 'used_by',
'columns' => ['used_by'],
],
[
'name' => 'note_search',
'type' => 'fulltext',
'columns' => ['note'],
'comment' => '备注全文索引,仅供显式 MATCH AGAINST 查询使用',
],
],
// 可选。历史升级回调;默认 [];键为目标版本,值可为 callable 或当前子类 public/protected 方法名。
'upgrades' => [
// 从 2026081001 升到 2026081002 时同步完整 Schema,并迁移历史状态值。
'2026081002' => 'upgrade_2026081002',
],
// 可选。插件或模块前缀;默认 '';例如 wu,最终表名为 {$wpdb->prefix}wu_sample_items。
'prefix' => 'wu',
// 可选。数据表用途说明;默认 '';会传给 BerlinDB Table。
'description' => 'WU_DB 字段、索引、升级与查询示例表',
// 可选。是否为多站点全局表;默认 false;false 使用 $wpdb->prefix,true 使用网络全局前缀和网络 option。
'global' => false,
// 可选。保存数据库结构版本的 option 键;默认 '';留空由 BerlinDB 根据表名自动生成。
'db_version_key' => '',
// 可选。CREATE TABLE 的表注释;默认 '';字符串最长由 BerlinDB 限制为 2048 字符。
'comment' => 'WU_DB sample table',
// 可选。SQL 表别名;默认 '';留空由 Query 根据前缀和表名自动生成。
'table_alias' => 'sample',
// 可选。单条数据名称;默认 '',WU_DB 会回退为 name;用于生成 BerlinDB 动作钩子名称。
'item_name' => 'sample_item',
// 可选。多条数据名称;默认 '',WU_DB 会回退为 name;用于生成 BerlinDB 动作钩子名称。
'item_name_plural' => 'sample_items',
// 可选。查询结果 Row 类;默认 WU_DB_Row::class;可选任意已加载的 WU_DB_Row 子类。
'row_class' => WU_DB_Row::class,
// 可选。WordPress 对象缓存组;默认 '',WU_DB 会回退为 name;字段结构不兼容时建议更换缓存组。
'cache_group' => 'wu_sample_items_v2',
// 可选。所有字段的默认 CRUD 权限;默认四项均为 manage_options;字段自己的 caps 可逐项覆盖。
'column_caps' => [
// 读取字段需要的 WordPress capability;默认 manage_options。
'select' => 'read',
// 新增字段需要的 WordPress capability;默认 manage_options。
'insert' => 'edit_posts',
// 更新字段需要的 WordPress capability;默认 manage_options。
'update' => 'edit_posts',
// 删除记录时该字段需要的 WordPress capability;默认 manage_options。
'delete' => 'delete_posts',
],
];
/**
* 把 2026081001 的旧表升级到 2026081002。
* dbDelta() 幂等地增加或修改字段和索引;数据回填也必须可重复执行,
* 因为回调失败时 BerlinDB 不会推进版本,下次 admin_init 会再次执行。
*
* @return bool 结构同步和历史数据迁移是否全部成功。
*/
protected function upgrade_2026081002() {
global $wpdb;
$schema_sql = $this->get_schema()->get_create_table_string();
if ($schema_sql === '') return false;
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
// 使用当前完整 Schema,兼容升级中断后的重复执行。
$table_name = $this->get_table_name();
$wpdb->last_error = '';
dbDelta("CREATE TABLE {$table_name} (\n{$schema_sql}\n) {$wpdb->get_charset_collate()};");
if ($wpdb->last_error !== '') return false;
// dbDelta() 返回的是变更描述而不是成功布尔值,所以显式验证本版本的关键结构。
$table = $this->get_table_manager();
if (!$table->column_exists('meta') || !$table->index_exists('note_search')) return false;
// 历史版本曾使用 active,新版本统一为 normal;影响 0 行也属于成功。
$sql = $wpdb->prepare(
"UPDATE {$table_name} SET status = %s WHERE status = %s",
'normal',
'active'
);
if (!is_string($sql)) return false;
return $wpdb->query($sql) !== false;
}
}