PHP项目如何实现决策引擎?

wen java案例 4

本文目录导读:

PHP项目如何实现决策引擎?

  1. 主流实现方式详解
  2. 性能优化建议

在PHP项目中实现决策引擎(Decision Engine),通常是指构建一套能够根据预定义的业务规则、条件逻辑、数据输入等,自动做出判断、计算或推荐的系统。

典型的业务场景包括:风控评分、优惠券发放规则、审批流程路由、动态定价、推荐拦截等。

实现决策引擎有几种主流思路,从简单到复杂:

方案对比

方案 适用场景 优缺点
硬编码/简单if-else 规则极少、几乎不变的项目 简单直接、维护噩梦、无法动态修改
规则引擎库 (e.g., RulerZ, JsonLogic) 中小型项目、规则较多但逻辑不复杂 结构化表达、可动态配置、性能尚可
表达式引擎 (e.g., Symfony ExpressionLanguage) 需要用户或运营配置灵活公式 安全沙箱、支持复杂运算、学习成本较低
决策表 / 规则脚本 (可存于DB) 规则维度多(如:年龄+收入+城市),需要可视化维护 灵活性最高、适合复杂业务、需要自己写解析器
集成外部规则引擎 (e.g., Drools, OpenFGA) 超大规模、低延时、需要复杂推理的分布式场景 专业且强大、但引入Java中间件或PaaS,架构复杂

主流实现方式详解

使用轻量级PHP规则引擎库 —— RulerZ

RulerZ 是一个专门为PHP设计的规则引擎库,它允许你用声明式的方式定义和评估规则。

安装

composer require "rulerz-php/rulerz"

定义规则(支持字符串、数组、YAML) 规则字符串有点像SQL的WHERE子句,非常直观。

use RulerZ\RulerZ;
use RulerZ\Compiler\Compiler;
use RulerZ\Target\Native\Native;
// 1. 设置编译器
$compiler = new Compiler(sprintf('%s/rulerz', sys_get_temp_dir()));
$rulerz = new RulerZ($compiler, [new Native()]);
// 2. 创建一个用户数组(作为数据上下文)
$users = [
    ['name' => 'Alice', 'age' => 25, 'country' => 'CN', 'score' => 600],
    ['name' => 'Bob', 'age' => 30, 'country' => 'US', 'score' => 400],
    ['name' => 'Charlie', 'age' => 17, 'country' => 'CN', 'score' => 700],
];
// 3. 定义一条规则
$rule = 'age >= 18 and country = "CN" and score > 500';
// 4. 执行过滤(相当于决策引擎的「筛选」模块)
$qualifiedUsers = $rulerz->filter($users, $rule);
print_r($qualifiedUsers);
// 输出: [['name' => 'Alice', 'age' => 25, 'country' => 'CN', 'score' => 600]]

优势:支持动态修改规则字符串(可存入数据库或Redis),无需重新部署代码。


利用表达式引擎 —— Symfony ExpressionLanguage

如果你需要让非技术人员(如运营)配置复杂的数学公式或逻辑,Symfony ExpressionLanguage 是最安全的选择,它不会执行任意PHP函数,只提供安全沙箱。

安装

composer require symfony/expression-language

实现一个动态定价决策

use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
$el = new ExpressionLanguage();
// 场景:计算订单折扣
$context = [
    'order_total' => 1000,
    'user_level' => 'gold', // gold, silver, normal
    'is_birthday' => true,
    'coupon_value' => 50,
];
// 规则存储在数据库里,由运营配置:如果订单满500且是金牌会员且生日,打8折,否则用优惠券
$rule_expressions = [
    'level_discount' => 'order_total > 500 and user_level == "gold" and is_birthday == true ? 0.8 : 1.0',
    'use_coupon' => 'coupon_value > 0',
];
// 引擎执行
$levelDiscount = $el->evaluate($rule_expressions['level_discount'], $context);
$useCoupon = $el->evaluate($rule_expressions['use_coupon'], $context);
// 决策结果
$finalPrice = $context['order_total'] * $levelDiscount;
if ($useCoupon) {
    $finalPrice -= $context['coupon_value'];
}
echo "最终价格: " . max(0, $finalPrice); // 输出: 750

优势:语法接近自然语言(PHP表达式),支持严格类型检查,自带沙箱安全。


基于数据库的决策表(最灵活、但需自行开发)

这是大型企业级PHP项目的首选,将规则拆分成「条件表」和「动作表」,用JSON/PHP数组存储逻辑。

数据库设计示例

规则表 (rules) | id | rule_name | priority | status | created_at | |:---|:---|:---|:---|:---| | 1 | 风控规则-匹配欺诈用户 | 10 | active | 2025-01-01 | | 2 | 风控规则-大额交易拦截 | 20 | active | 2025-01-02 |

规则条件表 (rule_conditions) | id | rule_id | field | operator | value | logic_group | |:---|:---|:---|:---|:---|:---| | 1 | 1 | risk_score | >= | 80 | A | | 2 | 1 | country | IN | ['NG', 'RU'] | A | | 3 | 2 | amount | > | 50000 | B | | 4 | 2 | is_verified | = | false | B |

规则动作表 (rule_actions) | id | rule_id | action_type | action_value | |:---|:---|:---|:---| | 1 | 1 | block | true | | 2 | 1 | risk_level | high | | 3 | 2 | need_approval | true |

PHP决策引擎核心类

class DecisionEngine
{
    private array $rules;
    public function __construct(array $allRulesWithConditionsAndActions) {
        $this->rules = $allRulesWithConditionsAndActions;
    }
    /**
     * 根据上下文执行决策,返回所有命中规则的动作列表
     */
    public function evaluate(array $context): array
    {
        $actions = [];
        // 按规则优先级排序
        usort($this->rules, fn($a, $b) => $a['priority'] <=> $b['priority']);
        foreach ($this->rules as $rule) {
            $allConditionsMet = false;
            $groupResults = [];
            // 按 logic_group 分组条件 (同一组内是 AND,组之间是 OR)
            $groups = $this->groupConditions($rule['conditions']);
            foreach ($groups as $group) {
                $groupResult = true;
                foreach ($group as $condition) {
                    // 从上下文中取值
                    $fieldValue = $context[$condition['field']] ?? null;
                    $operator = $condition['operator'];
                    $expectedValue = $condition['value'];
                    // 调用比较函数
                    if (!$this->compare($fieldValue, $operator, $expectedValue)) {
                        $groupResult = false;
                        break; // 组内AND,一个不满足即跳出
                    }
                }
                $groupResults[] = $groupResult;
            }
            // 组之间是 OR 关系
            $allConditionsMet = in_array(true, $groupResults, true);
            if ($allConditionsMet) {
                // 收集动作,也可以只取第一个命中规则(根据业务)
                foreach ($rule['actions'] as $action) {
                    $actions[] = [
                        'rule_name' => $rule['rule_name'],
                        'action_type' => $action['action_type'],
                        'action_value' => $action['action_value'],
                    ];
                }
                // 高级:如果需要短路(只执行优先级最高的规则),在此break
                // break;
            }
        }
        return $actions;
    }
    private function compare($actual, string $operator, $expected): bool
    {
        return match ($operator) {
            '=', '==' => $actual == $expected,
            '!=' => $actual != $expected,
            '>' => $actual > $expected,
            '>=' => $actual >= $expected,
            '<' => $actual < $expected,
            '<=' => $actual <= $expected,
            'IN' => in_array($actual, (array) $expected),
            'NOT_IN' => !in_array($actual, (array) $expected),
            'BETWEEN' => $actual >= $expected[0] && $actual <= $expected[1],
            'LIKE' => str_contains($actual, $expected),
            default => throw new \InvalidArgumentException("Unsupported operator: $operator"),
        };
    }
    private function groupConditions(array $conditions): array
    {
        $groups = [];
        foreach ($conditions as $cond) {
            $groupId = $cond['logic_group'] ?? 'default';
            $groups[$groupId][] = $cond;
        }
        return array_values($groups);
    }
}
// 使用示例(伪代码)
$context = [
    'risk_score' => 85,
    'country' => 'NG',
    'amount' => 60000,
    'is_verified' => false,
];
$engine = new DecisionEngine($rulesFromDB);
$result = $engine->evaluate($context);
// $result: [['rule_name'=>'风控规则-匹配欺诈用户', 'block'=>true, 'risk_level'=>'high']]

优势:完全自定义,可以做出复杂的多组条件(AND/OR嵌套),规则可动态管理,效率高(可加缓存)。


性能优化建议

  1. 规则缓存:不要每次请求都查数据库,可以使用 Redis 或 本地 APCu 缓存编译后的规则树/数组。
  2. 编译/预计算:对于像 RulerZ 或 Symfony ExpressionLanguage,在规则不变的前提下,将解析后的AST(抽象语法树)缓存起来,避免重复解析。
  3. 短路求值:在优先级规则命中后,根据业务逻辑决定是否跳过后续规则,风控“直接拒绝”规则一旦命中,停止执行后续低级别规则。
  4. 减少循环:如果数据量极大(如百万用户批量评估),考虑将规则下推到数据库层(用SQL/存储过程)或使用PHP的并行扩展(如p线程、Swoole协程)。

你的业务需求 推荐方案
规则简单(<10条),几乎不变 硬编码,不需要折腾
需要运营频繁修改规则 RulerZSymfony ExpressionLanguage
规则有复杂层级(多组AND/OR嵌套),需要持久化到数据库 自行实现的 数据库决策表
团队有其他Java/Go服务,需要统一规则 集成外置规则引擎,PHP通过API/RPC调用
规则需要可视化配置(让非技术人员拖拽) 数据库决策表 + 自定义管理后台 或 使用现成引擎(如Drools)

对于绝大多数PHP项目,方案三(数据库决策表) 是最适合落地且维护负担较轻的选择。

抱歉,评论功能暂时关闭!