ORM 模型

Viswoole 的 ORM 模型提供了面向对象的数据库操作方式,继承自 Viswoole\Database\Model 基类。模型封装了表映射、CRUD 操作、访问器/修改器、软删除等高级特性。

定义模型

基础模型

php
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;
}

模型属性

属性类型默认值说明
$tablestring(基于类名推断)对应的数据表名
$pkstringid主键字段名
$channelNamestring|nullnull指定使用的数据库通道
$hiddenarray[]序列化时隐藏的字段
$autoWriteTimestampint0自动时间戳:0=关闭,1=仅创建时间,2=仅更新时间,3=创建和更新时间
$enableSoftDeleteboolfalse是否启用软删除

隐藏字段

通过 $hidden 属性控制序列化输出的字段,常用于隐藏敏感信息:

php
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) 判断:

php
// 按 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 — 查询多条

php
// 查询所有记录
$users = UsersModel::select();

// 带条件查询
$activeUsers = UsersModel::where('status', 1)->select();

条件查询

模型支持与查询构造器相同的链式条件调用:

php
// 组合条件查询
$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() 静态方法。

php
$user = UsersModel::create([
    'name' => '张三',
    'email' => 'zhangsan@example.com',
    'status' => 1,
]);

echo "新用户 ID: {$user->id}";

如需创建后修改并保存,可对 create() 返回的 DataSet 操作:

php
$user = UsersModel::create(['name' => '张三', 'email' => 'zhangsan@example.com']);
// DataSet 支持 save() 方法保存修改
$user->merge(['status' => 1])->save();

更新数据

通过实例更新

注意DataSet 没有 update() 方法。修改记录应使用 merge() 合并数据后调用 save() 保存,或直接赋值后 save()

php
$user = UsersModel::find(1);

// 方式一:直接赋值后 save()
$user->name = '李四';
$user->email = 'lisi@example.com';
$user->save();

// 方式二:使用 merge() 传数组后 save()
$user->merge([
    'name' => '王五',
    'phone' => '13800138000',
])->save();

批量更新

php
UsersModel::where('status', 0)->update(['status' => -1]);

删除数据

delete — 删除记录

php
// 通过实例删除
$user = UsersModel::find(1);
$user->delete();

// 按条件批量删除
UsersModel::whereIn('id', [10, 11, 12])->delete();

自动时间戳

通过 $autoWriteTimestamp 属性控制自动时间戳的写入模式:

说明
0关闭自动时间戳
1仅自动写入创建时间
2仅自动写入更新时间
3创建和更新时间都写

默认字段名为 create_time(创建时间)和 update_time(更新时间),可通过 $createTimeFieldName$updateTimeFieldName 属性自定义。

php
class UsersModel extends Model
{
    protected string $table = 'users';
    protected string $pk = 'id';

    /**
     * 开启自动时间戳(创建和更新时间都写)
     * 创建时自动写入 create_time
     * 更新时自动写入 update_time
     */
    protected int $autoWriteTimestamp = 3;
}
操作自动写入字段
插入 (create)create_timeupdate_time(模式 1/3 写 create_time,模式 2/3 写 update_time)
更新 (update/save)update_time(模式 2/3)

注意:确保数据表中存在 create_timeupdate_time 字段(datetime 类型)。

软删除

软删除不会物理删除记录,而是将 delete_time 字段设置为当前时间,便于数据恢复和审计追踪。默认字段名为 delete_time,可通过 $softDeleteFieldName 属性自定义。

启用软删除

php
class UsersModel extends Model
{
    protected string $table = 'users';
    protected string $pk = 'id';

    /** 启用软删除功能 */
    protected bool $enableSoftDelete = true;
}

软删除操作

php
// 软删除(设置 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

php
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

事务管理

模型操作同样支持事务,推荐使用闭包事务保证数据一致性:

php
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;
}

完整模型示例

php
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 '******';
    }
}