天天看点

在 Laravel 的数据库模型中使用状态模式

 在 Laravel 的数据库模型中使用状态模式

在讲怎么在 Laravel 模型中使用状态模式之前先让我们来熟悉一下状态模式。

允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

在实际的开发中我们经常会遇到一个表会存在不同的状态,比如常见的订单表一般会有<code>预定</code>,<code>支付</code>,<code>已出货</code>,<code>已取消</code>等。注:由于我们使用的 <code>ORM</code> 工具会把数据库中表的每一行映射成一个对象实例,为了更好的表述模式,我会把表中的每一行称为对象。比如我会把某条订单记录称为订单对象,使得我们用面向对象的思维去思考业务。

我们常见的状态管理会向下面的代码这样,在 <code>Order</code> 类里面有 3 个行为分别是 <code>pay()</code>,<code>shipping()</code>,<code>cancel()</code>。在执行每个行为方法之前我们都会去验证当前对象的状态是否满足执行条件。

这样做使得跟状态相关的验证会分散在不同的地方,甚至会把这些验证逻辑泄露到控制器和服务层,在对象状态复杂且需求多变的情况下,后期的维护成本很高且容易出错。为了体现开放封闭原则和单一原则,则可以使用状态模式来管理对象的不同状态。

在 Laravel 的数据库模型中使用状态模式
把跟每个状态相关的逻辑都封装在了每个状态对象里面,状态的行为需要调用 order 对象的方法来改变 order 的状态,所以在需要改变 order 状态的行为里面需要持有一个 order 对象的引用。
这样一来把每个状态相关的逻辑都封装起来,很清晰的就能看出每个状态可以执行哪些行为哪些不能执行。每当要添加一个新的状态的时候只需要添加一个 <code>OrderState</code> 接口的一个实现就可以了。状态与状态之间互不依赖,也消除了之前对象行为的状态判断语句。
我在尝试在 Laravel 的模型上使用状态模式时遇到了一个问题,就是模型在数据库中查询到数据后怎么把字段 <code>state</code>(通常叫 state,也可以是其他表示状态的枚举字段)的值映射成我们对象上的某个状态对象。比如我们 order 表中的某一行的 state 字段是 2,那么映射到 order 对象上应该是 order 对象有一个 <code>PaidOrderState</code> 的对象。通过查询文档和查看源代码找到 Eloquent 在查询到数据后会触发每个模型的 <code>retrieved</code> 的事件。通过监听监听这个事件,我们可以在获取到数据后编写代码自动把 state 状态值和具体的状态对象进行关系映射。

在 Order 模型上添加如下事件监听代码:

这样一来就解决了从数据表到模型对象的映射问题。

目前为止还存在一个问题就是数据回写的问题,当我们从对象的某个状态迁移到另外一个状态的后再通过对象的 save () 方法保存到数据库的时候,其实这个时候 state 的字段值并没有改变。解决办法就是在对象修改状态的时候去修改 state 值。

像这样:

只需要添 <code>$this-&gt;stateCode = PaidOrderState::STATE_CODE;</code> 来修改 state 的值就可以了。

由于 Eloquent 是基于活动记录(Activity record)的 ORM,所以很难使用继承结构来使用更多的设计模式。但是 Eloquent 提供了 2 个非常有用的模型事件分别是 <code>retrieved</code> 和 <code>saving</code>。retrieved 事件我们可以在模型查询到数据的时候对模型对象做一些更改,比如上面用它解决了映射问题。saving 是在模型保存的时候触发的事件,可以用它在保存到数据库的时候再对模型做一些更改,比如我们在解决 state 字段回写到数据库的问题也可以使用 saving 事件来解决。这样一来通过这 2 个事件,我们可以有更多的想象空间来运用更多的设计模式来解决复杂的业务问题。