天天看點

在 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 個事件,我們可以有更多的想象空間來運用更多的設計模式來解決複雜的業務問題。