eftec / minilang
PHP的迷你脚本语言
Requires
- php: >=7.4
- ext-ctype: *
Requires (Dev)
- phpunit/phpunit: ^8.5.39
README
此库用于以简单且强大的定义存储业务逻辑。
PHP的迷你脚本语言。它执行三个简单的任务。
- (可选) 设置一些初始值 (INIT)。
- 它评估一个逻辑表达式 (WHERE)。
- 如果表达式(逻辑)为真,则执行SET表达式 (SET),因此我们可以更改变量的值,调用函数等。
- (可选) 如果表达式(逻辑)为假,则执行ELSE表达式 (INIT)。
例如
when var1>5 and var2>20 then var3=20 // when and then
init var5=20 when var1>5 and var2>20 then var3=var5 // init, when and then
init var5=20 when var1>5 and var2>20 then var3=var5 else var3=var20 // init, when, then and else
when var1>$abc then var3=var5 // $abc is a PHP variable.
为什么我们需要迷你脚本?
有时我们需要在“如果某些值等于,则设置或执行另一段代码”的基础上执行任意代码。
在PHP中,我们可以编写以下代码。
if($condition) { $variable=1; }
然而,此代码在运行时执行。如果我们需要在该代码的特定点执行此代码怎么办?
我们可以执行以下代码
$script='if($condition) { $variable=1; }'; // and later.. eval($script);
此解决方案有效(并且只有在调用eval命令时才会执行)。但它很冗长,容易出错,而且很危险。
我们的库执行相同的操作,但更安全、更简洁。
$mini->separate("when condition then variable=1");
目录
入门
使用composer安装它
composer requires eftec/minilang
创建新项目
use eftec\minilang\MiniLang; include "../lib/MiniLang.php"; // or the right path to MiniLang.php $mini=new MiniLang(); $mini->separate("when field1=1 then field2=2"); // we set the logic of the language but we are not executed it yet. $mini->separate("when field1=2 then field2=4"); // we set more logic. $result=['field1'=>1,'field2'=>0]; // used for variables. $callback=new stdClass(); // used for callbacks if any $mini->evalAllLogic($callback,$result); var_dump($result);
另一个示例
use eftec\minilang\MiniLang; include "../lib/MiniLang.php"; $result=['var1'=>'hello']; $global1="hello"; $mini=new MiniLang(null,$result); $mini->throwError=false; // if error then we store the errors in $mini->errorLog; $mini->separate('when var1="hello" then var2="world" '); // if var1 is equals "hello" then var2 is set "world" $mini->separate('when $global1="hello" then $global2="world" '); // we can also use php variables (global) $mini->evalAllLogic(false); // false means it continues to evaluate more expressions if any // (true means that it only evaluates the first expression where "when" is valid) var_dump($result); // array(2) { ["var1"]=> string(5) "hello" ["var2"]=> string(5) "world" } var_dump($global2); // string(5) "world"
方法
构造函数
__construct(&$caller,&$dict,array $specialCom=[],$areaName=[],$serviceClass=null)
- object $caller 表示具有回调的对象
- array $dict 初始值字典
- array $specialCom 特殊命令。它调用调用者的函数。
- array $areaName 它标记可以称为“somevalue”的特殊区域。*null|object $serviceClass 服务类。默认情况下,它使用$caller。
reset()
它重置以前的定义,但变量、服务和区域。
setCaller(&$caller)
设置调用者对象。调用者对象可以是服务类,其中包含可以在脚本内部调用的方法。
setDict(&$dict)
设置包含系统使用的变量的字典。
function separate($miniScript)
它将表达式发送到MiniLang,并将其分解为其部分。脚本不会执行,但会解析。
evalLogic($index)
它评估一个逻辑。它返回true或false。
- $index是separate()添加的逻辑数量
evalAllLogic($stopOnFound = true, $start = false)
- 布尔值 $stopOnFound:如果某些评估匹配则退出。
- 布尔值 $start:如果为真,则始终评估“init”表达式。
evalSet($idx = 0, $position = 'set')
它设置一个或多个值。它不考虑 WHERE 是否为真。
- 整型 $idx:表达式的数量。
- 字符串 $position = ['set', 'init'][$i],可以是 set 或 init。
字段
$throwError
布尔值。
- 如果为真(默认值),则当找到错误时(例如,如果方法不存在),库抛出错误。
- 如果为假,则每个错误都捕获在数组 $errorLog 中。
$errorLog
字符串数组。如果 $throwError 为假,则每个错误都存储在这里。
示例
$this->throwError=false; $mini->separate("when FIELDDOESNOTEXIST=1 then field2=2"); var_dump($this->errorLog);
定义
语法。
代码的语法分为四个部分:INIT、WHERE(或当)、SET(或 THEN)和 ELSE。
示例
$mini->separate("when field1=1 then field2=2");
它表示如果 field1=1,则将 field2 设置为 2。
变量
变量通过 varname
定义。
示例: examples/examplevariable.php
$mini=new MiniLang(); $mini->separate("when field1>0 then field2=3"); // we prepare the language $variables=['field1'=>1]; // we define regular variables $callback=new stdClass(); $mini->evalAllLogic($callback,$variables); // we set the variables and run the languageand run the language var_dump($variables); // field1=1, field2=3
由PHP对象定义的变量
变量可以包含 PHP 对象,并且可以调用和访问其内部的字段。
varname.field
- 如果字段存在,则使用它。
- 如果字段不存在,则使用 CALLER 对象的方法。
- 如果 CALLER 的方法不存在,则尝试使用服务类的服务方法。
- 如果服务类的服务方法不存在,则尝试使用类 MiniLang 的内部方法(带有前缀 _)。例如,函数 _param()。
- 最后,如果一切失败,则引发错误。
代码示例 examples/examplevariable2.php
class MyModel { var $id=1; var $value=""; public function __construct($id=0, $value="") { $this->id = $id; $this->value = $value; } } class ClassCaller { public function Processcaller($arg) { echo "Caller: setting the variable {$arg->id}<br>"; } } class ClassService { public function ProcessService($arg) { echo "Service: setting the variable {$arg->id}<br>"; } } $mini=new MiniLang([],[],new ClassService()); $mini->separate("when field1.id>0 then field2.value=3 and field3.processcaller and processcaller(field3) and processservice(field3)"); // we prepare the language $variables=['field1'=>new MyModel(1,"hi") ,'field2'=>new MyModel(2,'') ,'field3'=>new MyModel(3,'')]; // we define regular variables $callback=new ClassCaller(); $mini->evalAllLogic($callback,$variables,false); // we set the variables and run the languageand run the language var_dump($variables);
- field2.value 引用字段 "value"(MyModel)
- field3.processcaller 引用 ClassCaller::processcaller() 方法
- processcaller(field3) 与 field3.processcaller 相同
- processservice(field3) 调用 ClassService::processservice() 方法
由PHP数组定义的变量
变量可以包含关联/索引数组,并且可以读取和访问其中的元素。
示例
$mini=new MiniLang(null, [ 'vararray'=>['associindex'=>'hi',0=>'a',1=>'b',2=>'c',3=>'d',4=>'last','a'=>['b'=>['c'=>'nested']]] ] );
vararray.associndex // vararray['associindex'] ('hi') vararray.4 // vararray[4] 'last' vararray.123 // it will throw an error (out of index) vararray.param('a.b.c')) // vararray['a']['b']['c'] ('nested') param(vararray,'a.b.c')) // vararray['a']['b']['c'] ('nested') vararray._first // first element ('hi') vararray._last // last element ('last') vararray._count // returns the number of elements. (6)
- 如果元素存在,则使用它。
- 如果元素不存在,则使用调用者(CALLER)的方法。
- 如果调用者(CALLER)的方法不存在,则尝试使用服务类的方法。
- 最后,如果一切失败,则引发错误。
代码示例 examples/examplevariable_arr.php
class ClassCaller { public function Processcaller($arg) { echo "Caller: setting the variable {$arg['id']}<br>"; } } class ClassService { public function ProcessService($arg) { echo "Service: setting the variable {$arg['id']}<br>"; } } $mini=new MiniLang([],[],new ClassService()); $mini->separate("when field1.id>0 then field2.value=3 and field3.processcaller and processcaller(field3) and processservice(field3)"); $variables=['field1'=>['id'=>1,'value'=>3] ,'field2'=>['id'=>2,'value'=>''] ,'field3'=>['id'=>3,'value'=>''] ]; $callback=new ClassCaller(); $mini->evalAllLogic($callback,$variables,false); var_dump($variables);
- field2.value 引用数组中的元素 "value"。
- field3.processcaller 引用 ClassCaller::processcaller() 方法
- processcaller(field3) 与 field3.processcaller 相同
- processservice(field3) 调用 ClassService::processservice() 方法
全局变量
全局变量采用 PHP 的值($GLOBAL),因此不需要在语言内部定义或设置。
注意:出于安全目的。PHP 定义的作为 "$_namevar" 的全局变量不能被读取或修改。因此,如果您想保护一个全局变量,则可以使用下划线作为前缀重命名它。
示例:如果您尝试读取变量 $_SERVER,则它将返回变量 $SERVER 的值,它可能已定义或未定义。
全局变量通过
$globalname
$globalname.associndex // $globalname['associindex'] $globalname.4 // $globalname[4] $globalname.param('a.b.c') // $globalname['a']['b']['c'] param($globalname,'a.b.c') // $globalname['a']['b']['c']
示例
$globalname=30
示例代码: examples/exampleglobal.php
$field1=1; // our global variable $mini=new MiniLang(); $mini->separate('when $field1>0 then $field1=3'); // we prepare the language $variables=[]; // local variables $callback=new stdClass(); $mini->evalAllLogic($callback,$variables); // we set the variables and run the languageand run the language var_dump($field1); // returns 3
字面量
示例
设置 var=20 和 var2="hello" 和 var3="hello {{var}}" 和 var4=fn()
保留方法
示例: examples/examplereserved.php
$mini=new MiniLang(); $mini->separate("when true=true then field1=timer()"); // we prepare the language $variables=['field1'=>1]; // we define regular variables $callback=new stdClass(); $mini->evalAllLogic($callback,$variables); // we set the variables and run the language var_dump($variables);
示例定时器: examples/examplereservedtimer.php
class ClassWithTimer { var $dateLastChange; public function dateInit() { return time(); } public function __construct() { $this->dateLastChange=time(); } } $mini=new MiniLang(); $mini->separate("when true=true then field1=interval() and field2=fullinterval()"); // we prepare the language $variables=['field1'=>0,'field2'=>0]; // we define regular variables $callback=new ClassWithTimer(); $mini->evalAllLogic($callback,$variables); // we set the variables and run the language var_dump($variables);
init
该表达式部分允许设置值。该表达式通常是可选的,并且可以省略。
它与 SET 类似,但在 WHERE 执行之前执行,无论 WHERE 是否有效。
init counter=20 where variable1=20 set variable+counter
- 设置计数器为 20。
- 并比较:variable1 等于 20
- 如果为是,则通过计数器增加变量
代码
$mini->separate("init tmp=50 when condition=1 then field1+10"); // set tmp to 50. If condition is 1 then it increases the field1 by 10.
where
这个表达式部分向语句添加了一个条件。
我们还可以使用“当”。
where 表达式
或
when 表达式
可以通过用“和”或“或”分隔来同时比较多个条件。
where v1=10 and v2=20 or v3<50
示例
where variable1=20 and $variable2=variable3 or function(20)=40
where $field=20 and field2<>40 or field3=40 // SQL 语法
where $field==20 && field2!=40 || field3+=40 // PHP 语法
where 1 // 总是正确
允许的逻辑表达式
- 等于: = 和 ==(符号 "=" 和 "==" 的作用类似)。
- 不等于: <> 和 !=
- 小于 <
- 小于或等于 <=
- 大于 >
- 大于或等于 >=
- 与逻辑: "and" 和 &&
- 或逻辑: "or" 和 ||
set
这个表达式部分允许设置变量的值。可以通过用","或"and"分隔来同时设置多个变量。
我们还可以使用“设置”或“然后”表达式
设置 表达式
或
然后 表达式
只有当 WHERE 有效时,这部分表达式才会执行
允许的设置表达式
我们可以使用以下表达式设置变量
- variable=20
- variable=anothervariable
- variable=20+30
- variable=20-30 // 将会工作
- variable=20+-30 // !!!! 它将不会正确工作,因为它会将-30视为负数 "-30",而不是“20减30”
- variable=40*50+30
- variable+30 // 它将变量增加30
- variable+=anothervariable // 它将变量增加anothervariable的值
- variable-30 // 它将变量减少-30 注意:+variable-30 不会工作,因为符号 - 是一个操作,所以 +-20 是一个双重操作符
- variable=flip() // 它翻转值 0->1 或 1->0
这个库不允许使用复杂指令,例如
- variable=20+30*(20+30) // 不允许。
示例
set variable1=20 and $variable2=variable3 and function(20)=40
代码
$mini->separate("when condition=1 then field1+10"); // if condition is 1 then it increases the field1 by 10.
else
这个表达式可选部分允许设置变量的值。可以通过用","或"and"分隔来同时设置多个变量。
只有当 "where" 返回 false 或手动调用 ELSE 时,此代码才会评估。
示例
else variable1=20 and $variable2=variable3 and function(20)=40
循环
可以使用空格 "loop" 创建循环。
要开始循环,你必须写
$this->separate('loop variableloop=variable_with_values'); // where variable_with_values is must contains an array of values // variableloop._key will contain the key of the loop // variableloop._value will contain the value of the loop
并且要结束循环,你必须使用
$this->separate('loop end');
你可以在 "set" 或 "else" 中使用操作符 "break" 来跳出循环。
$this->separate('when condition set break else break');
注意:只有在评估所有逻辑时才会评估循环。它不与 evalLogic() 和 evalLogic2() 一起工作。
注意:你无法在循环中添加条件,但是你可以通过分配一个空数组来跳过循环。
示例
$this->separate('loop $row=variable'); $this->separate('loop $row2=variable'); $this->separate('where op=2 then cc=2'); $this->separate('where op=3 then break'); // ends of the first loop $this->separate('loop end'); $this->separate('loop end') $obj->evalAllLogic();
将逻辑编译成PHP类
可以创建一个类,该类使用语言创建的逻辑。目的是提高代码性能。
创建类
要生成类,首先我们需要使用方法 separate2() 而不是 separate() 来编写逻辑。它将逻辑存储在类的实例数组中。你可以直接使用代码,或者将其保存到类中,如下所示
// create an instance of MiniLang $mini=new MiniLang(null); // the logic goes here $mini->separate2('when var1="hello" and comp.f=false() then var2="world" '); // if var1 is equals "hello" then var2 is set "world" // and the generation of the class $r=$mini->generateClass('ExampleBasicClass','ns\example','ExampleBasicClass.php');
它将保存一个名为 📄 ExampleBasicClass.php 的新文件(你可以查看示例 📁 example/genclass/1.examplebasic.php)
使用类
生成了类后,您可以使用这个新类代替 MiniLang。因为这个类已经编译,所以运行速度极快。但是,如果您需要更改逻辑,那么您需要再次编译它。(您可以检查示例 📁 example/genclass/2.examplebasic.php 和 📁 example/genclass/ExampleBasicClass.php)
$result=['var1'=>'hello']; $obj=new ExampleBasicClass(null,$result); $obj->evalAllLogic(true);
这个类将看起来像
<?php namespace ns\example; use eftec\minilang\MiniLang; class ExampleBasicClass extends MiniLang { public $numCode=2; // num of lines of code public $usingClass=true; // if true then we are using a class (this class) public function whereRun($lineCode=0) { // ... } // end function WhereRun public function setRun($lineCode=0) { // ... } // end function SetRun public function elseRun($lineCode=0) { // ... } // end function ElseRun public function initRun($lineCode=0) { // ... } // end function InitRun } // end class
其中每个方法评估表达式的一部分。
基准测试
我们调用一些操作 1000 次。
(reset+separate+evalAllLogic) x 1000
- 我们调用方法 reset()、separate() 和 evalAllLogic 1000 次。
- 速度:0.028973 秒。比较:46.6%(越小越好)
evalAllLogic x 1000
- 我们调用方法 reset() 和 separate() 1 次
- 然后我们调用方法 evalAllLogic(true) 1000 次。
- 速度:0.002387 秒。比较:3.84%(越小越好)
(reset+separate2+evalAllLogic2) x 1000
- 我们调用方法 reset()、separate2() 和 evalAllLogic2() 1000 次。
- 速度:0.06217 秒。比较:100%(越小越好)。这是最慢的方法。
(evalAllLogic2) x 1000
- 我们调用方法 reset() 和 separate2() 1 次
- 然后我们调用方法 evalAllLogic2() 1000 次。
- 速度:0.013418 秒。比较:21.58%(越小越好)
PHP类方法 x 1000
- 我们创建一个具有方法 $mini->generateClass2(); 的类 1 次
- 然后,我们调用这个类(作为简单的 PHP 代码)1000 次。
- 速度:0.000763 秒。比较:1.23%(越小越好)。这是最快的方法。
文档
待办事项
- 文档。
版本
- 2.29 2024-08-12
- 阻止了对系统全局变量的访问。因此,这个库将无法读取以 $_namevar 定义的变量
- 修复了调用名为 "eval" 的方法的错误。注意,使用此方法无法调用 PHP 函数 eval(),但是您可以定义自己的 eval() 函数
- 2.28 2024-03-02
- 将依赖项更新到 PHP 7.4。PHP 7.2 的扩展支持已于 3 年前结束。
- 在代码中增加了更多类型提示。
- 2.27 2022-09-11
- 添加了可选说明
- 2.26 2022-09-11
- 添加了方法 setDictEntry()
- 2.25 2022-09-03
- 大多数方法进行了类型提示/验证。
- 2.24 2022-08-26
- 清理代码。
- [修复] $this->caller 可能是 null
- 2.23 2022-02-06
- [修复] 更新以兼容 PHP 8.1
- [更改] 更新依赖项以支持 PHP 7.2.5 及更高版本。
- 2.22 2021-12-05
- [修复] 添加了一些验证和一些小的修正,当值不正确时。
- 现在,每个值都是克隆而不是对象的实例(运行时)
- 2.21 2021-10-03
- 现在 a=b-1 可以工作,但是 a=b+-2 将不再工作
- 添加了循环
- 修复了当 set $var.field=20 时的错误。现在它设置值正确
- 删除了 evalAllLogic2(),因为它从未使用过。请使用 evalAllLogic() 代替
- 2.20.2 2021-09-26
- 修复了负数时的类生成问题。
- 2.20.1 2021-09-26
- 修复:比较中的 "<>"
- 修复了与零的比较。现在,零在 PHP 类中转换为 "0" 而不是 0。为什么?因为 field==null 等于 field==0。然而,field=='0' 不等于
- 生成的类现在更加压缩。
- 特殊函数现在正确地存储在生成的类中
- 2.20 2021-09-26
- 当它使用具有预计算值的类时,它允许更多功能
- 2.19 2021-09-26
- 它允许将库保存为 PHP 本地类
- 2.18.1 2021-08-25
- 修复了语言调用自定义函数而没有服务类的问题。
- 2.18 2021-01-16
- 一些清理。操作符 "@" 影响 PHP 的性能,所以最好使用 isset(x)?x:null 而不是 @x
- 2.17.1 2020-10-13
- 修复了字段是数字变量时的错误。30=30
- 2.17 2020-10-13
- 添加了新字段 $throwError 和 $errorLog。现在库在发现错误时抛出错误(默认情况下),而不是触发错误
- 逻辑运算符 AND 已优化,如果第一个表达式为假,则不会评估第二个表达式。
- 添加了数组 _first、_last 和 _count。
- 2.16 2020-09-22
- 代码已重构和优化。
- 2.15 2020-01-12
- 修复了 'set variable=function(variable2)' 的评估问题,其中 function 是 PHP 函数。
- 2.14 2019-10-26
- 修复了当函数的第一个参数是对象但方法在调用者或服务类中定义时,callFunction() 方法的问题。
- 2.12 2019-10-21
- 新增方法 separate2() 和 evalAllLogic2(),它们与 PHP 的 eval 一起工作。
- 新增方法 generateClass2()。
- separate2() 和 evalAllLogic2() 通过解析标记并将其转换为原生 PHP 代码来工作。
- 然而,它的速度比 evalAllLogic(true) 从 x2 到 x5 慢。
- 这个新引擎可以通过在字段 $setTxt、$whereTxt、$elseTxt、$initTxt 中缓存评估来工作。
- 查看基准测试。
- 2.11 2019-10-11 方法 _param (class)。现在允许 $a.fn()。
- 2.10 2019-10-07 方法 create()。
- 2.9 2019-08-28
-
- 设置字段.value=20,其中字段是一个数组有效。然而,field.1=20 不工作(解析器将 .1 视为十进制)。
- 2.8 2019-08-26
-
- 如果字段(在 where 部分)是一个对象,那么可以调用方法为 field.method(arg)。
-
- 方法 getDictEntry()。
- 2.7 2019-08-04
-
- 添加了 serialize() 和 unserialize() 方法。
-
- setCaller() 参数不再是一个引用(对象默认是引用)。
- 2.6 2019-08-03 现在允许 "else"。例如:"where exp then exp else exp"。
- 2.5 2019-08-03 现在允许引用数组索引(数字或关联)。
- 2.4 2019-08-02 添加了更多文档。现在我们允许单元表达式。
- 2.3 2019-05-24 修复了一些错误(如果方法未定义)。
- 2.0 2019-05-20 第二个版本。它使用 PHP 解析文件。
- 1.15 2019-01-06 如果我们添加 (+) 两个值,并且它们是数字,则相加。否则,进行连接。
- 1.14 2018-12-26 首个开源版本。