eloquent 的 getattribute/setattribute 不适合建模业务流程状态,应采用状态模式+专用状态类+状态迁移约束,eloquent 仅负责持久化底层字段;原因在于 accessor/mutator 会导致状态逻辑散落、绕过业务规则、无法审计轨迹。

直接说结论: Eloquent 的 getAttribute / setAttribute 不适合建模业务流程状态,硬套会导致状态逻辑散落、不可测试、难以审计;真正该用的是「状态模式 + 专用状态类 + 状态迁移约束」,Eloquent 只负责持久化底层字段(如 state 字符串或整型)。
为什么不能靠 Accessor/Mutator 搞状态流转?
很多人一上来就写 getStateAttribute 返回一个带方法的对象,或者在 setStateAttribute 里做校验和副作用——这会立刻踩中三个坑:
-
getStateAttribute每次访问都 new 对象,无法共享上下文(比如审批人、时间戳、前置条件) - 状态变更逻辑藏在 Mutator 里,
$model->state = 'approved'看似简单,实则绕过所有业务规则(比如“只有待审状态才能批准”) - 无法记录状态变迁轨迹:谁、何时、从哪来、到哪去、触发动作是什么——而这是工作流的核心诉求
正确姿势:用独立状态类 + 显式 transition 方法
把状态本身变成可行为的对象,而不是字符串别名。例如定义 PendingState、ApprovedState、RejectedState,每个类实现 canApprove()、approve(Model $model, array $context) 等契约方法。
模型里只保留基础字段和委托:
class Order extends Model
{
protected $casts = ['state' => 'string'];
public function getState(): StateInterface
{
return match($this->state) {
'pending' => new PendingState($this),
'approved' => new ApprovedState($this),
'rejected' => new RejectedState($this),
default => throw new InvalidArgumentException("Unknown state: {$this->state}"),
};
}
public function approve(array $context = []): void
{
$this->getState()->approve($context);
$this->save();
}
}
关键点:
- 状态类必须接收
$model实例,以便读取关联数据(如$model->user_id)、调用$model->save()或触发事件 - 所有状态变更必须通过显式方法(
approve()、reject()),禁止直接赋值$model->state = 'xxx' - 迁移动作里应写入审计日志:
StateTransition::create([...])
数据库字段设计要支持追溯和并发安全
光存一个 state 字段远远不够。至少加三列:
-
state:当前状态(索引字段,用于查询) -
last_state_at:datetime,最后变更时间(避免靠created_at/updated_at推断) -
state_version:整型,每次变更自增(用于乐观锁,防止并发覆盖)
如果需要完整轨迹,不要在主表加 JSON 字段存历史——单独建 state_transitions 表,含 from_state、to_state、triggered_by、triggered_via(是 API 还是后台按钮)、metadata(JSON,存审批意见等)。
别忘了 Laravel 自带的工具怎么配合用
状态类不是孤立存在,要嵌入 Laravel 生态:
- 用
Route::middleware('can:approve,order')配合 Gate 定义策略,策略里调$order->getState()->canApprove() - 用
dispatch(new ApproveOrder($order, $user))把状态变更转成 Job,便于失败重试和解耦 - 用
ModelObserver监听saving事件,校验$model->isDirty('state')时是否满足迁移约束(比如不允许跳过审核直接发布)
最常被忽略的一点:状态类的构造函数里不要做 heavy IO(如查用户权限),它可能被高频调用;权限检查应放在 canApprove() 这类按需执行的方法里,并加缓存。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











