ORM 模型
Viswoole 的 ORM 模型提供了面向对象的数据库操作方式,继承自 Viswoole\Database\Model 基类。模型封装了表映射、CRUD 操作、访问器/修改器、软删除等高级特性。
定义模型
基础模型
namespace App\Model;
use Viswoole\Database\Model;
/**
* 用户模型
* 对应数据库 users 表
*/
class UsersModel extends Model
{
/** @var string 数据表名 */
protected string $table = 'users';
/** @var string 主键字段名 */
protected string $pk = 'id';
/** @var string|null 数据库通道名称,null 表示使用默认通道 */
protected ?string $channelName = null;
}模型属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
$table | string | (基于类名推断) | 对应的数据表名 |
$pk | string | id | 主键字段名 |
$channelName | string|null | null | 指定使用的数据库通道 |
$hidden | array | [] | 序列化时隐藏的字段 |
$autoWriteTimestamp | int | 0 | 自动时间戳:0=关闭,1=仅创建时间,2=仅更新时间,3=创建和更新时间 |
$enableSoftDelete | bool | false | 是否启用软删除 |
隐藏字段
通过 $hidden 属性控制序列化输出的字段,常用于隐藏敏感信息:
class UsersModel extends Model
{
protected string $table = 'users';
protected string $pk = 'id';
/**
* 序列化时隐藏密码和手机号
* @var array<string>
*/
protected array $hidden = ['password', 'phone'];
}
$user = UsersModel::find(1);
// 输出时不包含 password 和 phone 字段查询数据
find — 主键查找
find() 返回 DataSet 对象,即使未查询到记录也会返回空 DataSet(非 null)。空 DataSet 在 PHP 中恒为 truthy,因此不能直接用 if ($user) 判断:
// 按 ID 查找
$user = UsersModel::find(1);
// 正确判断方式一:使用 isEmpty()
if (!$user->isEmpty()) {
echo $user->name;
}
// 正确判断方式二:传入 false 禁用空结果返回(无记录时返回 null)
$user = UsersModel::find(1, false);
if ($user) {
echo $user->name;
}select — 查询多条
// 查询所有记录
$users = UsersModel::select();
// 带条件查询
$activeUsers = UsersModel::where('status', 1)->select();条件查询
模型支持与查询构造器相同的链式条件调用:
// 组合条件查询
$users = UsersModel::where('status', 1)
->whereIn('role', ['admin', 'editor'])
->where('age', '>=', 18)
->orderBy('create_time', 'desc')
->page(1, 20)
->select();
// 聚合查询
$count = UsersModel::where('status', 1)->count();
$maxId = UsersModel::max('id');创建数据
create — 创建并保存
注意:
Model基类没有save()方法,save()仅存在于DataSet对象上。创建数据请使用create()静态方法。
$user = UsersModel::create([
'name' => '张三',
'email' => 'zhangsan@example.com',
'status' => 1,
]);
echo "新用户 ID: {$user->id}";如需创建后修改并保存,可对 create() 返回的 DataSet 操作:
$user = UsersModel::create(['name' => '张三', 'email' => 'zhangsan@example.com']);
// DataSet 支持 save() 方法保存修改
$user->merge(['status' => 1])->save();更新数据
通过实例更新
注意:
DataSet没有update()方法。修改记录应使用merge()合并数据后调用save()保存,或直接赋值后save()。
$user = UsersModel::find(1);
// 方式一:直接赋值后 save()
$user->name = '李四';
$user->email = 'lisi@example.com';
$user->save();
// 方式二:使用 merge() 传数组后 save()
$user->merge([
'name' => '王五',
'phone' => '13800138000',
])->save();批量更新
UsersModel::where('status', 0)->update(['status' => -1]);删除数据
delete — 删除记录
// 通过实例删除
$user = UsersModel::find(1);
$user->delete();
// 按条件批量删除
UsersModel::whereIn('id', [10, 11, 12])->delete();自动时间戳
通过 $autoWriteTimestamp 属性控制自动时间戳的写入模式:
| 值 | 说明 |
|---|---|
0 | 关闭自动时间戳 |
1 | 仅自动写入创建时间 |
2 | 仅自动写入更新时间 |
3 | 创建和更新时间都写 |
默认字段名为 create_time(创建时间)和 update_time(更新时间),可通过 $createTimeFieldName 和 $updateTimeFieldName 属性自定义。
class UsersModel extends Model
{
protected string $table = 'users';
protected string $pk = 'id';
/**
* 开启自动时间戳(创建和更新时间都写)
* 创建时自动写入 create_time
* 更新时自动写入 update_time
*/
protected int $autoWriteTimestamp = 3;
}| 操作 | 自动写入字段 |
|---|---|
| 插入 (create) | create_time、update_time(模式 1/3 写 create_time,模式 2/3 写 update_time) |
| 更新 (update/save) | update_time(模式 2/3) |
注意:确保数据表中存在
create_time和update_time字段(datetime 类型)。
软删除
软删除不会物理删除记录,而是将 delete_time 字段设置为当前时间,便于数据恢复和审计追踪。默认字段名为 delete_time,可通过 $softDeleteFieldName 属性自定义。
启用软删除
class UsersModel extends Model
{
protected string $table = 'users';
protected string $pk = 'id';
/** 启用软删除功能 */
protected bool $enableSoftDelete = true;
}软删除操作
// 软删除(设置 delete_time)
$user = UsersModel::find(1);
$user->delete(); // 不会物理删除,仅标记 delete_time
// 恢复已软删除的记录
// 注意:restore() 定义在 Model\Query 上,不能在 DataSet 实例上调用
// 方式一:传入主键
UsersModel::restore(1);
// 方式二:通过 where 条件
UsersModel::where('id', 1)->restore();
// 包含已软删除的记录查询
$allUsers = UsersModel::withTrashed(true)->select();withTrashed 说明
| 方法 | 说明 |
|---|---|
withTrashed(true) | 查询结果包含已软删除的记录 |
withTrashed(false) 或默认 | 仅查询未删除的记录(排除 delete_time 非 null) |
注意:框架未提供
onlyTrashed()方法。如需仅查询已软删除的记录,可结合withTrashed(true)与whereNotNull('delete_time')条件实现。
访问器 (Accessor)
访问器用于在获取模型属性值时进行转换或格式化处理。定义规则为:get + 字段名(驼峰)+ Attr。
class UsersModel extends Model
{
protected string $table = 'users';
protected string $pk = 'id';
protected array $hidden = ['password'];
/**
* 密码访问器 — 返回脱敏值
*
* @param mixed $value 数据库原始值
* @return mixed 处理后的值
*/
protected function getPasswordAttr(mixed $value): mixed
{
return '******';
}
/**
* 状态访问器 — 数值转为文字描述
*
* @param mixed $value 状态数值
* @return string 状态描述
*/
protected function getStatusAttr(mixed $value): string
{
return match ((int)$value) {
1 => '正常',
0 => '禁用',
default => '未知',
};
}
/**
* 注册时间访问器 — 格式化日期显示
*
* @param mixed $value 时间戳或日期字符串
* @return string 格式化后的日期
*/
protected function getCreateTimeAttr(mixed $value): string
{
return date('Y-m-d H:i', strtotime((string)$value));
}
}
// 使用
// 注意:访问器仅在 toArray() 序列化时应用,直接访问属性不会触发访问器
$user = UsersModel::find(1);
$array = $user->toArray();
echo $array['password']; // 输出: ******
echo $array['status']; // 输出: 正常
echo $array['create_time']; // 输出: 2025-06-22 10:30事务管理
模型操作同样支持事务,推荐使用闭包事务保证数据一致性:
use Viswoole\Database\Facade\Db;
// 闭包事务 — 自动提交/回滚
Db::startTransaction(function () {
$user = UsersModel::create([
'name' => '张三',
'email' => 'zhangsan@example.com',
]);
// 创建关联的资料记录
ProfileModel::create([
'user_id' => $user->id,
'bio' => '新人报道',
]);
// 如果以上任一操作抛出异常,整个事务自动回滚
});
// 手动事务
Db::startTransaction();
try {
UsersModel::find(1)->merge(['balance' => 100])->save();
OrderModel::create(['user_id' => 1, 'amount' => 50]);
Db::commit();
} catch (\Throwable $e) {
Db::rollBack();
throw $e;
}完整模型示例
namespace App\Model;
use Viswoole\Database\Model;
/**
* 用户模型
*
* 职责:用户数据的 CRUD 操作、状态转换、关联关系定义
*/
class UsersModel extends Model
{
/** @var string 对应数据表 */
protected string $table = 'users';
/** @var string 主键字段 */
protected string $pk = 'id';
/** @var array<string> 序列化隐藏字段 */
protected array $hidden = ['password', 'pay_password'];
/** @var int 开启自动时间戳 */
protected int $autoWriteTimestamp = 1;
/** @var bool 启用软删除 */
protected bool $enableSoftDelete = true;
/**
* 状态码转文字描述
*
* @param mixed $value 状态值
* @return string 状态描述
*/
protected function getStatusAttr(mixed $value): string
{
return match ((int)$value) {
1 => '正常',
0 => '禁用',
-1 => '注销',
default => '未知',
};
}
/**
* 密码脱敏
*
* @param mixed $value 原始密码哈希
* @return string 固定掩码
*/
protected function getPasswordAttr(mixed $value): mixed
{
return '******';
}
}