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()
都可以處理 ParsedExpression
和 SerializedParsedExpression
。
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();
擴展 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);
}
}