優雅且語義化的斷言之—將模型屬性斷言變為模型方法斷言

laravel_peng發表於2022-04-21

舉個例子,斷言直接裸露的寫在控制器中

控制器的方法:neckbeard:

<?php
use App\Models\Order;
class  OrderController  extends  Controller
{
    public  function  applyRefund(Order $order, ApplyRefundRequest $request)  {
        // 1.斷言 - 判斷是否未支付
        if (! $order->paid_at) {
        throw new InvalidRequestException('該訂單未支付,不可退款');
        }
        // 2.斷言 - 判斷訂單是否已退款
        if ($order->refund_status !== Order::REFUND_STATUS_PENDING) {
        throw new InvalidRequestException('該訂單已經申請過退款,請勿重複申請');
        }
        ...
    }
}

這樣寫不是不可以,也是正確的,但是存在三面問題::collision:

  1. 斷言有點長,看起來不夠優雅,控制器的程式碼應簡約一點。
  2. 斷言不具有語義化,閱讀起來不能快速理解其含義。
  3. 後期程式碼需要批次修改,麻煩且容易出錯。

針對以上問題我要做是以下兩點::dizzy:

  1. 長的斷言修改為方法,寫入到模型中,控制器直接呼叫模型方法即可。(程式碼看起來簡約且後期維護只需修改模型方法)
  2. 方法名使用快速可讀,可理解其中含義的名稱。

最佳化後的樣子:star2:

模型中的方法:

<?php
namespace  App\Models;
class  Order  extends  Model
{
    // 退訂狀態
    const  REFUND_STATUS_PENDING  =  'pending';

    // 是否未付款
    public function isUnpaid()  :bool
    {
        return ! ($this->paid_at);
    }
    // 是否訂單已退款
    public function isRefunded() :bool
    {
        return $this->refund_status !== self::REFUND_STATUS_PENDING;
    }
}

控制器中的呼叫:

<?php
use App\Models\Order;
class  OrderController  extends  Controller
{
    public  function  applyRefund(Order $order, ApplyRefundRequest $request)  {
        // 1.斷言 - 判斷是否未支付
        if ($order->isUnpaid()) {
            throw new InvalidRequestException('該訂單未支付,不可退款');
        }
        // 2.斷言 - 判斷訂單是否已退款
        if ($order->isRefunded()) {
            throw new InvalidRequestException('該訂單已經申請退款,請勿重複申請');
        }
        ...
    }
}

完了麼?並沒有,我們們繼續最佳化!!

模型中的方法:

<?php
namespace  App\Models;
class  Order  extends  Model
{
    // 退訂狀態
    const  REFUND_STATUS_PENDING  =  'pending';

    // 是否未付款
    public function isUnpaid()  :bool
    {
        return ! ($this->paid_at);
    }
    // 直接丟擲異常 - paid_at 未支付時值為null 
    public function isUnpaidNotSupported()  :bool
    {
        return ! $this->isUnpaid() ?: throw new InvalidRequestException('該訂單未支付,不可退款');
    }
    // 是否訂單已退款
    public function isRefunded() :bool
    {
        return $this->refund_status !== self::REFUND_STATUS_PENDING;
    }
    // 直接丟擲異常
    public function isRefundedNotSupported() :bool
    {
        return ! $this->isRefunded() ?: throw new InvalidRequestException('該訂單已經申請退款,請勿重複申請');;
    }
}

控制器中的呼叫:

<?php
use App\Models\Order;
class  OrderController  extends  Controller
{
    public  function  applyRefund(Order $order, ApplyRefundRequest $request)  {
        // 1.斷言 - 判斷是否未支付
        $order->isUnpaidNotSupported()
        // 2.斷言 - 判斷訂單是否已退款
        $order->isRefundedNotSupported()
        ...
    }
}

如此這樣,控制器的判斷看起來不那麼蹩腳,讀起來也很通順,後期程式碼維護直接修改模型方法,且屬性也可調整,挺好!以後都按照這種方式寫具有可讀可維護性的程式碼!賞心也悅目,哈哈!:kissing_heart: :kissing_heart:

還能再最佳化麼?

  1. 我想還能最佳化,直接控制器無需斷言。
  2. 將斷言放入中介軟體中。
  3. 將斷言放入請求類中,都可以,就看你要最佳化到那種程度了。
本作品採用《CC 協議》,轉載必須註明作者和本文連結
Xiao Peng

相關文章