PHP语言表达式库ExpressionLanguage

2024-02-21 16:15:17 浏览数 (1)

symfony/expression-language 是 Symfony 框架中的一个组件,它允许你解析和执行表达式。这个组件非常有用,特别是当你需要动态地计算或评估某些条件或表达式时。

安装

代码语言:javascript复制
composer require symfony/expression-language

如果您在Symfony应用程序之外安装此组件,则必须在代码中要求 vendor/autoload.php 文件以启用Composer提供的类自动加载机制。

表达语言如何帮助我?

该组件的目的是允许用户在配置中使用表达式来实现更复杂的逻辑。例如:Symfony Framework 在安全性、验证规则和路由匹配中使用表达式。

除了在框架本身中使用组件之外,ExpressionLanguage 组件是业务规则引擎基础的理想候选者。这个想法是让网站的网站管理员在不使用PHP的情况下以动态的方式配置东西,并且不会引入安全问题:

代码语言:javascript复制
# Get the special price if
user.getGroup() in ['good_customers', 'collaborator']

# Promote article to the homepage when
article.commentCount > 100 and article.category not in ["misc"]

# Send an alert when
product.stock < 15

表达式可以被看作是一个非常受限制的PHP沙箱,并且不太容易受到外部注入的影响,因为您必须显式声明表达式中哪些变量可用(但您仍然应该清理最终用户提供并传递给表达式的任何数据)。

使用

ExpressionLanguage 组件可以编译和计算表达式。表达式是一行程序,通常返回布尔值,可供代码在if语句中执行表达式时使用。一个简单的表达式例子是1 2。您也可以使用更复杂的表达式,例如someArray[3].someMethod('bar')

该组件提供了两种使用表达式的方法:

  • evaluation:表达式在没有编译成PHP的情况下被求值;
  • compile:表达式被编译为PHP,因此可以缓存和计算。

简单入门

代码语言:javascript复制
<?php
/**
 * @desc PHP语言表达式引擎库ExpressionLanguage
 * @author Tinywan(ShaoBo Wan)
 * @date 2024/2/20 9:44
 */
declare(strict_types=1);

require '../vendor/autoload.php';

$expressionLanguage = new SymfonyComponentExpressionLanguageExpressionLanguage();

var_dump($expressionLanguage->evaluate('1   2')); // displays 3

var_dump($expressionLanguage->compile('1   2')); // displays (1   2)

Parsing and Linting 表达式

ExpressionLanguage 组件提供了一种解析和lint表达式的方法。parse()方法返回一个ParsedExpression实例,该实例可用于检查和操作表达式。另一方面,lint()返回一个布尔值,指示表达式是否有效。

Parse(解析)

symfony/expression-language 中,解析(parse)是指将字符串形式的表达式转换成内部可执行的表达式结构。这通常是通过组件的 parse 方法来实现的。

代码语言:javascript复制
<?php
require '../vendor/autoload.php';

$expressionLanguage = new SymfonyComponentExpressionLanguageExpressionLanguage();

// 解析表达式
$parsedExpression = $expressionLanguage->parse('1   2 * (3 - 4)', []);

// $parsedExpression 是一个 ParsedExpression 对象,代表解析后的表达式结构
var_dump($parsedExpression);

需要注意的是,parse 方法在这里并不执行表达式,只是将其转换成一种可以在之后执行的形式。

Lint(语法检查)

symfony/expression-language 中,并没有直接提供名为 lint 的方法来进行语法检查。但是,你可以通过尝试解析表达式并捕获可能抛出的异常来检查表达式的语法是否正确。

代码语言:javascript复制
<?php
require '../vendor/autoload.php';

use SymfonyComponentExpressionLanguageExpressionLanguage;

$expressionLanguage = new ExpressionLanguage();

try {
    $expressionString = '1   2 * (3 - 4';
    // 尝试解析表达式,如果解析失败,则捕获异常
    $expressionLanguage->lint($expressionString, null);
    echo '表达式语法正确' . "n";
} catch (SymfonyComponentExpressionLanguageSyntaxError $e) {
    // 如果捕获到 SyntaxError 异常,说明表达式语法有误
    echo '表达式语法错误:' . $e->getMessage() . "n";
}

表达式语法错误:Unclosed "(" around position 8 for expression 1 2 * (3 - 4.

注册函数

函数在每个特定的 ExpressionLanguage 实例上注册。这意味着函数可以在该实例执行的任何表达式中使用。

要注册函数,请使用register()。此方法有3个参数:

  • name 表达式中函数的名称;
  • compiler 当使用函数编译表达式时执行的函数;
  • evaluator 当表达式被求值时执行的函数。

Example

代码语言:javascript复制
<?php
/**
 * @desc 注册函数
 * @author Tinywan(ShaoBo Wan)
 * @date 2024/2/20 9:44
 */
declare(strict_types=1);

require '../vendor/autoload.php';

$expressionLanguage = new SymfonyComponentExpressionLanguageExpressionLanguage();

$expressionLanguage->register('custom_function_tinywan', function ($str): string {
    return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str);
}, function ($arguments, $str): string {
    if (!is_string($str)) {
        return $str;
    }

    return strtolower($str);
});

var_dump($expressionLanguage->evaluate('custom_function_tinywan("开源技术小栈,HELLO")'));
// this will print: 开源技术小栈,hello

除了自定义函数参数之外,求值器还将传递一个 arguments 变量作为其第一个参数,该变量等于 evaluate() 的第二个参数(例如,在求值表达式时的“值”)。

对象及其属性

Symfony表达式非常强大,它可以在表达式语言中拦截PHP对象及其属性。

代码语言:javascript复制
<?php
/**
 * @desc 对象及其属性
 * @author Tinywan(ShaoBo Wan)
 * @date 2024/2/20 9:44
 */
declare(strict_types=1);
require '../vendor/autoload.php';

use SymfonyComponentExpressionLanguageExpressionLanguage;

$expressionLanguage = new ExpressionLanguage();

class Product {
    public string $name;
    public float $price;
}

$product = new Product();
$product->name = 'Tinywan';
$product->price = 2024;

echo 'Product price is ' .
    $expressionLanguage->evaluate('product.price', ['product' => $product]) . "rn";
echo 'Is Product price higher than 2022: ' .
    $expressionLanguage->evaluate('product.price > 2022', ['product' => $product]) . "rn";

打印输出结果

代码语言:javascript复制
Product price is 2024
Is Product price higher than 2022: 1

其他应用

PHP-Casbin 是一个强大的、高效的开源访问控制框架应用,其权限管理机制支持多种访问控制模型。

扩展库:https://github.com/php-casbin/php-casbin

Casbin 应用案例

代码语言:javascript复制
/**
 * @param array $functions
 *
 * @return ExpressionLanguage
 */
protected function getExpressionLanguage(array $functions): ExpressionLanguage
{
    $expressionLanguage = new ExpressionLanguage();
    foreach ($functions as $key => $func) {
        $expressionLanguage->register($key, function (...$args) use ($key) {
            return sprintf($key.'(%1$s)', implode(',', $args));
        }, function ($arguments, ...$args) use ($func) {
            return $func(...$args);
        });
    }

    return $expressionLanguage;
}

$expressionLanguage = $this->getExpressionLanguage($functions);
$expression = $expressionLanguage->parse($expWithRule, array_merge($rTokens, $pTokens));

$result = $expressionLanguage->evaluate($expression, $parameters);

if ($result) {
    $policyEffects[0] = Effector::ALLOW;
} else {
    $policyEffects[0] = Effector::INDETERMINATE;
}

0 人点赞