当前位置:首页 > PHP教程 > php高级应用 > 列表

规则引擎RulerZ用法及实现原理(代码示例)

发布:smiling 来源: PHP粉丝网  添加日期:2020-01-16 15:46:27 浏览: 评论:0 

本篇文章给大家带来的内容是关于规则引擎RulerZ用法及实现原理(代码示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

废话不多说,rulerz的官方地址是:https://github.com/K-Phoen/ru...

注意,本例中只拿普通数组做例子进行分析

1. 简介

RulerZ是一个用php实现的composer依赖包,目的是实现一个数据过滤规则引擎。RulerZ不仅支持数组过滤,也支持一些市面上常见的ORM,如Eloquent、Doctrine等,也支持Solr搜索引擎。

这是一个缺少中文官方文档的开源包,当然由于star数比较少,可能作者也觉得没必要。

2.安装

在你的项目composer.json所在目录下运行:

composer require 'kphoen/rulerz'

3.使用 - 过滤

现有数组如下:

  1. $players = [ 
  2.  
  3.     ['pseudo' => 'Joe',   'fullname' => 'Joe la frite',             'gender' => 'M''points' => 2500], 
  4.  
  5.     ['pseudo' => 'Moe',   'fullname' => 'Moe, from the bar!',       'gender' => 'M''points' => 1230], 
  6.  
  7.     ['pseudo' => 'Alice''fullname' => 'Alice, from... you know.''gender' => 'F''points' => 9001], 
  8.  
  9. ]; 

初始化引擎:

  1. use RulerZ\Compiler\Compiler; 
  2.  
  3. use RulerZ\Target; 
  4.  
  5. use RulerZ\RulerZ; 
  6.  
  7. // compiler 
  8.  
  9. $compiler = Compiler::create(); 
  10.  
  11. // RulerZ engine 
  12.  
  13. $rulerz = new RulerZ( 
  14.  
  15.     $compiler, [ 
  16.  
  17.         new Target\Native\Native([ // 请注意,这里是添加目标编译器,处理数组类型的数据源时对应的是Native 
  18.  
  19.             'length' => 'strlen' 
  20.  
  21.         ]), 
  22.  
  23.     ] 
  24.  
  25. ); 

创建一条规则:

$rule = "gender = :gender and points > :min_points'

将参数和规则交给引擎分析。

  1. $parameters = [ 
  2.  
  3.     'min_points' => 30, 
  4.  
  5.     'gender'     => 'F'
  6.  
  7. ]; 
  8.  
  9. $result = iterator_to_array( 
  10.  
  11.         $rulerz->filter($players$rule$parameters// the parameters can be omitted if empty 
  12.  
  13.     ); 
  14.  
  15. // result 是一个过滤后的数组 
  16.  
  17. array:1 [▼ 
  18.  
  19.   0 => array:4 [▼ 
  20.  
  21.     "pseudo" => "Alice" 
  22.  
  23.     "fullname" => "Alice, from... you know." 
  24. //phpfensi.com 
  25.     "gender" => "F" 
  26.  
  27.     "points" => 9001 
  28.  
  29.   ] 
  30.  

4.使用 - 判断是否满足规则

$rulerz->satisfies($player, $rule, $parameters);

// 返回布尔值,true表示满足

5.底层代码解读

下面,让我们看看从创建编译器开始,到最后出结果的过程中发生了什么。

1.Compiler::create();

这一步是实例化一个FileEvaluator类,这个类默认会将本地的系统临时目录当做下一步临时类文件读写所在目录,文件类里包含一个has()方法和一个write()方法。文件类如下:

  1. declare(strict_types=1); 
  2.  
  3. namespace RulerZ\Compiler; 
  4.  
  5. class NativeFilesystem implements Filesystem 
  6.  
  7.  
  8.     public function has(string $filePath): bool 
  9.  
  10.     { 
  11.  
  12.         return file_exists($filePath); 
  13.  
  14.     } 
  15. //phpfensi.com 
  16.     public function write(string $filePath, string $content): void 
  17.  
  18.     { 
  19.  
  20.         file_put_contents($filePath$content, LOCK_EX); 
  21.  
  22.     } 
  23.  

2.初始化RulerZ引擎,new RulerZ()

先看一下RulerZ的构建方法:

  1. public function __construct(Compiler $compilerarray $compilationTargets = []) 
  2.  
  3.  
  4.     $this->compiler = $compiler
  5.  
  6.  
  7.  
  8.     foreach ($compilationTargets as $targetCompiler) { 
  9.  
  10.         $this->registerCompilationTarget($targetCompiler); 
  11.  
  12.     } 
  13.  

这里的第一个参数,就是刚刚的编译器类,第二个是目标编译器类(实际处理数据源的),因为我们选择的是数组,所以这里的目标编译器是Native,引擎会将这个目标编译类放到自己的属性$compilationTargets。

  1. public function registerCompilationTarget(CompilationTarget $compilationTarget): void 
  2.  
  3.  
  4.     $this->compilationTargets[] = $compilationTarget
  5.  

3.运用filter或satisfies方法

这一点便是核心了。

以filter为例:

  1. public function filter($target, string $rulearray $parameters = [], array $executionContext = []) 
  2.  
  3.  
  4.     $targetCompiler = $this->findTargetCompiler($target, CompilationTarget::MODE_FILTER); 
  5.  
  6.     $compilationContext = $targetCompiler->createCompilationContext($target); 
  7.  
  8.     $executor = $this->compiler->compile($rule$targetCompiler$compilationContext); 
  9.  
  10.  
  11.  
  12.     return $executor->filter($target$parameters$targetCompiler->getOperators()->getOperators(), new ExecutionContext($executionContext)); 
  13.  

第一步会检查目标编译器是否支持筛选模式。

第二步创建编译上下文,这个一般统一是Context类实例

  1. public function createCompilationContext($target): Context 
  2.  
  3.  
  4.     return new Context(); 
  5.  

第三步,执行compiler的compile()方法

  1.         public function compile(string $rule, CompilationTarget $target, Context $context): Executor 
  2.  
  3.     { 
  4.  
  5.         $context['rule_identifier'] = $this->getRuleIdentifier($target$context$rule); 
  6.  
  7.         $context['executor_classname'] = 'Executor_'.$context['rule_identifier']; 
  8.  
  9.         $context['executor_fqcn'] = '\RulerZ\Compiled\Executor\\Executor_'.$context['rule_identifier']; 
  10.  
  11.  
  12.  
  13.         if (!class_exists($context['executor_fqcn'], false)) { 
  14.  
  15.             $compiler = function () use ($rule$target$context) { 
  16.  
  17.                 return $this->compileToSource($rule$target$context); 
  18.  
  19.             }; 
  20.  
  21.  
  22.  
  23.             $this->evaluator->evaluate($context['rule_identifier'], $compiler); 
  24.  
  25.         } 
  26.  
  27.  
  28.  
  29.         return new $context['executor_fqcn'](); 
  30.  
  31.     } 
  32.  
  33.         protected function getRuleIdentifier(CompilationTarget $compilationTarget, Context $context, string $rule): string 
  34.  
  35.     { 
  36.  
  37.         return hash('crc32b', get_class($compilationTarget).$rule.$compilationTarget->getRuleIdentifierHint($rule$context)); 
  38.  
  39.     } 
  40.  
  41.       
  42.  
  43.         protected function compileToSource(string $rule, CompilationTarget $compilationTarget, Context $context): string 
  44.  
  45.     { 
  46.  
  47.         $ast = $this->parser->parse($rule); 
  48.  
  49.         $executorModel = $compilationTarget->compile($ast$context); 
  50.  
  51.  
  52.  
  53.         $flattenedTraits = implode(PHP_EOL, array_map(function ($trait) { 
  54.  
  55.             return "\t".'use \\'.ltrim($trait, '\\').';'; 
  56.  
  57.         }, $executorModel->getTraits())); 
  58.  
  59.  
  60.  
  61.         $extraCode = ''
  62.  
  63.         foreach ($executorModel->getCompiledData() as $key => $value) { 
  64.  
  65.             $extraCode .= sprintf('private $%s = %s;'.PHP_EOL, $key, var_export($value, true)); 
  66.  
  67.         } 
  68.  
  69.  
  70.  
  71.         $commentedRule = str_replace(PHP_EOL, PHP_EOL.'    // '$rule); 
  72.  
  73.  
  74.  
  75.         return <<<EXECUTOR 
  76.  
  77. namespace RulerZ\Compiled\Executor; 
  78.  
  79.  
  80.  
  81. use RulerZ\Executor\Executor; 
  82.  
  83.  
  84.  
  85. class {$context['executor_classname']} implements Executor 
  86.  
  87.  
  88.     $flattenedTraits 
  89.  
  90.  
  91.  
  92.     $extraCode 
  93.  
  94.  
  95.  
  96.     // $commentedRule 
  97.  
  98.     protected function execute(\$targetarray \$operatorsarray \$parameters
  99.  
  100.     { 
  101.  
  102.         return {$executorModel->getCompiledRule()}; 
  103.  
  104.     } 
  105.  
  106.  
  107. EXECUTOR; 
  108.  
  109.     } 

这段代码会依照crc13算法生成一个哈希串和Executor拼接作为执行器临时类的名称,并将执行器相关代码写进上文提到的临时目录中去。生成的代码如下:

  1. // /private/var/folders/w_/sh4r42wn4_b650l3pc__fh7h0000gp/T/rulerz_executor_ff2800e8 
  2.  
  3. <?php 
  4.  
  5. namespace RulerZ\Compiled\Executor;  
  6.  
  7. use RulerZ\Executor\Executor;  
  8.  
  9. class Executor_ff2800e8 implements Executor 
  10.  
  11.  
  12.         use \RulerZ\Executor\ArrayTarget\FilterTrait; 
  13.  
  14.     use \RulerZ\Executor\ArrayTarget\SatisfiesTrait; 
  15.  
  16.     use \RulerZ\Executor\ArrayTarget\ArgumentUnwrappingTrait;     
  17.  
  18.  
  19.     // gender = :gender and points > :min_points and points > :min_points 
  20.  
  21.     protected function execute($targetarray $operatorsarray $parameters
  22.  
  23.     { 
  24.  
  25.         return ($this->unwrapArgument($target["gender"]) == $parameters["gender"] && ($this->unwrapArgument($target["points"]) > $parameters["min_points"] && $this->unwrapArgument($target["points"]) > $parameters["min_points"])); 
  26.  
  27.     } 
  28.  

这个临时类文件就是最后要执行过滤动作的类。

FilterTrait中的filter方法是首先被执行的,里面会根据execute返回的布尔值来判断,是否通过迭代器返回符合条件的行。

execute方法就是根据具体的参数和操作符挨个判断每行中对应的cell是否符合判断来返回true/false。

  1. public function filter($targetarray $parametersarray $operators, ExecutionContext $context
  2.  
  3.  
  4.     return IteratorTools::fromGenerator(function () use ($target$parameters$operators) { 
  5.  
  6.         foreach ($target as $row) { 
  7.  
  8.             $targetRow = is_array($row) ? $row : new ObjectContext($row); 
  9.  
  10.  
  11.  
  12.             if ($this->execute($targetRow$operators$parameters)) { 
  13.  
  14.                 yield $row
  15.  
  16.             } 
  17.  
  18.         } 
  19.  
  20.     }); 
  21.  

satisfies和filter基本逻辑类似,只是最后satisfies是执行单条判断。

有一个问题,我们的编译器是如何知道我们设立的操作规则$rule的具体含义的,如何parse的?

这就涉及另一个问题了,抽象语法树(AST)。

Go further - 抽象语法树

我们都知道php zend引擎在解读代码的过程中有一个过程是语法和词法分析,这个过程叫做parser,中间会将代码转化为抽象语法树,这是引擎能够读懂代码的关键步骤。

同样,我们在写一条规则字符串的时候,代码如何能够明白我们写的是什么呢?那就是抽象语法树。

以上面的规则为例:

gender = :gender and points > :min_points

这里, =、and、>都是操作符,但是机器并不知道他们是操作符,也不知道其他字段是什么含义。

于是rulerz使用自己的语法模板。

首先是默认定义了几个操作符。

  1. <?php 
  2.  
  3.  
  4.  
  5. declare(strict_types=1); 
  6.  
  7.  
  8.  
  9. namespace RulerZ\Target\Native; 
  10.  
  11.  
  12.  
  13. use RulerZ\Target\Operators\Definitions; 
  14.  
  15.  
  16.  
  17. class NativeOperators 
  18.  
  19.  
  20.     public static function create(Definitions $customOperators): Definitions 
  21.  
  22.     { 
  23.  
  24.         $defaultInlineOperators = [ 
  25.  
  26.             'and' => function ($a$b) { 
  27.  
  28.                 return sprintf('(%s && %s)'$a$b); 
  29.  
  30.             }, 
  31.  
  32.             'or' => function ($a$b) { 
  33.  
  34.                 return sprintf('(%s || %s)'$a$b); 
  35.  
  36.             }, 
  37.  
  38.             'not' => function ($a) { 
  39.  
  40.                 return sprintf('!(%s)'$a); 
  41.  
  42.             }, 
  43.  
  44.             '=' => function ($a$b) { 
  45.  
  46.                 return sprintf('%s == %s'$a$b); 
  47.  
  48.             }, 
  49.  
  50.             'is' => function ($a$b) { 
  51.  
  52.                 return sprintf('%s === %s'$a$b); 
  53.  
  54.             }, 
  55.  
  56.             '!=' => function ($a$b) { 
  57.  
  58.                 return sprintf('%s != %s'$a$b); 
  59.  
  60.             }, 
  61.  
  62.             '>' => function ($a$b) { 
  63.  
  64.                 return sprintf('%s > %s'$a$b); 
  65.  
  66.             }, 
  67.  
  68.             '>=' => function ($a$b) { 
  69.  
  70.                 return sprintf('%s >= %s'$a$b); 
  71.  
  72.             }, 
  73.  
  74.             '<' => function ($a$b) { 
  75.  
  76.                 return sprintf('%s < %s'$a$b); 
  77.  
  78.             }, 
  79.  
  80.             '<=' => function ($a$b) { 
  81.  
  82.                 return sprintf('%s <= %s'$a$b); 
  83.  
  84.             }, 
  85.  
  86.             'in' => function ($a$b) { 
  87.  
  88.                 return sprintf('in_array(%s, %s)'$a$b); 
  89.  
  90.             }, 
  91.  
  92.         ]; 
  93.  
  94.  
  95.  
  96.         $defaultOperators = [ 
  97.  
  98.             'sum' => function () { 
  99.  
  100.                 return array_sum(func_get_args()); 
  101.  
  102.             }, 
  103.  
  104.         ]; 
  105.  
  106.  
  107.  
  108.         $definitions = new Definitions($defaultOperators$defaultInlineOperators); 
  109.  
  110.  
  111.  
  112.         return $definitions->mergeWith($customOperators); 
  113.  
  114.     } 
  115.  

在RulerZParserParser中,有如下方法:

  1. public function parse($rule
  2.  
  3.  
  4.     if ($this->parser === null) { 
  5.  
  6.         $this->parser = Compiler\Llk::load( 
  7.  
  8.             new File\Read(__DIR__.'/../Grammar.pp'
  9.  
  10.         ); 
  11.  
  12.     } 
  13.  
  14.  
  15.  
  16.     $this->nextParameterIndex = 0; 
  17.  
  18.  
  19.  
  20.     return $this->visit($this->parser->parse($rule)); 
  21.  

这里要解读一个核心语法文件:

  1. // 
  2.  
  3. // Hoa 
  4.  
  5. // 
  6.  
  7. // 
  8.  
  9. // @license 
  10.  
  11. // 
  12.  
  13. // New BSD License 
  14.  
  15. // 
  16.  
  17. // Copyright © 2007-2015, Ivan Enderlin. All rights reserved. 
  18.  
  19. // 
  20.  
  21. // Redistribution and use in source and binary forms, with or without 
  22.  
  23. // modification, are permitted provided that the following conditions are met: 
  24.  
  25. //     * Redistributions of source code must retain the above copyright 
  26.  
  27. //       notice, this list of conditions and the following disclaimer. 
  28.  
  29. //     * Redistributions in binary form must reproduce the above copyright 
  30.  
  31. //       notice, this list of conditions and the following disclaimer in the 
  32.  
  33. //       documentation and/or other materials provided with the distribution. 
  34.  
  35. //     * Neither the name of the Hoa nor the names of its contributors may be 
  36.  
  37. //       used to endorse or promote products derived from this software without 
  38.  
  39. //       specific prior written permission. 
  40.  
  41. // 
  42.  
  43. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
  44.  
  45. // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
  46.  
  47. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
  48.  
  49. // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE 
  50.  
  51. // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
  52.  
  53. // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
  54.  
  55. // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
  56.  
  57. // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
  58.  
  59. // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
  60.  
  61. // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
  62.  
  63. // POSSIBILITY OF SUCH DAMAGE. 
  64.  
  65. // 
  66.  
  67. // Inspired from \Hoa\Ruler\Grammar. 
  68.  
  69. // 
  70.  
  71. // @author     Stéphane Py <stephane.py@hoa-project.net> 
  72.  
  73. // @author     Ivan Enderlin <ivan.enderlin@hoa-project.net> 
  74.  
  75. // @author     Kévin Gomez <contact@kevingomez.fr> 
  76.  
  77. // @copyright  Copyright © 2007-2015 Stéphane Py, Ivan Enderlin, Kévin Gomez. 
  78.  
  79. // @license    New BSD License 
  80.  
  81.  
  82.  
  83.  
  84.  
  85. %skip   space         \s 
  86.  
  87.  
  88.  
  89. // Scalars. 
  90.  
  91. %token  true          (?i)true 
  92.  
  93. %token  false         (?i)false 
  94.  
  95. %token  null          (?i)null 
  96.  
  97.  
  98.  
  99. // Logical operators 
  100.  
  101. %token  not           (?i)not\b 
  102.  
  103. %token  and           (?i)and\b 
  104.  
  105. %token  or            (?i)or\b 
  106.  
  107. %token  xor           (?i)xor\b 
  108.  
  109.  
  110.  
  111. // Value 
  112.  
  113. %token  string        ("|')(.*?)(?<!\\)\1 
  114.  
  115. %token  float         -?\d+\.\d+ 
  116.  
  117. %token  integer       -?\d+ 
  118.  
  119. %token  parenthesis_  \( 
  120.  
  121. %token _parenthesis   \) 
  122.  
  123. %token  bracket_      \[ 
  124.  
  125. %token _bracket       \] 
  126.  
  127. %token  comma          , 
  128.  
  129. %token  dot           \. 
  130.  
  131.  
  132.  
  133. %token  positional_parameter \? 
  134.  
  135. %token  named_parameter      :[a-z-A-Z0-9_]+ 
  136.  
  137.  
  138.  
  139. %token  identifier    [^\s\(\)\[\],\.]+ 
  140.  
  141.  
  142.  
  143. #expression: 
  144.  
  145.     logical_operation() 
  146.  
  147.  
  148.  
  149. logical_operation: 
  150.  
  151.     operation() 
  152.  
  153.     ( ( ::and:: #and | ::or:: #or | ::xor:: #xor ) logical_operation() )? 
  154.  
  155.  
  156.  
  157. operation: 
  158.  
  159.     operand() ( <identifier> logical_operation() #operation )? 
  160.  
  161.  
  162.  
  163. operand: 
  164.  
  165.     ::parenthesis_:: logical_operation() ::_parenthesis:: 
  166.  
  167.   | value() 
  168.  
  169.  
  170.  
  171. parameter: 
  172.  
  173.     <positional_parameter> 
  174.  
  175.   | <named_parameter> 
  176.  
  177.  
  178.  
  179. value: 
  180.  
  181.     ::not:: logical_operation() #not 
  182.  
  183.   | <true> | <false> | <null> | <float> | <integer> | <string> 
  184.  
  185.   | parameter() 
  186.  
  187.   | variable() 
  188.  
  189.   | array_declaration() 
  190.  
  191.   | function_call() 
  192.  
  193.  
  194.  
  195. variable: 
  196.  
  197.     <identifier> ( object_access() #variable_access )* 
  198.  
  199.  
  200.  
  201. object_access: 
  202.  
  203.     ::dot:: <identifier> #attribute_access 
  204.  
  205.  
  206.  
  207. #array_declaration: 
  208.  
  209.     ::bracket_:: value() ( ::comma:: value() )* ::_bracket:: 
  210.  
  211.  
  212.  
  213. #function_call: 
  214.  
  215.     <identifier> ::parenthesis_:: 
  216.  
  217.     ( logical_operation() ( ::comma:: logical_operation() )* )? 
  218.  
  219.     ::_parenthesis:: 

上面Llk::load方法会加载这个基础语法内容并解析出片段tokens,tokens解析的逻辑就是正则匹配出我们需要的一些操作符和基础标识符,并将对应的正则表达式提取出来:

  1. array:1 [▼ 
  2.  
  3.   "default" => array:20 [▼ 
  4.  
  5.     "skip" => "\s" 
  6.  
  7.     "true" => "(?i)true" 
  8.  
  9.     "false" => "(?i)false" 
  10.  
  11.     "null" => "(?i)null" 
  12.  
  13.     "not" => "(?i)not\b" 
  14.  
  15.     "and" => "(?i)and\b" 
  16.  
  17.     "or" => "(?i)or\b" 
  18.  
  19.     "xor" => "(?i)xor\b" 
  20.  
  21.     "string" => "("|')(.*?)(?<!\\)\1" 
  22.  
  23.     "float" => "-?\d+\.\d+" 
  24.  
  25.     "integer" => "-?\d+" 
  26.  
  27.     "parenthesis_" => "\(" 
  28.  
  29.     "_parenthesis" => "\)" 
  30.  
  31.     "bracket_" => "\[" 
  32.  
  33.     "_bracket" => "\]" 
  34.  
  35.     "comma" => "," 
  36.  
  37.     "dot" => "\." 
  38.  
  39.     "positional_parameter" => "\?" 
  40.  
  41.     "named_parameter" => ":[a-z-A-Z0-9_]+" 
  42.  
  43.     "identifier" => "[^\s\(\)\[\],\.]+" 
  44.  
  45.   ] 
  46.  

这一步也会生成一个rawRules

  1. array:10 [▼ 
  2.  
  3.   "#expression" => " logical_operation()" 
  4.  
  5.   "logical_operation" => " operation() ( ( ::and:: #and | ::or:: #or | ::xor:: #xor ) logical_operation() )?" 
  6.  
  7.   "operation" => " operand() ( <identifier> logical_operation() #operation )?" 
  8.  
  9.   "operand" => " ::parenthesis_:: logical_operation() ::_parenthesis:: | value()" 
  10.  
  11.   "parameter" => " <positional_parameter> | <named_parameter>" 
  12.  
  13.   "value" => " ::not:: logical_operation() #not | <true> | <false> | <null> | <float> | <integer> | <string> | parameter() | variable() | array_declaration() | function_call( ▶" 
  14.  
  15.   "variable" => " <identifier> ( object_access() #variable_access )*" 
  16.  
  17.   "object_access" => " ::dot:: <identifier> #attribute_access" 
  18.  
  19.   "#array_declaration" => " ::bracket_:: value() ( ::comma:: value() )* ::_bracket::" 
  20.  
  21.   "#function_call" => " <identifier> ::parenthesis_:: ( logical_operation() ( ::comma:: logical_operation() )* )? ::_parenthesis::" 
  22.  

这个rawRules会通过analyzer类的analyzeRules方法解析替换里面的::表示的空位,根据$_ppLexemes属性的值,Compiler\Llk\Lexer()词法解析器会将rawRules数组每一个元素解析放入双向链表栈(SplStack)中,然后再通过对该栈插入和删除操作,形成一个包含所有操作符和token实例的数组$rules。

  1. array:54 [▼ 
  2.  
  3.   0 => Concatenation {#64 ▶} 
  4.  
  5.   "expression" => Concatenation {#65 ▼ 
  6.  
  7.     #_name: "expression" 
  8.  
  9.     #_children: array:1 [▼ 
  10.  
  11.       0 => 0 
  12.  
  13.     ] 
  14.  
  15.     #_nodeId: "#expression" 
  16.  
  17.     #_nodeOptions: [] 
  18.  
  19.     #_defaultId: "#expression" 
  20.  
  21.     #_defaultOptions: [] 
  22.  
  23.     #_pp: " logical_operation()" 
  24.  
  25.     #_transitional: false 
  26.  
  27.   } 
  28.  
  29.   2 => Token {#62 ▶} 
  30.  
  31.   3 => Concatenation {#63 ▼ 
  32.  
  33.     #_name: 3 
  34.  
  35.     #_children: array:1 [▼ 
  36.  
  37.       0 => 2 
  38.  
  39.     ] 
  40.  
  41.     #_nodeId: "#and" 
  42.  
  43.     #_nodeOptions: [] 
  44.  
  45.     #_defaultId: null 
  46.  
  47.     #_defaultOptions: [] 
  48.  
  49.     #_pp: null 
  50.  
  51.     #_transitional: true 
  52.  
  53.   } 
  54.  
  55.   4 => Token {#68 ▶} 
  56.  
  57.   5 => Concatenation {#69 ▶} 
  58.  
  59.   6 => Token {#70 ▶} 
  60.  
  61.   7 => Concatenation {#71 ▶} 
  62.  
  63.   8 => Choice {#72 ▶} 
  64.  
  65.   9 => Concatenation {#73 ▶} 
  66.  
  67.   10 => Repetition {#74 ▶} 
  68.  
  69.   "logical_operation" => Concatenation {#75 ▶} 
  70.  
  71.   12 => Token {#66 ▶} 
  72.  
  73.   13 => Concatenation {#67 ▶} 
  74.  
  75.   14 => Repetition {#78 ▶} 
  76.  
  77.   "operation" => Concatenation {#79 ▶} 
  78.  
  79.   16 => Token {#76 ▶} 
  80.  
  81.   17 => Token {#77 ▶} 
  82.  
  83.   18 => Concatenation {#82 ▶} 
  84.  
  85.   "operand" => Choice {#83 ▶} 
  86.  
  87.   20 => Token {#80 ▶} 
  88.  
  89.   21 => Token {#81 ▼ 
  90.  
  91.     #_tokenName: "named_parameter" 
  92.  
  93.     #_namespace: null 
  94.  
  95.     #_regex: null 
  96.  
  97.     #_ast: null 
  98.  
  99.     #_value: null 
  100.  
  101.     #_kept: true 
  102.  
  103.     #_unification: -1 
  104.  
  105.     #_name: 21 
  106.  
  107.     #_children: null 
  108.  
  109.     #_nodeId: null 
  110.  
  111.     #_nodeOptions: [] 
  112.  
  113.     #_defaultId: null 
  114.  
  115.     #_defaultOptions: [] 
  116.  
  117.     #_pp: null 
  118.  
  119.     #_transitional: true 
  120.  
  121.   } 
  122.  
  123.   "parameter" => Choice {#86 ▶} 
  124.  
  125.   23 => Token {#84 ▶} 
  126.  
  127.   24 => Concatenation {#85 ▶} 
  128.  
  129.   25 => Token {#89 ▶} 
  130.  
  131.   26 => Token {#90 ▶} 
  132.  
  133.   27 => Token {#91 ▶} 
  134.  
  135.   28 => Token {#92 ▶} 
  136.  
  137.   29 => Token {#93 ▶} 
  138.  
  139.   30 => Token {#94 ▶} 
  140.  
  141.   "value" => Choice {#95 ▶} 
  142.  
  143.   32 => Token {#87 ▶} 
  144.  
  145.   33 => Concatenation {#88 ▶} 
  146.  
  147.   34 => Repetition {#98 ▶} 
  148.  
  149.   "variable" => Concatenation {#99 ▶} 
  150.  
  151.   36 => Token {#96 ▶} 
  152.  
  153.   37 => Token {#97 ▶} 
  154.  
  155.   "object_access" => Concatenation {#102 ▶} 
  156.  
  157.   39 => Token {#100 ▶} 
  158.  
  159.   40 => Token {#101 ▶} 
  160.  
  161.   41 => Concatenation {#105 ▶} 
  162.  
  163.   42 => Repetition {#106 ▶} 
  164.  
  165.   43 => Token {#107 ▶} 
  166.  
  167.   "array_declaration" => Concatenation {#108 ▶} 
  168.  
  169.   45 => Token {#103 ▶} 
  170.  
  171.   46 => Token {#104 ▶} 
  172.  
  173.   47 => Token {#111 ▶} 
  174.  
  175.   48 => Concatenation {#112 ▶} 
  176.  
  177.   49 => Repetition {#113 ▶} 
  178.  
  179.   50 => Concatenation {#114 ▶} 
  180.  
  181.   51 => Repetition {#115 ▶} 
  182.  
  183.   52 => Token {#116 ▶} 
  184.  
  185.   "function_call" => Concatenation {#117 ▶} 
  186.  

然后返回HoaCompilerLlkParser实例,这个实例有一个parse方法,正是此方法构成了一个语法树。

  1. public function parse($text$rule = null, $tree = true) 
  2.  
  3.     { 
  4.  
  5.         $k = 1024; 
  6.  
  7.  
  8.  
  9.         if (isset($this->_pragmas['parser.lookahead'])) { 
  10.  
  11.             $k = max(0, intval($this->_pragmas['parser.lookahead'])); 
  12.  
  13.         } 
  14.  
  15.  
  16.  
  17.         $lexer                = new Lexer($this->_pragmas); 
  18.  
  19.         $this->_tokenSequence = new Iterator\Buffer( 
  20.  
  21.             $lexer->lexMe($text$this->_tokens), 
  22.  
  23.             $k 
  24.  
  25.         ); 
  26.  
  27.         $this->_tokenSequence->rewind(); 
  28.  
  29.  
  30.  
  31.         $this->_errorToken = null; 
  32.  
  33.         $this->_trace      = []; 
  34.  
  35.         $this->_todo       = []; 
  36.  
  37.  
  38.  
  39.         if (false === array_key_exists($rule$this->_rules)) { 
  40.  
  41.             $rule = $this->getRootRule(); 
  42.  
  43.         } 
  44.  
  45.  
  46.  
  47.         $closeRule   = new Rule\Ekzit($rule, 0); 
  48.  
  49.         $openRule    = new Rule\Entry($rule, 0, [$closeRule]); 
  50.  
  51.         $this->_todo = [$closeRule$openRule]; 
  52.  
  53.  
  54.  
  55.         do { 
  56.  
  57.             $out = $this->unfold(); 
  58.  
  59.  
  60.  
  61.             if (null  !== $out && 
  62.  
  63.                 'EOF' === $this->_tokenSequence->current()['token']) { 
  64.  
  65.                 break
  66.  
  67.             } 
  68.  
  69.  
  70.  
  71.             if (false === $this->backtrack()) { 
  72.  
  73.                 $token  = $this->_errorToken; 
  74.  
  75.  
  76.  
  77.                 if (null === $this->_errorToken) { 
  78.  
  79.                     $token = $this->_tokenSequence->current(); 
  80.  
  81.                 } 
  82.  
  83.  
  84.  
  85.                 $offset = $token['offset']; 
  86.  
  87.                 $line   = 1; 
  88.  
  89.                 $column = 1; 
  90.  
  91.  
  92.  
  93.                 if (!emptyempty($text)) { 
  94.  
  95.                     if (0 === $offset) { 
  96.  
  97.                         $leftnl = 0; 
  98.  
  99.                     } else { 
  100.  
  101.                         $leftnl = strrpos($text"\n", -(strlen($text) - $offset) - 1) ?: 0; 
  102.  
  103.                     } 
  104.  
  105.  
  106.  
  107.                     $rightnl = strpos($text"\n"$offset); 
  108.  
  109.                     $line    = substr_count($text"\n", 0, $leftnl + 1) + 1; 
  110.  
  111.                     $column  = $offset - $leftnl + (0 === $leftnl); 
  112.  
  113.  
  114.  
  115.                     if (false !== $rightnl) { 
  116.  
  117.                         $text = trim(substr($text$leftnl$rightnl - $leftnl), "\n"); 
  118.  
  119.                     } 
  120.  
  121.                 } 
  122.  
  123.  
  124.  
  125.                 throw new Compiler\Exception\UnexpectedToken( 
  126.  
  127.                     'Unexpected token "%s" (%s) at line %d and column %d:' . 
  128.  
  129.                     "\n" . '%s' . "\n" . str_repeat(' '$column - 1) . '↑'
  130.  
  131.                     0, 
  132.  
  133.                     [ 
  134.  
  135.                         $token['value'], 
  136.  
  137.                         $token['token'], 
  138.  
  139.                         $line
  140.  
  141.                         $column
  142.  
  143.                         $text 
  144.  
  145.                     ], 
  146.  
  147.                     $line
  148.  
  149.                     $column 
  150.  
  151.                 ); 
  152.  
  153.             } 
  154.  
  155.         } while (true); 
  156.  
  157.  
  158.  
  159.         if (false === $tree) { 
  160.  
  161.             return true; 
  162.  
  163.         } 
  164.  
  165.  
  166.  
  167.         $tree = $this->_buildTree(); 
  168.  
  169.  
  170.  
  171.         if (!($tree instanceof TreeNode)) { 
  172.  
  173.             throw new Compiler\Exception( 
  174.  
  175.                 'Parsing error: cannot build AST, the trace is corrupted.'
  176.  
  177.                 1 
  178.  
  179.             ); 
  180.  
  181.         } 
  182.  
  183. //phpfensi.com 
  184.  
  185.         return $this->_tree = $tree
  186.  
  187.     } 

我们得到的一个完整的语法树是这样的:

  1. Rule {#120 ▼ 
  2.  
  3.   #_root: Operator {#414 ▼ 
  4.  
  5.     #_name: "and" 
  6.  
  7.     #_arguments: array:2 [▼ 
  8.  
  9.       0 => Operator {#398 ▼ 
  10.  
  11.         #_name: "=" 
  12.  
  13.         #_arguments: array:2 [▼ 
  14.  
  15.           0 => Context {#396 ▼ 
  16.  
  17.             #_id: "gender" 
  18.  
  19.             #_dimensions: [] 
  20.  
  21.           } 
  22.  
  23.           1 => Parameter {#397 ▼ 
  24.  
  25.             -name: "gender" 
  26.  
  27.           } 
  28.  
  29.         ] 
  30.  
  31.         #_function: false 
  32.  
  33.         #_laziness: false 
  34.  
  35.         #_id: null 
  36.  
  37.         #_dimensions: [] 
  38.  
  39.       } 
  40.  
  41.       1 => Operator {#413 ▼ 
  42.  
  43.         #_name: "and" 
  44.  
  45.         #_arguments: array:2 [▼ 
  46.  
  47.           0 => Operator {#401 ▼ 
  48.  
  49.             #_name: ">" 
  50.  
  51.             #_arguments: array:2 [▼ 
  52.  
  53.               0 => Context {#399 ▶} 
  54.  
  55.               1 => Parameter {#400 ▶} 
  56.  
  57.             ] 
  58.  
  59.             #_function: false 
  60.  
  61.             #_laziness: false 
  62.  
  63.             #_id: null 
  64.  
  65.             #_dimensions: [] 
  66.  
  67.           } 
  68.  
  69.           1 => Operator {#412 ▶} 
  70.  
  71.         ] 
  72.  
  73.         #_function: false 
  74.  
  75.         #_laziness: true 
  76.  
  77.         #_id: null 
  78.  
  79.         #_dimensions: [] 
  80.  
  81.       } 
  82.  
  83.     ] 
  84.  
  85.     #_function: false 
  86.  
  87.     #_laziness: true 
  88.  
  89.     #_id: null 
  90.  
  91.     #_dimensions: [] 
  92.  
  93.   } 
  94.  

这里有根节点、子节点、操作符参数以及HoaRulerModelOperator实例。

这时$executorModel = $compilationTarget->compile($ast, $context);就可以通过NativeVisitor的visit方法对这个语法树进行访问和分析了。

这一步走的是visitOperator()

  1. /** 
  2.  
  3.  * {@inheritdoc} 
  4.  
  5.  */ 
  6.  
  7. public function visitOperator(AST\Operator $element, &$handle = null, $eldnah = null) 
  8.  
  9.  
  10.     $operatorName = $element->getName(); 
  11.  
  12.  
  13.  
  14.     // the operator does not exist at all, throw an error before doing anything else. 
  15.  
  16.     if (!$this->operators->hasInlineOperator($operatorName) && !$this->operators->hasOperator($operatorName)) { 
  17.  
  18.         throw new OperatorNotFoundException($operatorName, sprintf('Operator "%s" does not exist.'$operatorName)); 
  19.  
  20.     } 
  21.  
  22.  
  23.  
  24.     // expand the arguments 
  25.  
  26.     $arguments = array_map(function ($argumentuse (&$handle$eldnah) { 
  27.  
  28.         return $argument->accept($this$handle$eldnah); 
  29.  
  30.     }, $element->getArguments()); 
  31.  
  32.  
  33.  
  34.     // and either inline the operator call 
  35.  
  36.     if ($this->operators->hasInlineOperator($operatorName)) { 
  37.  
  38.         $callable = $this->operators->getInlineOperator($operatorName); 
  39.  
  40.  
  41.  
  42.         return call_user_func_array($callable$arguments); 
  43.  
  44.     } 
  45.  
  46.  
  47.  
  48.     $inlinedArguments = emptyempty($arguments) ? '' : ', '.implode(', '$arguments); 
  49.  
  50.  
  51.  
  52.     // or defer it. 
  53.  
  54.     return sprintf('call_user_func($operators["%s"]%s)'$operatorName$inlinedArguments); 
  55.  

返回的逻辑代码可以通过得到:

$executorModel->getCompiledRule()

Tags: 规则引擎 RulerZ

分享到: