Компонент ExpressionLanguage
Дата обновления перевода 2024-07-03
Компонент ExpressionLanguage
Компонент ExpressionLanguage предоставляет движок, который может скомпилировать и оценить выражения. Выражения - это однострочник, который возвращает значение (в основном, но не только, булево).
Установка
1
$ composer require symfony/expression-language
Note
Если вы устанавливаете этот компонент вне приложения Symfony, вам нужно
подключить файл vendor/autoload.php
в вашем коде для включения механизма
автозагрузки классов, предоставляемых Composer. Детальнее читайте в
этой статье.
Чем мне может помочь Expression Engine?
Целью этого компонента является позволить пользователям использовать выражения внутри конфигурации для более сложной логики. Например, фреймворк Symfony использует выражения в безопасности, для валидации правил и в сопоставлении маршрутов.
Кроме использования комонента в самом фреймворке, компонент ExpressionLanguage также является идеальным кандидатом для основания двигателя бизнес-правила. Идея заключатеся в том, чтобы позволить веб-разработчику сайта сконфигурировать всё динамически, не используя PHP и не вызывая проблем безопасности:
1 2 3 4 5 6 7 8
# Получить специальную цену, если
user.getGroup() in ['good_customers', 'collaborator']
# Продвинуть статью на главную страницу, когда
article.commentCount > 100 and article.category not in ["misc"]
# Отправить уведомление, когда
product.stock < 15
Выражения можно рассматривать, как очень защищённую песочницу PHP, и они имеют иммунитет к внешним внедрениям, так как вы должны ясно объявить, какие переменные доступны в выражении.
Использование
Компонент ExpressionLanguage может компилировать и оценивать выражения. Выражения
- это однострочники, которые часто возвращают булево значение, которое может быть
использовано кодом, выполняя выражение в утверждении 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)
Tip
О синтаксисе компонента ExpressionLanguage читайте в Синтаксис выражений.
Оператор коалесценции нуля
Note
Это содержимое было перемещено в раздел справочной страницы по синтаксису языка ExpressionLanguage ref:`оператор коалесценции нуля <component-expression-null-coalescing-operator>`_.
Парсинг и линтинг выражений
Компонент ExpressionLanguage предоставляет способ парсинга и линтинга выражений.
Метод parse()
возвращает экземпляр ParsedExpression,
который может быть использован для проверки и манипулирования выражением.
lint(), с другой
стороны, возвращает булево число, указывающее, является ли выражение валидным или нет:
1 2 3 4 5 6 7 8 9
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
$expressionLanguage = new ExpressionLanguage();
var_dump($expressionLanguage->parse('1 + 2'));
// отображает AST узлы выражения, которые можно
// исследовать и манипулировать
var_dump($expressionLanguage->lint('1 + 2')); // displays true
Поведение этих методов можно сконфигурировать с помощью некоторых флажков, определенных в классе 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();
// это возвращает true, так как неизвестные переменные и функции игнорируются
var_dump($expressionLanguage->lint('unknown_var + unknown_function()', Parser::IGNORE_UNKNOWN_VARIABLES | Parser::IGNORE_UNKNOWN_FUNCTIONS));
7.1
Поддержка флажков в методах parse()
и lint()
была
представлена в Symfony 7.1.
Передача в переменных
Вы также можете передать переменные в выражение, которые могут быть любым влаидным 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,
]
)); // отображает "Honeycrisp"
При использовании этого компонента в приложении Symfony некоторые объекты и переменные автоматически внедряются Symfony, чтобы вы могли использовать их в своих выражениях (например, запрос, текущий пользователь и т.д.):
- Переменные, доступные в выражениях безопасности;
- Переменные, доступные в выражениях сервис-контейнера;
- Переменные, доступные в выражениях маршрутизации .
Caution
При использовании переменных в выражениях избегайте передачи недостоверных данных в массив переменных. Если этого избежать не удается, дезинфицируйте неалфавитно-цифровые символы в недоверенных данных, чтобы предотвратить внедрение злоумышленниками управляющих символов и изменение выражения.
Кеширование
Компонент 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);
See also
Дополнительную информацию о доступных адаптерах кеша см. в документации Компонент 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
Сброс и редактирование АСД
Сложно манипулировать или проверять выражения, созданные с помощью компонента ExpressionLanguage, поскольку выражения представляют собой обычные строки. Более эффективным подходом является преобразовать эти выражения в АСД. В информатике, АСД (Абстрактное синтаксическое дерево) - это "древовидное представление структуры исходного кода, написанного на каком-либо языке программирования ". В Symfony АСД языка ExpressionLanguage представляет собой набор узлов, содержащих PHP-классы, представляющие данное выражение.
Сброс АСД
Вызовите метод getNodes() после разбора любого выражения, чтобы получить его АСД:
1 2 3 4 5 6 7 8 9 10 11 12
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
$ast = (new ExpressionLanguage())
->parse('1 + 2', [])
->getNodes()
;
// сбросить узлы АСД для проверки
var_dump($ast);
// сбросить узлы АСД как представление строки
$astAsString = $ast->dump();
Манипуляции с АСД
Узлы АСД также могут быть сброшены в массив узлов PHP, что позволяет манипулировать ими. Вызовите toArray() для преобразования АСД в массив:
// ...
- $astAsArray = (new ExpressionLanguage())
- ->parse('1 + 2', []) ->getNodes() ->toArray()
;
Расширение ExpressionLanguage
ExpressionLanguage может быть расширен путем добавления пользовательских функций. Например, в фреймворке Symfony, безопасность имеет пользовательские функции для проверки роли пользователя.
Note
Если вы хотите узнать, как использовать функции в выражении, прочитайте "".
Регистрация функций
Функции регистрируются на каждом конкретном экземпляре 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")'));
// Это выведет: 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);
}),
];
}
}
Tip
Для создания функции выражения из функции PHP с помощью статического метода :метод:`Symfony\Component\ExpressionLanguage\ExpressionFunction::fromPhp`:
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;
// используя конструктор
$expressionLanguage = new ExpressionLanguage(null, [
new StringExpressionLanguageProvider(),
// ...
]);
// используя registerProvider()
$expressionLanguage->registerProvider(new StringExpressionLanguageProvider());
Tip
Рекомендуется создать собственный класс 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 = [])
{
// добавляет провайдер по умолчанию, чтобы пользователи могли его переопределить
array_unshift($providers, new StringExpressionLanguageProvider());
parent::__construct($cache, $providers);
}
}