Автор оригинала: David Wong.
Не так уж много глупостей. Официальным адресом правителей является https://github.com/K-Phoen/ru…
Обратите внимание, что в данном случае в качестве примеров для анализа используются только обычные массивы.
1. введение
Rulez-это пакет зависимостей composer, реализованный на PHP для реализации механизма правил фильтрации данных. RulerZ не только поддерживает фильтрацию массивов, но также поддерживает ORM, такие как поисковая система Eloquent, Doctrine и Solr. Это пакет с открытым исходным кодом, в котором отсутствуют официальные китайские документы. Конечно, из-за относительно небольшого количества звезд автор также может чувствовать себя ненужным.
2. установка
Запустите в каталоге, где находится ваш композитор проекта. JSON находится:
composer require 'kphoen/rulerz'
3. Использовать-фильтр
Существующий массив выглядит следующим образом:
$players = [
['pseudo' => 'Joe', 'fullname' => 'Joe la frite', 'gender' => 'M', 'points' => 2500],
['pseudo' => 'Moe', 'fullname' => 'Moe, from the bar!', 'gender' => 'M', 'points' => 1230],
['pseudo' => 'Alice', 'fullname' => 'Alice, from... you know.', 'gender' => 'F', 'points' => 9001],
];
Механизм инициализации:
use RulerZ\Compiler\Compiler;
use RulerZ\Target;
use RulerZ\RulerZ;
// compiler
$compiler = Compiler::create();
// RulerZ engine
$rulerz = new RulerZ(
$compiler, [
New Target Native Native ([/// Note, here is the addition of the target compiler, which corresponds to Native when processing data sources of array type
'length' => 'strlen'
]),
]
);Создайте правило:
$rule = "gender = :gender and points > :min_points'
Передайте параметры и правила движку для анализа.
$parameters = [
'min_points' => 30,
'gender' => 'F',
];
$result = iterator_to_array(
$rulerz->filter($players, $rule, $parameters) // the parameters can be omitted if empty
);
// result is a filtered array
array:1 [▼
0 => array:4 [▼
"pseudo" => "Alice"
"fullname" => "Alice, from... you know."
"gender" => "F"
"points" => 9001
]
]4. Использовать-определяет, соблюдаются ли правила
$rulerz->satisfies($player, $rule, $parameters); // Returns a Boolean value, true for satisfaction
5. Интерпретация Базового Кода
Далее давайте посмотрим, что происходит от создания компилятора до конечного результата. 1. Компилятор::создать();
2. Инициализируйте механизм правил. новый правитель()
public function __construct(Compiler $compiler, array $compilationTargets = [])
{
$this->compiler = $compiler;
foreach ($compilationTargets as $targetCompiler) {
$this->registerCompilationTarget($targetCompiler);
}
}Первый параметр здесь-это просто класс компилятора, а второй-целевой класс компилятора (фактически обрабатывающий источник данных), потому что мы выбрали массив, поэтому целевой компилятор здесь Родной Движок поместит целевой скомпилированный класс в свои собственные свойства $цели компиляции 。
public function registerCompilationTarget(CompilationTarget $compilationTarget): void
{
$this->compilationTargets[] = $compilationTarget;
}3. Использование фильтра или удовлетворяет метод
Это и есть ядро. Возьмем в качестве примера фильтр:
public function filter($target, string $rule, array $parameters = [], array $executionContext = [])
{
$targetCompiler = $this->findTargetCompiler($target, CompilationTarget::MODE_FILTER);
$compilationContext = $targetCompiler->createCompilationContext($target);
$executor = $this->compiler->compile($rule, $targetCompiler, $compilationContext);
return $executor->filter($target, $parameters, $targetCompiler->getOperators()->getOperators(), new ExecutionContext($executionContext));
}Первый шаг проверяет, поддерживает ли целевой компилятор режим фильтрации. Вторым шагом является создание контекста компиляции, который, как правило, является единым экземпляром класса Context
public function createCompilationContext($target): Context
{
return new Context();
}Третий шаг заключается в выполнении метода compile () компилятора
public function compile(string $rule, CompilationTarget $target, Context $context): Executor
{
$context['rule_identifier'] = $this->getRuleIdentifier($target, $context, $rule);
$context['executor_classname'] = 'Executor_'.$context['rule_identifier'];
$context['executor_fqcn'] = '\RulerZ\Compiled\Executor\Executor_'.$context['rule_identifier'];
if (!class_exists($context['executor_fqcn'], false)) {
$compiler = function () use ($rule, $target, $context) {
return $this->compileToSource($rule, $target, $context);
};
$this->evaluator->evaluate($context['rule_identifier'], $compiler);
}
return new $context['executor_fqcn']();
}
protected function getRuleIdentifier(CompilationTarget $compilationTarget, Context $context, string $rule): string
{
return hash('crc32b', get_class($compilationTarget).$rule.$compilationTarget->getRuleIdentifierHint($rule, $context));
}
protected function compileToSource(string $rule, CompilationTarget $compilationTarget, Context $context): string
{
$ast = $this->parser->parse($rule);
$executorModel = $compilationTarget->compile($ast, $context);
$flattenedTraits = implode(PHP_EOL, array_map(function ($trait) {
return "\t".'use \'.ltrim($trait, '\').';';
}, $executorModel->getTraits()));
$extraCode = '';
foreach ($executorModel->getCompiledData() as $key => $value) {
$extraCode .= sprintf('private $%s = %s;'.PHP_EOL, $key, var_export($value, true));
}
$commentedRule = str_replace(PHP_EOL, PHP_EOL.' // ', $rule);
return <<getCompiledRule()};
}
}
EXECUTOR;
} Этот код генерирует хэш-строку и сращивание Исполнителя в соответствии с алгоритмом crc 13 в качестве имени временного класса исполнителя и записывает связанный с исполнителем код во временный каталог, упомянутый выше. Сгенерированный код выглядит следующим образом:
// /private/var/folders/w_/sh4r42wn4_b650l3pc__fh7h0000gp/T/rulerz_executor_ff2800e8
:min_points and points > :min_points
protected function execute($target, array $operators, array $parameters)
{
return ($this->unwrapArgument($target["gender"]) == $parameters["gender"] && ($this->unwrapArgument($target["points"]) > $parameters["min_points"] && $this->unwrapArgument($target["points"]) > $parameters["min_points"]));
}
}Этот файл временного класса является последним классом, выполняющим действие фильтрации. Сначала выполняется метод фильтра в свойстве фильтра, который основан на логическом значении, возвращаемом execute, чтобы определить, следует ли возвращать квалифицированные строки через итераторы. Метод execute возвращает значение true/false, определяя, соответствует ли соответствующая ячейка в каждой строке решению по одному в соответствии с конкретными параметрами и операторами.
public function filter($target, array $parameters, array $operators, ExecutionContext $context)
{
return IteratorTools::fromGenerator(function () use ($target, $parameters, $operators) {
foreach ($target as $row) {
$targetRow = is_array($row) ? $row : new ObjectContext($row);
if ($this->execute($targetRow, $operators, $parameters)) {
yield $row;
}
}
});
}Удовлетворяет и фильтры имеют схожую базовую логику, но, наконец, удовлетворяет, выполняет одно суждение.
Одна из проблем заключается в том, как наш компилятор знает установленные нами правила работы. $правило Что означает синтаксический анализ? Это связано с другой проблемой-абстрактным синтаксическим деревом (AST).
Идем дальше – абстрактное синтаксическое дерево
Мы все знаем, что движок PHP Zend в процессе интерпретации кода представляет собой процесс грамматического и лексического анализа, этот процесс называется синтаксическим анализатором, в середине кода в абстрактное синтаксическое дерево, что является ключевым шагом для движка для понимания кода.
Аналогично, когда мы пишем строку правила, как код может понять, что мы пишем? Это абстрактное грамматическое дерево.
Возьмем в качестве примера приведенное выше правило:
пол =:пол и баллы >:min_points
Здесь=, и, > – все операторы, но машина не знает, что они являются операторами или что означают другие поля.
Поэтому rulez использует свой собственный шаблон грамматики.
Во-первых, по умолчанию определено несколько операторов.
function ($a, $b) {
return sprintf('(%s && %s)', $a, $b);
},
'or' => function ($a, $b) {
return sprintf('(%s || %s)', $a, $b);
},
'not' => function ($a) {
return sprintf('!(%s)', $a);
},
'=' => function ($a, $b) {
return sprintf('%s == %s', $a, $b);
},
'is' => function ($a, $b) {
return sprintf('%s === %s', $a, $b);
},
'!=' => function ($a, $b) {
return sprintf('%s != %s', $a, $b);
},
'>' => function ($a, $b) {
return sprintf('%s > %s', $a, $b);
},
'>=' => function ($a, $b) {
return sprintf('%s >= %s', $a, $b);
},
'<' => function ($a, $b) {
return sprintf('%s < %s', $a, $b);
},
'<=' => function ($a, $b) {
return sprintf('%s <= %s', $a, $b);
},
'in' => function ($a, $b) {
return sprintf('in_array(%s, %s)', $a, $b);
},
];
$defaultOperators = [
'sum' => function () {
return array_sum(func_get_args());
},
];
$definitions = new Definitions($defaultOperators, $defaultInlineOperators);
return $definitions->mergeWith($customOperators);
}
}
В синтаксическом анализаторе линейки существуют следующие методы:
public function parse($rule)
{
if ($this->parser === null) {
$this->parser = Compiler\Llk::load(
new File\Read(__DIR__.'/../Grammar.pp')
);
}
$this->nextParameterIndex = 0;
return $this->visit($this->parser->parse($rule));
}
Здесь мы будем интерпретировать основной файл грамматики, Grammar.pp, грамматический скрипт на языке Паскаль:
// // Hoa // // // @license // // New BSD License // // Copyright © 2007-2015, Ivan Enderlin. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of the Hoa nor the names of its contributors may be // used to endorse or promote products derived from this software without // specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // Inspired from \Hoa\Ruler\Grammar. // // @author Stéphane Py <[email protected]> // @author Ivan Enderlin <[email protected]> // @author Kévin Gomez <[email protected]> // @copyright Copyright © 2007-2015 Stéphane Py, Ivan Enderlin, Kévin Gomez. // @license New BSD License %skip space \s // Scalars. %token true (?i)true %token false (?i)false %token null (?i)null // Logical operators %token not (?i)not\b %token and (?i)and\b %token or (?i)or\b %token xor (?i)xor\b // Value %token string ("|')(.*?)(? logical_operation() #operation )? operand: ::parenthesis_:: logical_operation() ::_parenthesis:: | value() parameter:| value: ::not:: logical_operation() #not | | | | | | | parameter() | variable() | array_declaration() | function_call() variable: ( object_access() #variable_access )* object_access: ::dot:: #attribute_access #array_declaration: ::bracket_:: value() ( ::comma:: value() )* ::_bracket:: #function_call: ::parenthesis_:: ( logical_operation() ( ::comma:: logical_operation() )* )? ::_parenthesis::
Приведенная выше ссылка:: метод load загружает базовое содержимое грамматики и анализирует маркеры фрагментов. Логика синтаксического анализа токенов заключается в регулярном сопоставлении некоторых операторов и базовых идентификаторов, которые нам нужны, и извлечении соответствующих регулярных выражений:
array:1 [▼
"default" => array:20 [▼
"skip" => "\s"
"true" => "(?i)true"
"false" => "(?i)false"
"null" => "(?i)null"
"not" => "(?i)not\b"
"and" => "(?i)and\b"
"or" => "(?i)or\b"
"xor" => "(?i)xor\b"
"string" => "("|')(.*?)(? "-?\d+\.\d+"
"integer" => "-?\d+"
"parenthesis_" => "\("
"_parenthesis" => "\)"
"bracket_" => "\["
"_bracket" => "\]"
"comma" => ","
"dot" => "\."
"positional_parameter" => "\?"
"named_parameter" => ":[a-z-A-Z0-9_]+"
"identifier" => "[^\s\(\)\[\],\.]+"
]
]
Этот шаг также создает необработанные правила
array:10 [▼ "#expression" => " logical_operation()" "logical_operation" => " operation() ( ( ::and:: #and | ::or:: #or | ::xor:: #xor ) logical_operation() )?" "operation" => " operand() (logical_operation() #operation )?" "operand" => " ::parenthesis_:: logical_operation() ::_parenthesis:: | value()" "parameter" => " | " "value" => " ::not:: logical_operation() #not | | | | | | | parameter() | variable() | array_declaration() | function_call( ▶" "variable" => " ( object_access() #variable_access )*" "object_access" => " ::dot:: #attribute_access" "#array_declaration" => " ::bracket_:: value() ( ::comma:: value() )* ::_bracket::" "#function_call" => " ::parenthesis_:: ( logical_operation() ( ::comma:: logical_operation() )* )? ::_parenthesis::" ]
rawRules анализирует и заменяет пустые места в методе analyzeRules класса analyzer: лексический анализатор компилятора Llk Lexer () анализирует каждый элемент массива rawRules в двусторонний стек списков (SplStack) на основе значения атрибута $_ppLexemes, а затем вставляет и удаляет стек, чтобы сформировать массив всех операторов и экземпляров токенов. Исп..
array:54 [▼
0 => Concatenation {#64 ▶}
"expression" => Concatenation {#65 ▼
#_name: "expression"
#_children: array:1 [▼
0 => 0
]
#_nodeId: "#expression"
#_nodeOptions: []
#_defaultId: "#expression"
#_defaultOptions: []
#_pp: " logical_operation()"
#_transitional: false
}
2 => Token {#62 ▶}
3 => Concatenation {#63 ▼
#_name: 3
#_children: array:1 [▼
0 => 2
]
#_nodeId: "#and"
#_nodeOptions: []
#_defaultId: null
#_defaultOptions: []
#_pp: null
#_transitional: true
}
4 => Token {#68 ▶}
5 => Concatenation {#69 ▶}
6 => Token {#70 ▶}
7 => Concatenation {#71 ▶}
8 => Choice {#72 ▶}
9 => Concatenation {#73 ▶}
10 => Repetition {#74 ▶}
"logical_operation" => Concatenation {#75 ▶}
12 => Token {#66 ▶}
13 => Concatenation {#67 ▶}
14 => Repetition {#78 ▶}
"operation" => Concatenation {#79 ▶}
16 => Token {#76 ▶}
17 => Token {#77 ▶}
18 => Concatenation {#82 ▶}
"operand" => Choice {#83 ▶}
20 => Token {#80 ▶}
21 => Token {#81 ▼
#_tokenName: "named_parameter"
#_namespace: null
#_regex: null
#_ast: null
#_value: null
#_kept: true
#_unification: -1
#_name: 21
#_children: null
#_nodeId: null
#_nodeOptions: []
#_defaultId: null
#_defaultOptions: []
#_pp: null
#_transitional: true
}
"parameter" => Choice {#86 ▶}
23 => Token {#84 ▶}
24 => Concatenation {#85 ▶}
25 => Token {#89 ▶}
26 => Token {#90 ▶}
27 => Token {#91 ▶}
28 => Token {#92 ▶}
29 => Token {#93 ▶}
30 => Token {#94 ▶}
"value" => Choice {#95 ▶}
32 => Token {#87 ▶}
33 => Concatenation {#88 ▶}
34 => Repetition {#98 ▶}
"variable" => Concatenation {#99 ▶}
36 => Token {#96 ▶}
37 => Token {#97 ▶}
"object_access" => Concatenation {#102 ▶}
39 => Token {#100 ▶}
40 => Token {#101 ▶}
41 => Concatenation {#105 ▶}
42 => Repetition {#106 ▶}
43 => Token {#107 ▶}
"array_declaration" => Concatenation {#108 ▶}
45 => Token {#103 ▶}
46 => Token {#104 ▶}
47 => Token {#111 ▶}
48 => Concatenation {#112 ▶}
49 => Repetition {#113 ▶}
50 => Concatenation {#114 ▶}
51 => Repetition {#115 ▶}
52 => Token {#116 ▶}
"function_call" => Concatenation {#117 ▶}
]
Затем вернитесь к экземпляру LlkParser компилятора Hoa, который имеет метод синтаксического анализа, формирующий грамматическое дерево.
public function parse($text, $rule = null, $tree = true)
{
$k = 1024;
if (isset($this->_pragmas['parser.lookahead'])) {
$k = max(0, intval($this->_pragmas['parser.lookahead']));
}
$lexer = new Lexer($this->_pragmas);
$this->_tokenSequence = new Iterator\Buffer(
$lexer->lexMe($text, $this->_tokens),
$k
);
$this->_tokenSequence->rewind();
$this->_errorToken = null;
$this->_trace = [];
$this->_todo = [];
if (false === array_key_exists($rule, $this->_rules)) {
$rule = $this->getRootRule();
}
$closeRule = new Rule\Ekzit($rule, 0);
$openRule = new Rule\Entry($rule, 0, [$closeRule]);
$this->_todo = [$closeRule, $openRule];
do {
$out = $this->unfold();
if (null !== $out &&
'EOF' === $this->_tokenSequence->current()['token']) {
break;
}
if (false === $this->backtrack()) {
$token = $this->_errorToken;
if (null === $this->_errorToken) {
$token = $this->_tokenSequence->current();
}
$offset = $token['offset'];
$line = 1;
$column = 1;
if (!empty($text)) {
if (0 === $offset) {
$leftnl = 0;
} else {
$leftnl = strrpos($text, "\n", -(strlen($text) - $offset) - 1) ?: 0;
}
$rightnl = strpos($text, "\n", $offset);
$line = substr_count($text, "\n", 0, $leftnl + 1) + 1;
$column = $offset - $leftnl + (0 === $leftnl);
if (false !== $rightnl) {
$text = trim(substr($text, $leftnl, $rightnl - $leftnl), "\n");
}
}
throw new Compiler\Exception\UnexpectedToken(
'Unexpected token "%s" (%s) at line %d and column %d:' .
"\n" . '%s' . "\n" . str_repeat(' ', $column - 1) . '↑',
0,
[
$token['value'],
$token['token'],
$line,
$column,
$text
],
$line,
$column
);
}
} while (true);
if (false === $tree) {
return true;
}
$tree = $this->_buildTree();
if (!($tree instanceof TreeNode)) {
throw new Compiler\Exception(
'Parsing error: cannot build AST, the trace is corrupted.',
1
);
}
return $this->_tree = $tree;
}
Мы получаем полное грамматическое дерево, подобное этому:
Rule {#120 ▼
#_root: Operator {#414 ▼
#_name: "and"
#_arguments: array:2 [▼
0 => Operator {#398 ▼
#_name: "="
#_arguments: array:2 [▼
0 => Context {#396 ▼
#_id: "gender"
#_dimensions: []
}
1 => Parameter {#397 ▼
-name: "gender"
}
]
#_function: false
#_laziness: false
#_id: null
#_dimensions: []
}
1 => Operator {#413 ▼
#_name: "and"
#_arguments: array:2 [▼
0 => Operator {#401 ▼
#_name: ">"
#_arguments: array:2 [▼
0 => Context {#399 ▶}
1 => Parameter {#400 ▶}
]
#_function: false
#_laziness: false
#_id: null
#_dimensions: []
}
1 => Operator {#412 ▶}
]
#_function: false
#_laziness: true
#_id: null
#_dimensions: []
}
]
#_function: false
#_laziness: true
#_id: null
#_dimensions: []
}
}
Существуют корневые узлы, дочерние узлы, параметры оператора и экземпляры оператора модели линейки Тсж.
На этом этапе $executorModel = $цель компиляции – > компиляция ($ast, $контекст); вы можете получить доступ к дереву грамматики и проанализировать его с помощью метода посещения Native Visitor.
Этот шаг-visitOperator ()
/**
* {@inheritdoc}
*/
public function visitOperator(AST\Operator $element, &$handle = null, $eldnah = null)
{
$operatorName = $element->getName();
// the operator does not exist at all, throw an error before doing anything else.
if (!$this->operators->hasInlineOperator($operatorName) && !$this->operators->hasOperator($operatorName)) {
throw new OperatorNotFoundException($operatorName, sprintf('Operator "%s" does not exist.', $operatorName));
}
// expand the arguments
$arguments = array_map(function ($argument) use (&$handle, $eldnah) {
return $argument->accept($this, $handle, $eldnah);
}, $element->getArguments());
// and either inline the operator call
if ($this->operators->hasInlineOperator($operatorName)) {
$callable = $this->operators->getInlineOperator($operatorName);
return call_user_func_array($callable, $arguments);
}
$inlinedArguments = empty($arguments) ? '' : ', '.implode(', ', $arguments);
// or defer it.
return sprintf('call_user_func($operators["%s"]%s)', $operatorName, $inlinedArguments);
}
Затем скомпилированные правила можно получить следующими способами:
$executorModel->getCompiledRule() // The rule is $this - > unwrapArgument ($target ["gender"]) === parameters ["gender"] & ($this - > unwrapArgument ($target ["points"]) > parameters ["min_points"] & $this - > unwrapArgument ($target ["points"]) > parameters ["min_points"]).
Настройка оператора
Поскольку официальный документ слишком старый и не более, если вы настроите его в соответствии с его документом, у вас закружится голова. Вот соответствующий пример.
$compiler = Compiler::create();
$rulerz = new RulerZ($compiler, [
new Native([
'length' => 'strlen'
],[
'contains' => function ($a, $b) {
return sprintf('strstr(%s, %s)', $a, $b);
}
])
]);Выше содержит Представляет системную функцию. strstr() Чтобы определить, содержит ли $a символ $b, поскольку скомпилированный код генерируется строками, вы должны использовать строки для выражения логики суждения в этой анонимной функции, что также является одним из ее недостатков.
Оригинал: “https://developpaper.com/interpretation-of-rulerz-usage-and-implementation-principle-of-rule-engine/”