PHP 怎么DSL 规则

wen PHP项目 3

本文目录导读:

PHP 怎么DSL 规则

  1. 方法链式调用(Fluent Interface)
  2. 使用匿名函数或闭包
  3. 使用数组配置 DSL
  4. 使用解析器模式(复杂 DSL)
  5. 使用现有库
  6. 选择建议

在 PHP 中实现 DSL(领域特定语言)规则,主要有以下几种方式:

方法链式调用(Fluent Interface)

这是最常用的方式,通过方法链构建规则。

<?php
class RuleBuilder {
    private $rules = [];
    private $currentField = '';
    private $currentOperator = '';
    private $currentValue = null;
    public function field($field) {
        $this->currentField = $field;
        return $this;
    }
    public function equals($value) {
        $this->currentOperator = '=';
        $this->currentValue = $value;
        return $this->addRule();
    }
    public function greaterThan($value) {
        $this->currentOperator = '>';
        $this->currentValue = $value;
        return $this->addRule();
    }
    public function lessThan($value) {
        $this->currentOperator = '<';
        $this->currentValue = $value;
        return $this->addRule();
    }
    public function addRule() {
        $this->rules[] = [
            'field' => $this->currentField,
            'operator' => $this->currentOperator,
            'value' => $this->currentValue
        ];
        return $this;
    }
    public function getRules() {
        return $this->rules;
    }
}
// 使用示例
$builder = new RuleBuilder();
$rules = $builder
    ->field('age')->greaterThan(18)
    ->field('status')->equals('active')
    ->field('score')->lessThan(100)
    ->getRules();
print_r($rules);

使用匿名函数或闭包

<?php
class RuleEngine {
    private $rules = [];
    public function when(callable $condition, callable $action) {
        $this->rules[] = [
            'condition' => $condition,
            'action' => $action
        ];
        return $this;
    }
    public function evaluate($data) {
        foreach ($this->rules as $rule) {
            if ($rule['condition']($data)) {
                return $rule['action']($data);
            }
        }
        return null;
    }
}
// 使用示例
$engine = new RuleEngine();
$result = $engine
    ->when(
        fn($user) => $user['age'] >= 18 && $user['status'] === 'active',
        fn($user) => "已成年且活跃用户"
    )
    ->when(
        fn($user) => $user['age'] < 18,
        fn($user) => "未成年用户"
    )
    ->evaluate(['age' => 25, 'status' => 'active']);
echo $result;

使用数组配置 DSL

<?php
class ArrayRuleDSL {
    private $rules = [];
    public function addRule(array $rule) {
        $this->rules[] = $rule;
        return $this;
    }
    public function evaluate($data) {
        foreach ($this->rules as $rule) {
            if ($this->matches($rule['condition'], $data)) {
                return $rule['result'];
            }
        }
        return null;
    }
    private function matches($condition, $data) {
        foreach ($condition as $field => $constraint) {
            if (is_array($constraint)) {
                foreach ($constraint as $op => $value) {
                    if (!$this->applyOperator($op, $data[$field] ?? null, $value)) {
                        return false;
                    }
                }
            } elseif ($data[$field] !== $constraint) {
                return false;
            }
        }
        return true;
    }
    private function applyOperator($operator, $actual, $expected) {
        switch ($operator) {
            case 'gt': return $actual > $expected;
            case 'gte': return $actual >= $expected;
            case 'lt': return $actual < $expected;
            case 'lte': return $actual <= $expected;
            case 'eq': return $actual === $expected;
            case 'neq': return $actual !== $expected;
            case 'in': return in_array($actual, $expected);
            case 'between': return $actual >= $expected[0] && $actual <= $expected[1];
            default: return false;
        }
    }
}
// 使用示例
$ruleDSL = new ArrayRuleDSL();
$ruleDSL
    ->addRule([
        'condition' => [
            'age' => ['gte' => 18, 'lte' => 65],
            'status' => 'active'
        ],
        'result' => '符合资质的用户'
    ])
    ->addRule([
        'condition' => [
            'age' => ['lt' => 18]
        ],
        'result' => '未成年用户'
    ]);
echo $ruleDSL->evaluate(['age' => 25, 'status' => 'active']);

使用解析器模式(复杂 DSL)

<?php
class DSLParser {
    private $data = [];
    public function parse($expression) {
        // 解析类似:"IF age > 18 AND status = 'active' THEN '符合条件'"
        $expression = trim($expression);
        if (preg_match('/IF (.+?) THEN (.+)/i', $expression, $matches)) {
            $condition = $this->parseCondition($matches[1]);
            $result = $matches[2];
            if ($this->evaluateCondition($condition)) {
                return $result;
            }
        }
        return null;
    }
    private function parseCondition($condition) {
        // 支持 AND/OR
        $parts = preg_split('/\s+(AND|OR)\s+/i', $condition, -1, PREG_SPLIT_DELIM_CAPTURE);
        $groups = [];
        $currentGroup = [];
        foreach ($parts as $part) {
            if (strtoupper($part) === 'AND' || strtoupper($part) === 'OR') {
                $currentGroup['operator'] = strtoupper($part);
            } else {
                $currentGroup['conditions'][] = $this->parseSimpleCondition($part);
            }
        }
        return $currentGroup;
    }
    private function parseSimpleCondition($condition) {
        // 解析:field > value, field = 'value', field BETWEEN x AND y
        if (preg_match('/^(.+?)\s*(>=|<=|==|!=|>|<|=)\s*(.+)$/', trim($condition), $matches)) {
            return [
                'field' => trim($matches[1]),
                'operator' => $matches[2],
                'value' => $this->parseValue($matches[3])
            ];
        }
        return null;
    }
    private function parseValue($value) {
        $value = trim($value);
        if (preg_match('/^\'(.+)\'$/', $value, $matches)) {
            return $matches[1];
        }
        if (is_numeric($value)) {
            return (float)$value;
        }
        return $value;
    }
    private function evaluateCondition($condition) {
        if (isset($condition['operator'])) {
            $result = false;
            foreach ($condition['conditions'] as $subCondition) {
                $subResult = $this->evaluateSimple($subCondition);
                if ($condition['operator'] === 'AND') {
                    $result = $result && $subResult;
                } else {
                    $result = $result || $subResult;
                }
            }
            return $result;
        }
        return $this->evaluateSimple($condition['conditions'][0]);
    }
    private function evaluateSimple($condition) {
        $fieldValue = $this->data[$condition['field']] ?? null;
        switch ($condition['operator']) {
            case '>': return $fieldValue > $condition['value'];
            case '<': return $fieldValue < $condition['value'];
            case '>=': return $fieldValue >= $condition['value'];
            case '<=': return $fieldValue <= $condition['value'];
            case '=': return $fieldValue == $condition['value'];
            case '==': return $fieldValue === $condition['value'];
            case '!=': return $fieldValue != $condition['value'];
            default: return false;
        }
    }
    public function setData($data) {
        $this->data = $data;
        return $this;
    }
}
// 使用示例
$parser = new DSLParser();
$result = $parser
    ->setData(['age' => 25, 'status' => 'active'])
    ->parse("IF age >= 18 AND status = 'active' THEN '资格通过'");
echo $result;

使用现有库

也可以使用一些现成的 PHP DSL 库:

使用 symfony/expression-language

composer require symfony/expression-language
<?php
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
$language = new ExpressionLanguage();
$data = ['age' => 25, 'status' => 'active'];
// 评估表达式
$result = $language->evaluate(
    "age >= 18 and status == 'active'",
    $data
);
echo $result ? '通过' : '不通过';

使用 hanson/expression

composer require hanson/expression
<?php
use Hanson\Expression\Expression;
$expression = new Expression();
$result = $expression->evaluate("age > 18 && status = 'active'", ['age' => 20, 'status' => 'active']);

选择建议

  1. 简单规则:使用方法链式调用或数组配置
  2. 复杂业务规则:使用闭包组合
  3. 需要动态配置:使用表达式解析器
  4. 生产环境:考虑使用成熟的库(如 symfony/expression-language)

这些方式可以根据你的具体需求选择或组合使用,DSL 的核心目的是让规则更加可读、可维护,降低代码耦合度。

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