【问题标题】:PHP - Laravel - Best way to manage conditional rulesPHP - Laravel - 管理条件规则的最佳方式
【发布时间】:2020-05-22 04:34:57
【问题描述】:

我有一个使用 Laravel 框架的应用程序,有一些条件规则我不知道什么是最好的编码和维护方式。

用例:有条件地应用促销代码

  • 可以在特定日期或日期范围内应用促销代码
  • 订单可使用促销代码 >= $100
  • 可以为特定项目申请促销代码
  • ...

基本的解决方案是编写多个 IF ELSE 语句来逐一检查。例如:

if ($promo->specific_date) {

} 
elseif ($promo->date_range >= 'date' && $promo->specific_date <= 'date') {

}

if ($totalAmount < 100) {
    // Dont allow
}

if (! $promo->allowed_items) {
    // Dont allow
}

// More conditions ...

我可以看到代码在测试和维护时会出现问题。

所以我想知道是否有更好的方法来处理这个问题?例如。使用 OOP 方式?

P/S:澄清我的用例:

  • 我需要通过所有规则才能使促销有效
  • 我正在考虑创建一个规则模型,以便我可以有一个 CRUD 来管理它们,并且在后端,我可以运行一个查询来获取所有规则,然后调用一个类来管道并检查每个规则......(不确定这是好主意还是坏主意)

谢谢,

【问题讨论】:

  • 据我所知switchif
  • 不同促销代码的规则是否不同(我的意思是,您是否有多个促销代码)?我假设是的,但以防万一。
  • @NipunTharuksha I'm doubtful of that. 在任何情况下,在它们之间做出决定时,性能应该无关紧要。
  • @Jeto 感谢您指点我以便更好地理解。请您检查一下switch vs if else
  • 嗯,关键是使用内联 IF 和/或 SWITCH 难以阅读和消化;这就是我试图避免的。

标签: php laravel if-statement switch-statement conditional-statements


【解决方案1】:

您可以利用 Laravel 管道对您的订单进行某种检查。

想象一下,您可以从数据库中正确提取约束配置并构建一个数组(或类似 ConstraintBag 实例),其中包含您需要检查的所有约束:

configuration
$constraints = [
    DateRangeConstraint::class,
    TotalAmountConstraint::class,
    AllowedItemsContraint::class,
];

每个约束都可以遵循相同的接口(本 PoC 中的 Constraint),该接口将定义一个 handle 方法:

use Closure;

class DateRangeConstraint implements Constraint
{
    public function handle($order, Closure $next)
    {
        if ($order->promo->start_date >= 'date' || $order->promo->end_date <= 'date') {
            throw new PromotionConstraintException($this);
        }

        return $next($order);
    }
}

然后在您的控制器/服务方法中,您可以在管道中使用此规则数组,并通过约束传递订单对象(或包含验证所有约束所需的所有部分的对象)。如果其中任何一个失败,您可以触发自定义异常(可能每个约束类别一个/每个约束一个)并返回validation 流程的结果:

// Do not forget to add the use statement
use Illuminate\Pipeline\Pipeline;

class PromotionValidationService
{
    protected $constraints;

    // Pass in the constraints array you have already built
    public function __construct($constraints)
    {
        $this->constraints = $constraints;
    }

    // Start the validation process and cycle through all the constraints
    // I would pass in the order object as you might need to access the total
    // order amount and/or the items in the order
    public function validate($order)
    {
        try {
            app(Pipeline::class)
                ->send($order)
                ->through($this->constraints);
        } catch (PromotionConstraintException $exception) {
            // Handle the exception and return false or rethrow
            // the exception for further handling from the caller.
            return false;
        }

        return true;
    }
}

显然,这仍然是一个概念证明,需要更多的研究和架构规划来处理您可能需要检查的各种约束(例如:传递整个 $order 对象可能不是最好的主意,或者它可能检查促销约束时尚不可用)。但是,这可能是一个灵活的替代方案,可以替代需要针对每次更改进行编辑的固定 if/else 序列。

【讨论】:

    【解决方案2】:

    您可以在 Promotion 模型中定义促销属性(这意味着它们可能应该存储在您的数据库中的某个位置),然后有一个标准化的验证器,您可以调用任何促销。

    这里有一些示例/伪代码来解释这个过程(它是 PHP 7.4,只需删除属性类型以使其适用于以前的版本):

    final class Promotion
    {
      private DateTime $minDate;
      private DateTime $maxDate;
      private DateTime $minAmount;
      private array $allowedItems;
    
      public function getMinDate(): DateTime
      {
        return $this->minDate;
      }
    
      public function getMaxDate(): DateTime
      {
        return $this->maxDate;
      }
    
      public function getMinAmount(): DateTime
      {
        return $this->minAmount;
      }
    
      public function getAllowedItems(): array
      {
        return $this->allowedItems;
      }
    }
    
    final class PromotionValidator
    {
      public function isPromotionValid(Promotion $promo, array $purchasedItems, int $totalAmount): bool
      {
        $now = new \DateTime();
    
        if ($now < $promo->getMinDate() || $now > $promo->getMaxDate()) {
          return false;
        }
    
        if ($totalAmount < $promo->getMinAmount()) {
          return false;
        }
    
        if (count(array_intersect($purchasedItems, $promo->getAllowedItems())) !== count($promo->getAllowedItems())) {
          return false;
        }
    
        return true;
      }
    }
    

    用法:

    $promotionValidator = new PromotionValidator();
    $promoIsValid = $promotionValidator->isPromotionValid($promo, $cartItems, $cartAmount);
    

    【讨论】:

    • 感谢@Jeto,这就是我最初的想法,创建一个服务来验证。但是,我担心的是我想避免使用嵌套的内联 if 语句来检查(就像您在 isPromotionValid() 方法中所拥有的那样),因为将来很难维护和扩展(例如添加更多规则、重构规则。 ..)
    • 那么规则模型怎么样,促销可以绑定到任意数量的? isPromotionValid 代码将类似于 foreach ($promo-&gt;getRules() as $rule) { if (!$rule-&gt;verify($promo, ...) return false; } return true;。那会有帮助吗?在某些时候,您将不得不编写检查它们的代码(除非您想使用像ExpressionLanguage 这样的东西)。
    【解决方案3】:

    在此处使用 switch 语句,因为它比 if

    更快

    默认设置promo_code 标志false 因为现在只想我的任何条件它为真然后设置promo_code 标志为真..

    如果你想一个一个地使用 if 语句,那很好,因为它易于阅读和维护

    $promo_code_flag = false;
    
    if ($promo->specific_date) {
        $promo_code_flag = true;
    } 
    elseif ($promo->date_range >= 'date' && $promo->specific_date <= 'date') {
         $promo_code_flag = true;
    }
    
    if ($totalAmount > 100) {
        $promo_code_flag = true;
    }
    
    if ( $promo->allowed_items) {
        $promo_code_flag = true;
    }
    
    if($promo_code_flag) { 
     //allow promo code
    }//other wise it will not allowe
    

    【讨论】:

    • 这是错误的,因为如果验证了任何条规则,而不是所有规则,它将允许促销代码。此外,您不需要一直检查所有规则。
    • @Jeto 他只希望验证任何条件比促销代码允许,所以我根据答案给出该条件
    • 他首先测试无效规则,所以对我来说,他显然打算只有在所有这些规则都通过时才验证促销。
    • 谢谢大家,我很抱歉,我一开始并不清楚。提供的 sn-p 只是一个简单的示例。我的情况是我需要通过所有规则才能使其有效。
    猜你喜欢
    • 2015-06-29
    • 2016-05-17
    • 2010-11-28
    • 1970-01-01
    • 2013-08-02
    • 2021-08-18
    • 2020-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多