vertilia / recif
规则引擎的代码生成器
Requires
- php: >=7.4
- ext-json: *
Requires (Dev)
- phpunit/phpunit: ^9.5
README
将规则文件中的条件从json或yaml格式转换为本地代码的规则引擎解析器。
描述
当企业表达新的或更新的规则集时,这些规则集可能由程序员编码到现有信息系统中,或者可能使用系统可以解析和评估的特殊方言声明。在这种情况下,不需要程序员干预,但解析和评估的时间可能很长。每次系统需要评估规则集中特定上下文的条件(例如,定义产品细分)时,系统都需要解析规则文件,从顶级条件开始处理条件,直到找到匹配的规则,所有这些操作都递归调用规则集中提到的每个操作的方法。
使用recif,当规则文件中的规则集更新时,我们调用转换器将规则转换为本地代码。该代码代表文件中的规则集条件。所有操作都已转换为本地结构,如OR、AND等,因此完整规则集的执行是最佳的。要在项目中使用该类,请实例化对象并调用其evaluate()
方法,将上下文作为其参数。如果找到匹配项,则evaluate()
将返回true
(或相应的值);如果传递的上下文不匹配任何条件,则返回false
(是的,您可以提供自己的返回值)。
示例1
单个条件。输入上下文是文数字值,如果大于10则返回true
{ "gt": [{"cx":""}, 10] }
示例2
使用AND
运算符组合多个条件。输入上下文是文数字值。如果上下文值在0..100
范围内则返回true
{ "and": [ {"ge": [{"cx":""}, 0]}, {"le": [{"cx":""}, 100]} ] }
示例3
复杂条件:上下文是一个包含country
和currency
条目的关联数组,必须与以下矩阵匹配
如果条件匹配,它将返回上下文的大陆名称(北美洲
或欧洲
)而不是简单的true
值。
{ "or": [ { "and": [ {"in": [{"cx":"country"}, {"_": ["DE", "ES", "FR", "IT"]}]}, {"eq": [{"cx":"currency"}, "EUR"]} ], "return": "Europe" }, { "and": [ {"eq": [{"cx":"country"}, "US"]}, {"eq": [{"cx":"currency"}, "USD"]} ], "return": "North America" } ] }
注意:要表示数组,您需要使用特殊的{"_": ...}
内联运算符。
安装
使用composer
要求
$ composer require vertilia/recif
命令行使用
使用composer
安装后,recif
二进制文件作为vendor/bin/recif
可用。
$ vendor/bin/recif
Usage: recif [options] <ruleset.json >Ruleset.php
Options:
-C COMMENT add comments text, should not contain open / close comment sequences (default: not set)
-n NAMESPACE use namespace (default: not set)
-s generate static evaluate() method (default: not set)
-c CLASS use class name (default: Ruleset)
-e CLASS extends class (default: not set)
-i INTERFACE implements interfaces, comma separated list (default: not set)
-x TYPE define context parameter type (default: not set)
-r TYPE define method return type (default: not set)
-S VALUE return on success default value (default: true)
-F VALUE return on fail default value (default: false)
-d declare static_types (default: not set)
-5 generate php5-compatible code
-y input stream in YAML format (needs yaml PECL extension)
最简单的规则集:评估true
语句
echo 'true' | recif
同一级别:评估1 > 0
echo '{"gt":[1,0]}' | recif
做有意义的事:检查上下文,向生成的类添加命名空间并指定evaluate()
方法的返回类型
echo '{"gt":[{"cx":""},0]}' | recif -n MyNamespace\\Level2 -r bool
规则文件语法
规则文件包含一个以根条件开始的条件树。
每个条件是一个对象,包含一个单一必需条目,其中包含操作名称作为键和参数作为值。可选的return
条目可以包含操作匹配时的返回值,否则将返回默认的true
。
如or
之类的运算符必须包含一个参数数组。一元运算符如not
对一个值操作,而不是数组。有关可用操作的全列表,请参阅操作参考。
每个参数可能具有以下形式之一
- 文数字值或数组(通常用于将值与上下文字段进行比较),如
true
或"US"
; - 对象(表示上下文或另一个操作),如
{"cx":"currency"}
或{"eq": [{"cx":"country"}, "US"]}
或{"_": ["DE", "ES", "FR", "IT"]}
。
返回值表示当相应的条件评估为 true
时,返回给外部操作的数据值。它可以声明为任何类型。如果值是对象,它可能表示上下文或其属性之一,如 {"cx":""}
或 {"cx":"currency.iso_name"}
。在这种情况下,将返回此元素或属性的值。
操作符参考
下面的示例使用 json 语法表示,含义以 PHP 代码的形式给出。
上下文操作符
cx
- 上下文值(参数:如果上下文是字面量,则为空字符串;如果为数组,则为点分隔的键列表)
示例: {"cx":""}
, {"cx":"url.path"}
含义: $context
, $context['url']['path']
比较操作符
eq
- 等于
示例: {"eq": [{"cx":""}, 0]}
含义: $context == 0
===
- 全等(等于且类型相同)
示例: {"===": [{"cx":""}, '15h']}
含义: $context === '15h'
ne
- 不等于
示例: {"ne": [{"cx":""}, 0]}
含义: $context != 0
!==
- 不全等(不相等或类型不同)
示例: {"!==": [{"cx":""}, '15h']}
含义: $context !== '15h'
lt
- 小于
示例: {"lt": [{"cx":""}, 0]}
含义: $context < 0
le
- 小于等于
示例: {"le": [{"cx":""}, 0]}
含义: $context <= 0
gt
- 大于
示例: {"gt": [{"cx":""}, 0]}
含义: $context > 0
ge
- 大于等于
示例: {"ge": [{"cx":""}, 0]}
含义: $context >= 0
逻辑操作符
or
- 或(两个或更多参数)
示例: {"or": [{"eq": [{"cx":""}, 0]}, {"eq": [{"cx":""}, 10]}]}
含义: ($context == 0) or ($context == 10)
and
- 与(两个或更多参数)
示例: {"and": [{"gt": [{"cx":""}, 0]}, {"lt": [{"cx":""}, 10]}]}
含义: ($context > 0) and ($context < 10)
not
- 非(一元操作符,单个参数)
示例: {"not": {"lt": [{"cx":""}, 0]}}
含义: ! ($context < 0)
数学操作
mod
- 取模(参数:值,模数)
示例: {"mod": [{"cx":""}, 10]}
含义: $context % 10
rnd
- 在指定范围内生成随机整数(参数:最小值,最大值)
示例: {"rnd": [1, 10]}
含义: rand(1, 10)
数组操作
in
- 元素存在于数组中(参数:元素,数组)
示例: {"in": [{"cx":""}, {"_": [1, 2, 3, 5, 8, 13]}]}
含义: in_array($context, [1, 2, 3, 5, 8, 13])
inx
- 通过索引从哈希表获取元素(参数:索引,哈希表)
示例: {"inx": [{"cx":""}, {"_": ["FR": "Europe", "US": "North America"]}]}
, {"inx": [{"cx":""}, {"_": ["US": "North America", "default": "Europe"]}]}
含义: (["FR" => "Europe", "US" => "North America"])[$context]
, (["US" => "North America"])[$context] ?? "Europe"
字符串操作
sub
- 针对目标字符串的子字符串存在(参数:针,目标字符串)。在 Unicode 不区分大小写的模式下搜索。
示例: {"sub": ["word", {"cx":""}]}
含义: mb_stripos($context, "word") !== false
re
- 字符串与正则表达式匹配(参数:正则表达式,文本)。
示例: {"re": ["/^\w+$/u", {"cx":""}]}
含义: preg_match('/^\w+$/u', $context)
spf
- 格式化字符串(参数:格式字符串,零个或多个值)。
示例: {"spf": ["%.2f", {"cx":""}]}
含义: sprintf('%.2f', $context)
内联操作符
_
(下划线) - 直接使用参数值(任何类型的参数)。
示例: {"_": true}
, {"_": ["a": "b"]}
, {"_": [1, {"_": []}, 3]}
含义: true
, ['a' => 'b']
, [1, [], 3]
原生函数调用
fn
- 使用提供的参数调用原生函数(参数:函数名,参数1,...)。
示例: {"fn": ["Currencies::getRateForCountry", {"cx":"country"}]}
含义: Currencies::getRateForCountry($context['country'])
代码示例
示例1
规则文件
{ "gt": [{"cx":""}, 10] }
生成的代码
<?php class Ruleset { public function evaluate($context) { // return on success $success = true; // rules if ($context > 10) { return $success; } // not found return false; } }
以下示例中生成的代码将创建在 Ruleset::evaluate()
方法内部,就像上面的示例一样,所以只展示相应的 // rules
部分。
示例2
规则文件
{ "and": [ {"ge": [{"cx":""}, 0]}, {"le": [{"cx":""}, 100]} ] }
生成的代码
// rules if ( ($context >= 0) and ($context <= 100) ) { return $success; }
示例3
规则文件
{ "or": [ { "and": [ {"eq": [{"cx":"country"}, "US"]}, {"eq": [{"cx":"currency"}, "USD"]} ], "return": "North America" }, { "and": [ {"in": [{"cx":"country"}, {"_": ["DE", "ES", "FR", "IT"]}]}, {"eq": [{"cx":"currency"}, "EUR"]} ], "return": "Europe" } ] }
生成的代码
// rules if ( ( ( ($context['country'] ?? null) === "US" and ($context['currency'] ?? null) === "USD" ) and is_string($success = "North America") ) or ( ( in_array($context['country'] ?? null, ["DE", "ES", "FR", "IT"]) and ($context['currency'] ?? null) === "EUR" ) and is_string($success = "Europe") ) ) { return $success; }
注意 在 php-5
兼容模式下, ($context['currency'] ?? null)
和类似的构造将替换为 isset($context['currency']) and $context['currency']
构造。