跳到內容

ExpressionLanguage 組件

編輯此頁面

ExpressionLanguage 組件提供了一個引擎,可以編譯和評估運算式。運算式是一個單行程式碼,它會傳回一個值(主要是布林值,但不限於此)。

安裝

1
$ composer require symfony/expression-language

注意

如果您在 Symfony 應用程式之外安裝此組件,您必須在您的程式碼中引入 vendor/autoload.php 檔案,以啟用 Composer 提供的類別自動載入機制。請閱讀這篇文章以取得更多詳細資訊。

Expression Language 如何幫助我?

此組件的目的是允許使用者在設定中使用運算式,以實現更複雜的邏輯。例如,Symfony 框架在安全性、驗證規則和路由匹配中使用運算式。

除了在框架本身中使用此組件外,ExpressionLanguage 組件也是建構「商業規則引擎」的完美選擇。其想法是讓網站管理員以動態方式配置事物,而無需使用 PHP 且不會引入安全問題

1
2
3
4
5
6
7
8
# 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 組件可以編譯和評估運算式。運算式是通常傳回布林值 (Boolean) 的單行程式碼,程式碼可以使用此布林值在 if 語句中執行。運算式的簡單範例是 1 + 2。您也可以使用更複雜的運算式,例如 someArray[3].someMethod('bar')

此組件提供 2 種使用運算式的方式

  • 評估:運算式在未編譯為 PHP 的情況下進行評估;
  • 編譯:運算式被編譯為 PHP,因此可以快取和評估。

此組件的主要類別是 ExpressionLanguage

1
2
3
4
5
6
7
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$expressionLanguage = new ExpressionLanguage();

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

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

提示

請參閱運算式語法以了解 ExpressionLanguage 組件的語法。

Null 聯合運算符

注意

此內容已移至 ExpressionLanguage 語法參考頁面的null 聯合運算符章節。

解析和檢查運算式

ExpressionLanguage 組件提供了解析和檢查運算式的方法。parse() 方法返回一個 ParsedExpression 實例,可用於檢查和操作運算式。另一方面,如果運算式無效,lint() 會拋出 SyntaxError

1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$expressionLanguage = new ExpressionLanguage();

var_dump($expressionLanguage->parse('1 + 2', []));
// displays the AST nodes of the expression which can be
// inspected and manipulated

$expressionLanguage->lint('1 + 2', []); // doesn't throw anything

$expressionLanguage->lint('1 + a', []);
// throws a SyntaxError exception:
// "Variable "a" is not valid around position 5 for expression `1 + a`."

這些方法的行為可以使用 Parser 類別中定義的一些旗標來配置

  • IGNORE_UNKNOWN_VARIABLES:如果運算式中未定義變數,則不拋出例外;
  • IGNORE_UNKNOWN_FUNCTIONS:如果運算式中未定義函數,則不拋出例外。

以下是如何使用這些旗標

1
2
3
4
5
6
7
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\Parser;

$expressionLanguage = new ExpressionLanguage();

// does not throw a SyntaxError because the unknown variables and functions are ignored
$expressionLanguage->lint('unknown_var + unknown_function()', [], Parser::IGNORE_UNKNOWN_VARIABLES | Parser::IGNORE_UNKNOWN_FUNCTIONS);

7.1

在 Symfony 7.1 中引入了對 parse()lint() 方法中旗標的支援。

傳入變數

您也可以將變數傳遞到運算式中,變數可以是任何有效的 PHP 類型 (包括物件)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$expressionLanguage = new ExpressionLanguage();

class Apple
{
    public string $variety;
}

$apple = new Apple();
$apple->variety = 'Honeycrisp';

var_dump($expressionLanguage->evaluate(
    'fruit.variety',
    [
        'fruit' => $apple,
    ]
)); // displays "Honeycrisp"

當在 Symfony 應用程式內使用此組件時,Symfony 會自動注入某些物件和變數,以便您可以在運算式中使用它們 (例如,請求、當前使用者等)。

快取

ExpressionLanguage 組件提供了一個 compile() 方法,以便能夠以純 PHP 快取運算式。但在內部,此組件也會快取已解析的運算式,因此重複的運算式可以更快地編譯/評估。

工作流程

evaluate()compile() 都需要在提供回傳值之前執行一些操作。對於 evaluate(),這種額外負荷甚至更大。

這兩種方法都需要將運算式符號化和解析。這由 parse() 方法完成。它返回一個 ParsedExpression。現在,compile() 方法只返回此物件的字串轉換。evaluate() 方法需要遍歷「節點」(儲存在 ParsedExpression 中的運算式片段) 並即時評估它們。

為了節省時間,ExpressionLanguage 會快取 ParsedExpression,以便它可以跳過重複運算式的符號化和解析步驟。快取由 PSR-6 CacheItemPoolInterface 實例完成 (預設情況下,它使用 ArrayAdapter)。您可以透過建立自訂快取池或使用可用的快取池之一,並使用建構子注入它來自訂此設定

1
2
3
4
5
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$cache = new RedisAdapter(...);
$expressionLanguage = new ExpressionLanguage($cache);

另請參閱

請參閱快取組件文件以取得有關可用快取配接器的更多資訊。

使用已解析和序列化的運算式

evaluate()compile() 都可以處理 ParsedExpressionSerializedParsedExpression

1
2
3
4
5
6
// ...

// the parse() method returns a ParsedExpression
$expression = $expressionLanguage->parse('1 + 4', []);

var_dump($expressionLanguage->evaluate($expression)); // prints 5
1
2
3
4
5
6
7
8
9
use Symfony\Component\ExpressionLanguage\SerializedParsedExpression;
// ...

$expression = new SerializedParsedExpression(
    '1 + 4',
    serialize($expressionLanguage->parse('1 + 4', [])->getNodes())
);

var_dump($expressionLanguage->evaluate($expression)); // prints 5

AST 傾印和編輯

很難操作或檢查使用 ExpressionLanguage 組件建立的運算式,因為這些運算式是純字串。更好的方法是將這些運算式轉換為 AST。在電腦科學中,AST (抽象語法樹) 是「以程式語言編寫的原始碼結構的樹狀表示法」。在 Symfony 中,ExpressionLanguage AST 是一組包含代表給定運算式的 PHP 類別的節點。

傾印 AST

在解析任何運算式後呼叫 getNodes() 方法以取得其 AST。

1
2
3
4
5
6
7
8
9
10
11
12
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$ast = (new ExpressionLanguage())
    ->parse('1 + 2', [])
    ->getNodes()
;

// dump the AST nodes for inspection
var_dump($ast);

// dump the AST nodes as a string representation
$astAsString = $ast->dump();

操作 AST

AST 的節點也可以傾印到 PHP 節點陣列中,以允許操作它們。呼叫 toArray() 方法以將 AST 轉換為陣列。

1
2
3
4
5
6
7
// ...

$astAsArray = (new ExpressionLanguage())
    ->parse('1 + 2', [])
    ->getNodes()
    ->toArray()
;

擴展 ExpressionLanguage

ExpressionLanguage 可以透過新增自訂函數來擴展。例如,在 Symfony 框架中,安全性具有自訂函數來檢查使用者的角色。

注意

如果您想了解如何在運算式中使用函數,請閱讀「運算式語法」。

註冊函數

函數在每個特定的 ExpressionLanguage 實例上註冊。這表示函數可以用於由該實例執行的任何運算式。

若要註冊函數,請使用 register()。此方法有 3 個參數:

  • name - 運算式中函數的名稱;
  • compiler - 在使用函數編譯運算式時執行的函數;
  • evaluator - 在評估運算式時執行的函數。

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$expressionLanguage = new ExpressionLanguage();
$expressionLanguage->register('lowercase', 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('lowercase("HELLO")'));
// this will print: hello

除了自訂函數參數外,evaluator 還會將 arguments 變數作為其第一個參數傳遞,該參數等於 evaluate() 的第二個參數 (例如,評估運算式時的「值」)。

使用運算式提供器

當您在程式庫中使用 ExpressionLanguage 類別時,您通常會想要新增自訂函數。若要執行此操作,您可以透過建立實作 ExpressionFunctionProviderInterface 的類別來建立新的運算式提供器。

此介面需要一個方法:getFunctions(),它返回要註冊的運算式函數陣列 (ExpressionFunction 的實例)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use Symfony\Component\ExpressionLanguage\ExpressionFunction;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;

class StringExpressionLanguageProvider implements ExpressionFunctionProviderInterface
{
    public function getFunctions(): array
    {
        return [
            new ExpressionFunction('lowercase', 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);
            }),
        ];
    }
}

提示

若要使用 fromPhp() 靜態方法從 PHP 函數建立運算式函數

1
ExpressionFunction::fromPhp('strtoupper');

支援命名空間函數,但它們需要第二個參數來定義運算式的名稱。

1
ExpressionFunction::fromPhp('My\strtoupper', 'my_strtoupper');

您可以使用 registerProvider() 註冊提供器,或使用建構子的第二個參數。

1
2
3
4
5
6
7
8
9
10
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

// using the constructor
$expressionLanguage = new ExpressionLanguage(null, [
    new StringExpressionLanguageProvider(),
    // ...
]);

// using registerProvider()
$expressionLanguage->registerProvider(new StringExpressionLanguageProvider());

提示

建議您在程式庫中建立自己的 ExpressionLanguage 類別。現在您可以透過覆寫建構子來新增擴展。

1
2
3
4
5
6
7
8
9
10
11
12
13
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage;

class ExpressionLanguage extends BaseExpressionLanguage
{
    public function __construct(?CacheItemPoolInterface $cache = null, array $providers = [])
    {
        // prepends the default provider to let users override it
        array_unshift($providers, new StringExpressionLanguageProvider());

        parent::__construct($cache, $providers);
    }
}
本作品,包括程式碼範例,均依 Creative Commons BY-SA 3.0 授權條款授權。
目錄
    版本