发布于2026-07-19 阅读(0)
扫一扫,手机访问
Eloquent 的 get 和 set 访问器,说到底,它们就是来做字段转换的,别指望它们能撑起一个游戏状态机。正确的做法,是引入自定义 Cast 类,搭配 GameState 枚举或类,把状态逻辑封装起来,然后用 start()、end() 这样的显式方法去控制流转,而不是直接赋值。这样才能保证校验、事件和审计都到位。

记住,Eloquent 的 get* 和 set* 访问器,它们只负责字段转换,不触发状态校验、不维护流转约束、也不记录变更历史。所以,别指望它们能存游戏状态机。
cast + 自定义 Cast 类处理游戏状态值直接把 game_state 字段设为字符串或整型,再靠访问器硬编码状态逻辑,很快会失控。那么,正确的做法是什么?让 Eloquent 知道这个字段“是个状态对象”,而不是一个普通的字符串或整数。
GameState 枚举类(PHP 8.1+ 首选)或者一个经典 class,把所有的合法状态和流转规则都封装在里面。$casts = ['game_state' => GameStateCast::class] 声明类型转换。GameStateCast 这个类要实现 Illuminate\Contracts\Database\Eloquent\CastsAttributes 接口。它的 get 方法返回 GameState 实例,set 方法只接受合法实例,并存储其底层值,比如 'waiting' 或 1。这样一来,$game->game_state->isWaiting() 这样的调用就变得可读性很强,而 $game->game_state = GameState::playing() 这样的赋值也完全可控。数据库里存的,还是那个原始值,干净利落。
game_state允许用户或代码直接写 $game->game_state = 'ended',这就像打开潘多拉魔盒,会绕过所有业务约束——比如,没检查是否已结算、没通知玩家、没更新分数表。所以,必须定义显式方法。
start()、pause()、end() 等方法,每个方法内部都要做状态合法性判断。例如,只有 waiting 状态才能 start。$this->setAttribute('game_state', ...),而不是直接赋值属性,这样才能确保 set* 访问器和 cast 仍然生效。static::updating() 观察器,在保存前进行二次校验:如果新旧状态不合法,比如从 ended 跳回 playing,就抛出 InvalidArgumentException。GameStateChanged::dispatch($this, $old, $new),让积分、推送、日志等解耦逻辑能够响应。这个很常见。有人在 getIsPlayingAttribute() 里,去查关联的 Player 表,判断是否全员就位——这会导致 N+1 查询问题。更糟的是,有人在 setGameStateAttribute() 里自动调用 $this->end(),让赋值行为变得不可预测。
get* 访问器只能基于当前模型已加载的属性做计算,不能触发额外查询,也不能修改其他字段。canStart(): bool,由调用方明确决定何时执行。说到底,真正难的不是怎么让状态看起来像属性,而是怎么让每次状态跳转都留下痕迹、可回滚、可审计。Eloquent 属性机制只是数据管道,状态流转规则得靠领域模型自己守住边界。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8