Компонент ExpressionLanguage

Дата оновлення перекладу 2024-05-02

Компонент ExpressionLanguage

Компонент ExpressionLanguage надає двигун, який може скомпілювати та оцінити вирази. Вирази - це один рядок, який повертає значення (в основному, але не тільки, булеве).

Установка

1
$ composer require symfony/expression-language

Note

Якщо ви встановлюєте цей компонент поза додатком Symfony, вам потрібно підключити файл vendor/autoload.phpу вашому коді для включення механізму автозавантаження класів, наданих Composer. Детальніше можна прочитати у цій статті.

Як може двигун виразів допомогти мені?

Як може мова виразів допомогти мені?

Ціль цього компонента - дозволити користувачам використовувати вираз всередині конфігурації для складнішої логіки. Наприклад, фреймворк 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

Цей зміст було переміщено до сторінки ref:`оператор нульової коалесценції <component-expression-null-coalescing-operator>`_, розділу довідкової сторінки синтаксису ExpressionLanguage.

Парсинг та лінтування виразів

Компонент 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
// ...

// метод parse() повертає 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(), щоб перетворити АСД на масив:

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

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

Розширення ExpressionLanguage

ExpressionLanguage може бути розширено шляхом додавання користувацьких фукнцій. Наприклад, у фреймворку Symfony, безпека має користувацькі функції для перевірки ролі користувача.

Note

Якщо ви хочете дізнатися, як використовувати функції в якості виразів, прочитайте "".

Реєстрація функцій

Функції реєструються у кожному конкретному екземплярі ExpressionLanguage. Це означає, що функції можуть бути використані в будь-якому виразі, виконаному цим екземпляром.

Щоб зареєструвати функцію, використайте register(). Цей метод має 3 аргументи:

  • імʼя - Імʼя функції у виразі;
  • компілятор - Функція, виконана при компіляції виразу з використанням функції;
  • оцінювач - Функція, виконана при оцінюванні виразу.

Приклад:

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

На додаток до користувацьких аргументів функцій, оцінювачу передається змінна 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 зі статичним методом 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);
    }
}