ORM 模型

ORM 模型(Object-Relational Mapping)以面向对象方式操作数据表,基类为 Viswoole\Database\Model。模型在查询构造器之上自动处理表名推断、时间戳写入、软删除、获取器/修改器与关联预加载,静态方法调用自动转发到模型查询器。

本文依据框架源码 src/Database/Model.phpsrc/Database/Model/Query.phpsrc/Database/Collection/{BaseCollection,DataSet,Collection}.php 编写。

定义模型

php
namespace App\Model;

use Viswoole\Database\Model;

class UserModel extends Model
{
    // 对应数据表;省略时按类名推断(见下文推断规则)
    protected string $table = 'user';

    // 主键字段名
    protected string $pk = 'id';

    // 数据库通道,null 使用默认通道
    protected ?string $channelName = null;
}

表名推断规则

未显式定义 $table 时,框架从类名推断:去除 $suffix 后缀(默认 Model),驼峰转蛇形并小写。例如 UserProfileModeluser_profileUserModeluser

模型属性

属性类型默认值说明
$tablestring类名推断数据表名
$suffixstringModel推断表名时去除的类名后缀
$pkstringid主键字段名
$channelNamestring|nullnull绑定的数据库通道,见 多通道
$hiddenarray[]toArray() 序列化时隐藏的字段
$autoWritePkboolfalse写入时自动生成主键,模型需定义 public function autoWritePk() 返回主键值

查询数据

模型静态方法自动转发到模型查询器(Viswoole\Database\Model\Query,继承查询构造器),因此查询构造器的全部条件、排序、分页方法均可直接使用:

php
use App\Model\UserModel;

// 静态调用:UserModel::where(...) → 转发到 Query 实例
$users = UserModel::where('status', 1)
    ->whereIn('type', [1, 2])
    ->orderBy('id', 'desc')
    ->page(1, 20)
    ->select();

// 实例调用(IDE 提示更友好):推荐 $model->query()->select()
$model = new UserModel();
$user  = $model->query()->where('id', 1)->find();

推荐调用方式

$model->query() / $model->query-> 与静态调用 UserModel::where(...) 结果一致。实例方式拥有完整的 IDE 智能提示,且省去魔术方法转发开销,推荐在模型方法内部使用 $this->query->

查询结果

方法返回说明
find($value = null, $allowEmpty = true)DataSet主键或条件查单条;空结果为空 DataSet,用 isEmpty() 判断;$allowEmpty = false 时空结果抛 DataNotFoundException
first($allowEmpty = true)DataSet条件查单条(自动 LIMIT 1)
select() / get()Collection多行集合
getArray()array多行纯数组
count() / min() / max() / avg() / sum()标量聚合查询
cursor() / chunk(int $size)Generator大结果集游标/分批处理

DataSet(单行)与 Collection(多行)支持数组式访问与链式集合操作:

php
$user = UserModel::find(1);
if (!$user->isEmpty()) {
    $user->name = '李四';      // 修改字段(自动追踪变更)
    $user->save();             // 仅持久化变更的字段
    $user->merge(['email' => 'lisi@example.com'])->save();
    $user->delete();           // 删除当前行(启用软删除时为软删除)
}

// Collection:filter / map / sortBy / where / update / delete 等
UserModel::where('status', 0)->select()->update(['status' => -1]);

toArray() 会应用模型 $hidden 隐藏字段与获取器转换;对象直接 JSON 序列化(如响应输出)等价于 toArray()

创建数据

php
// create(array $data, array $columns = []):写入并返回含主键的 DataSet
// $columns 为允许写入的字段白名单
$user = UserModel::create(
    ['name' => '张三', 'status' => 1, 'hack' => 'x'],
    ['name', 'status']          // hack 字段被过滤
);
echo $user->id;

// 也可使用构造器风格的写入
UserModel::insert(['name' => '张三']);
UserModel::where('id', 1)->update(['name' => '李四']);

自动时间戳

$autoWriteTimestamp 控制创建/更新时间的自动写入:

行为
0关闭(默认)
1插入时写入创建时间
2更新时写入更新时间
3插入写创建时间 + 更新写更新时间
属性默认值说明
$createTimeFieldNamecreate_time创建时间字段名
$updateTimeFieldNameupdate_time更新时间字段名
$createTimeFormatTypedatetime创建时间格式
$updateTimeFormatTypedatetime更新时间格式

格式类型支持 datetimeY-m-d H:i:s)、timestamp(Unix 时间戳)、dateY-m-d),或任意 date() 可识别的格式串。手动传入的时间字段不会被覆盖。

php
class UserModel extends Model
{
    protected int $autoWriteTimestamp = 3;   // 创建与更新时间都自动写入
}

软删除

软删除(Soft Delete)不物理删除记录,而是向删除标记字段写入当前时间;查询时自动排除已删除数据。

php
class UserModel extends Model
{
    protected bool $enableSoftDelete = true;                  // 启用软删除
    protected string $softDeleteFieldName = 'delete_time';    // 标记字段名(默认)
    protected string $softDeleteFieldType = 'datetime';       // 字段值格式
    protected null|string|int $softDeleteFieldDefaultValue = null; // 未删除状态的字段值
}
操作写法说明
软删除$user->delete()写入 delete_time 当前时间
硬删除$user->delete(true)物理 DELETE,$real = true 时忽略软删除
批量删除UserModel::whereIn('id', [1,2])->delete()同样遵循软删除
恢复UserModel::restore(1)UserModel::where('id', 1)->restore()delete_time 写回默认值;须传主键或带 where 条件,未启用软删除返回 0
含已删数据UserModel::withTrashed(true)->select()查询包含已软删除记录;withTrashed(false) 恢复过滤

软删除过滤原理

启用软删除后,SELECT 自动追加 delete_time IS NULL(或 delete_time = 默认值)条件;withTrashed(true) 可临时取消该过滤。

没有 onlyTrashed()

框架未提供 onlyTrashed()。仅查已删除数据可用 UserModel::withTrashed(true)->whereNotNull('delete_time')->select()

获取器与修改器

获取器(Accessor)在读取序列化时转换字段值,修改器(Mutator)在写入数据库前转换字段值。命名规则:蛇形字段名转驼峰后加前缀,如 user_name 对应 getUserNameAttr / setUserNameAttr

php
class UserModel extends Model
{
    /**
     * 获取器:状态值转文字(仅在 toArray()/JSON 序列化时应用)
     */
    public function getStatusAttr(mixed $value): string
    {
        return match ((int)$value) {
            1 => '正常',
            0 => '禁用',
            default => '未知',
        };
    }

    /**
     * 修改器:密码写入前自动哈希(insert / insertGetId / update / create 均生效)
     */
    public function setPasswordAttr(mixed $value): string
    {
        return password_hash((string)$value, PASSWORD_DEFAULT);
    }
}

$user = UserModel::find(1);
$user->toArray()['status'];    // '正常'(获取器生效)

UserModel::create(['name' => 'a', 'password' => 'secret']);
// 库中 password 存储的是哈希值(修改器生效)

使用要点:

  • 必须声明为 public:框架从模型外部作用域校验方法可见性,非 public 视为未定义;
  • 获取器仅在 toArray() 序列化时应用,直接访问属性返回数据库原始值;
  • 修改器只转换字段值、不改变字段名;批量插入逐行应用;
  • 框架自动注入的字段(create_time / update_time / 自动主键)不经过修改器。

通配获取器/修改器

需要按字段名模式统一转换时,可定义通配方法 getAttr(string $field, mixed $value) / setAttr(string $field, mixed $value)$field 为蛇形原名字段名。查找顺序:具名 get|set{Field}Attr 优先,未命中回退通配方法,两者皆无返回原值。

php
class UserModel extends Model
{
    /**
     * 通配获取器:*_id 字段统一字符串化,避免 JS 处理大整数精度丢失
     */
    public function getAttr(string $field, mixed $value): mixed
    {
        if ($value !== null && str_ends_with($field, '_id')) {
            return (string)$value;
        }
        return $value;
    }
}

完整示例

php
namespace App\Model;

use Viswoole\Database\Model;

class UserModel extends Model
{
    protected string $table = 'user';
    protected string $pk = 'id';
    protected array $hidden = ['password'];        // 序列化隐藏字段
    protected int $autoWriteTimestamp = 1;         // 插入时写入 create_time
    protected bool $enableSoftDelete = true;       // 启用软删除

    public function setPasswordAttr(mixed $value): string
    {
        return password_hash((string)$value, PASSWORD_DEFAULT);
    }

    public function getStatusAttr(mixed $value): string
    {
        return (int)$value === 1 ? '正常' : '禁用';
    }
}

下一步