模型(Model / ORM-lite)
Mtphp\Database\Model —— 轻量模型,用最简单的静态方法完成最常见的增删改查,无需写 SQL、无需复杂配置。
namespace App\Models;
use Mtphp\Database\Model;
class User extends Model
{
protected static ?string $table = 'user'; // 表名(不含前缀);可省略,默认按类名推导为 user
protected static string $primaryKey = 'id'; // 主键列名(默认 id)
}
User::find(1); // 按主键查一条(不存在返回 null)
User::where('age', '>', 18)->get(); // 链式查询(返回数组)
User::where('age', '>', 18)->orderBy('id','desc')->limit(10)->get();
User::findBy('name', '张三'); // 按某列查一条
User::create(['name' => '张三']); // 插入,返回自增主键 ID
User::update(1, ['name' => '李四']); // 按主键更新,返回影响行数
User::delete(1); // 按主键删除,返回影响行数
User::count(); // 计数
要点:
static::query()返回底层Query构造器,需要 JOIN / 聚合时直接继续链式即可(与第 10.3 节能力一致)。- 表名未声明时按“类名转 snake_case”推导(
User→user,OrderItem→order_item)。 app/Models/User.php是一个可直接复制改写的示例模型。
13.1 类型转换(casts)与访问器 / 修改器
声明 $casts 后,字段在读出(DB→应用)与写入(应用→DB)时自动按类型处理,无需手动 json_encode / (int) 等。
class User extends Model
{
// 支持:int/integer、float、bool/boolean、string、json/array、datetime/date/timestamp
protected static array $casts = [
'is_admin' => 'bool',
'score' => 'int',
'meta' => 'json', // 入库 JSON 字符串,读出为数组
'created_at' => 'datetime', // 字符串原样保留(或整型时间戳转 Y-m-d H:i:s)
];
}
$u = User::find(1);
$u['is_admin']; // 已是 bool
$u['meta']; // 已是数组(无需手动 json_decode)
- 访问器 / 修改器:重写
hasMutators()返回true,并定义getXxxAttribute($value)/setXxxAttribute($value)即可在读出 / 写入时拦截该字段(与$casts二选一,访问器优先)。 CastUser::create(['meta' => ['k' => 1]])写入时自动 JSON 编码入库;读出再自动解码为数组。
13.2 关联计数(withCount)
一对多 / 一对一场景下,常需要“每个用户发了多少条帖子”这类计数。用 withCount 一条查询搞定,结果行会多出 {关联名}_count 字段:
$users = User::withCount('posts')->get();
foreach ($users as $u) {
echo $u['name'] . ' 发了 ' . $u['posts_count'] . ' 篇';
}
// 可与 with() 预加载并用:行内 posts 数组长度 === posts_count
$users = User::with('posts')->withCount('posts')->get();
底层按
SELECT 外键, COUNT(*) ... WHERE 外键 IN (...) GROUP BY 外键批量统计,避免 N+1;withCount同样支持'a.b'嵌套写法(按关联定义递归)。
13.3 模型事件(Model Events)
在「写操作前 / 后」挂载钩子(如自动填充、审计、拦截非法写入),无需改动 create/update/delete 内部逻辑。支持的事件:
| 阶段 | 事件 |
|---|---|
| 写前(可拦截) | saving、creating、updating、deleting、restoring、forceDeleting |
| 写后 | saved、created、updated、deleted、restored、forceDeleted |
use Mtphp\Database\Model;
class User extends Model
{
protected static ?string $table = 'users';
}
// 方式一:直接注册回调(写前事件返回 false 可拦截本次写操作)
User::registerModelEvent('creating', function (string $class, $payload) {
if (empty($payload['name'])) {
return false; // 拦截插入,create() 返回 0
}
});
User::registerModelEvent('created', function (string $class, $payload) {
// $payload 含写入数据与自增主键:$payload['id']
});
// 方式二:观察者(方法名即事件名,自动绑定)
class UserObserver
{
public function creating(string $class, $payload) { /* ... */ }
public function created(string $class, $payload) { /* ... */ }
}
User::observe(UserObserver::class); // 或传入实例:User::observe(new UserObserver());
$id = User::create(['name' => 'neo']); // 触发 saving→creating→(写入)→created→saved
User::update($id, ['name' => 'neo2']); // 触发 saving→updating→(写入)→updated→saved
User::delete($id); // 触发 deleting→(写入)→deleted
要点:
- 监听器签名统一为
function (string $modelClass, mixed $payload): ?bool;写前事件(creating/updating/saving/deleting/restoring/forceDeleting)显式返回false即可拦截,写后事件返回值被忽略。 - 事件按「模型类」隔离:
User注册的监听器不会影响Post;同一模型可用User::clearModelEvents()清空(测试常用)。 - 软删除模型(
use SoftDeletes)同样会触发deleting/deleted/restoring/restored/forceDeleting/forceDeleted。