Spec-Zone.ru › Twig 2

Расширение Twig

Twig можно расширить множеством способов; вы можете добавить дополнительные теги, фильтры, тесты, операторы, глобальные переменные и функции. Вы даже можете расширить сам парсер с помощью посетителей узлов.

Примечание

Первый раздел этой главы описывает, как расширить Twig. Если вы хотите повторно использовать свои изменения в разных проектах или поделиться ими с другими, вам следует создать расширение, как описано в следующем разделе.

Внимание

При расширении Twig без создания расширения Twig не сможет перекомпилировать ваши шаблоны при обновлении PHP-кода. Чтобы увидеть изменения в реальном времени, отключите кеширование шаблонов или упакуйте свой код в расширение (см. следующий раздел этой главы).

Прежде чем расширять Twig, вы должны понять различия между всеми возможными точками расширения и когда их использовать.

Во-первых, помните, что Twig имеет две основные языковые конструкции:

  • {{ }}: используется для вывода результата вычисления выражения;
  • {% %}: используется для выполнения операторов.

Чтобы понять, почему Twig предоставляет так много точек расширения, давайте посмотрим, как реализовать генератор Lorem ipsum (он должен знать количество слов для генерации).

Вы можете использовать lipsum тег:

{% lipsum 40 %}

Это работает, но использование тега для lipsum не является хорошей идеей по крайней мере по трем причинам:

  • lipsum не является языковой конструкцией;

  • Тег выводит что-то;

  • Тег не гибкий, так как вы не можете использовать его в выражении:

    {{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}
    

На самом деле, вам редко нужно создавать теги; и это хорошая новость, потому что теги — самая сложная точка расширения.

Теперь давайте используем lipsum фильтр:

{{ 40|lipsum }}

Опять же, это работает. Но фильтр должен преобразовать переданное значение в другое. Здесь мы используем значение для указания количества слов для генерации (поэтому 40 является аргументом фильтра, а не значением, которое мы хотим преобразовать).

Далее, давайте используем lipsum функцию:

{{ lipsum(40) }}

Вот и все. Для этого конкретного примера создание функции является используемой точкой расширения. И вы можете использовать ее везде, где допускается выражение:

{{ 'some text' ~ lipsum(40) ~ 'some more text' }}

{% set lipsum = lipsum(40) %}

Наконец, вы также можете использовать объект глобального с методом, способным генерировать текст lorem ipsum:

{{ text.lipsum(40) }}

Как правило, используйте функции для часто используемых функций и глобальные объекты для всего остального.

Помните следующее, когда вы хотите расширить Twig:

Что? Сложность реализации? Как часто? Когда?
макрос просто часто Генерация контента
глобальный просто часто Объект помощник
функция просто часто Генерация контента
фильтр просто часто Преобразование значения
тег сложно редко Конструкции языка DSL
тест просто редко Булево решение
оператор просто редко Преобразование значений

Глобальные переменные

Глобальная переменная похожа на любую другую переменную шаблона, за исключением того, что она доступна во всех шаблонах и макросах:

$twig = new \Twig\Environment($loader);
$twig->addGlobal('text', new Text());

Затем вы можете использовать переменную text в любом месте шаблона:

{{ text.lipsum(40) }}

Фильтры

Создание фильтра состоит в сопоставлении имени с вызываемой PHP-функцией:

// an anonymous function
$filter = new \Twig\TwigFilter('rot13', function ($string) {
    return str_rot13($string);
});

// or a simple PHP function
$filter = new \Twig\TwigFilter('rot13', 'str_rot13');

// or a class static method
$filter = new \Twig\TwigFilter('rot13', ['SomeClass', 'rot13Filter']);
$filter = new \Twig\TwigFilter('rot13', 'SomeClass::rot13Filter');

// or a class method
$filter = new \Twig\TwigFilter('rot13', [$this, 'rot13Filter']);
// the one below needs a runtime implementation (see below for more information)
$filter = new \Twig\TwigFilter('rot13', ['SomeClass', 'rot13Filter']);

Первый аргумент, переданный в конструктор \Twig\TwigFilter, — это имя фильтра, которое вы будете использовать в шаблонах, а второй — вызываемая PHP-функция, которую следует с ним связать.

Затем добавьте фильтр в среду Twig:

$twig = new \Twig\Environment($loader);
$twig->addFilter($filter);

И вот как его использовать в шаблоне:

{{ 'Twig'|rot13 }}

{# will output Gjvt #}

При вызове Twig вызываемая PHP-функция получает левую часть фильтра (перед символом трубы |) в качестве первого аргумента и дополнительные аргументы, переданные фильтру (в скобках ()) как дополнительные аргументы.

Например, следующий код:

{{ 'TWIG'|lower }}
{{ now|date('d/m/Y') }}

Компилируется примерно так:

<?php echo strtolower('TWIG') ?>
<?php echo twig_date_format_filter($now, 'd/m/Y') ?>

Класс \Twig\TwigFilter принимает массив опций в качестве последнего аргумента:

$filter = new \Twig\TwigFilter('rot13', 'str_rot13', $options);

Фильтры, осознающие среду

Если вы хотите получить доступ к экземпляру текущей среды в своем фильтре, установите опцию needs_environment в true; Twig передаст текущую среду в качестве первого аргумента вызова фильтра:

$filter = new \Twig\TwigFilter('rot13', function (\Twig\Environment $env, $string) {
    // get the current charset for instance
    $charset = $env->getCharset();

    return str_rot13($string);
}, ['needs_environment' => true]);

Фильтры, осознающие контекст

Если вы хотите получить доступ к текущему контексту в своем фильтре, установите опцию needs_context в true; Twig передаст текущий контекст в качестве первого аргумента вызова фильтра (или второго, если needs_environment также установлен в true):

$filter = new \Twig\TwigFilter('rot13', function ($context, $string) {
    // ...
}, ['needs_context' => true]);

$filter = new \Twig\TwigFilter('rot13', function (\Twig\Environment $env, $context, $string) {
    // ...
}, ['needs_context' => true, 'needs_environment' => true]);

Автоматическое экранирование

Если автоматическое экранирование включено, вывод фильтра может быть экранирован перед печатью. Если ваш фильтр действует как экранировщик (или явно выводит HTML или JavaScript-код), вам потребуется вывести исходный вывод. В этом случае установите опцию is_safe:

$filter = new \Twig\TwigFilter('nl2br', 'nl2br', ['is_safe' => ['html']]);

Некоторые фильтры могут нуждаться в работе с вводом, который уже экранирован или безопасен, например, при добавлении (безопасных) HTML-тегов к исходному небезопасному выводу. В этом случае установите опцию pre_escape для экранирования входных данных перед их обработкой вашим фильтром:

$filter = new \Twig\TwigFilter('somefilter', 'somefilter', ['pre_escape' => 'html', 'is_safe' => ['html']]);

Многоаргументные фильтры

Если фильтр должен принимать произвольное количество аргументов, установите опцию is_variadic в true; Twig передаст дополнительные аргументы как последний аргумент в виде массива:

$filter = new \Twig\TwigFilter('thumbnail', function ($file, array $options = []) {
    // ...
}, ['is_variadic' => true]);

Будьте осторожны, что именованные аргументы, переданные многоаргументному фильтру, не могут быть проверены на валидность, так как они автоматически окажутся в массиве опций.

Динамические фильтры

Имя фильтра, содержащее специальный символ *, является динамическим фильтром, и часть * будет соответствовать любой строке:

$filter = new \Twig\TwigFilter('*_path', function ($name, $arguments) {
    // ...
});

Следующие фильтры соответствуют вышеопределенному динамическому фильтру:

  • product_path
  • category_path

Динамический фильтр может определить более одной динамической части:

$filter = new \Twig\TwigFilter('*_path_*', function ($name, $suffix, $arguments) {
    // ...
});

Фильтр получает все значения динамической части перед обычными аргументами фильтра, но после среды и контекста. Например, вызов 'foo'|a_path_b() приведет к передаче следующих аргументов в фильтр: ('a', 'b', 'foo').

Устаревшие фильтры

Вы можете пометить фильтр как устаревший, установив опцию deprecated в true. Вы также можете указать альтернативный фильтр, который заменяет устаревший, если это имеет смысл:

$filter = new \Twig\TwigFilter('obsolete', function () {
    // ...
}, ['deprecated' => true, 'alternative' => 'new_one']);

Когда фильтр устарел, Twig выводит сообщение об устаревании при компиляции шаблона, использующего его. Для получения дополнительной информации см. Отображение сообщений об устаревании.

Функции

Функции определяются точно так же, как фильтры, но необходимо создать экземпляр \Twig\TwigFunction:

$twig = new \Twig\Environment($loader);
$function = new \Twig\TwigFunction('function_name', function () {
    // ...
});
$twig->addFunction($function);

Функции поддерживают те же функции, что и фильтры, за исключением опций pre_escape и preserves_safety.

Тесты

Тесты определяются точно так же, как фильтры и функции, но необходимо создать экземпляр \Twig\TwigTest:

$twig = new \Twig\Environment($loader);
$test = new \Twig\TwigTest('test_name', function () {
    // ...
});
$twig->addTest($test);

Тесты позволяют создавать пользовательскую логику приложения для оценки логических условий. В качестве простого примера давайте создадим Twig-тест, который проверяет, являются ли объекты «красными»:

$twig = new \Twig\Environment($loader);
$test = new \Twig\TwigTest('red', function ($value) {
    if (isset($value->color) && $value->color == 'red') {
        return true;
    }
    if (isset($value->paint) && $value->paint == 'red') {
        return true;
    }
    return false;
});
$twig->addTest($test);

Функции-тесты всегда должны возвращать true/false.

При создании тестов вы можете использовать опцию node_class для предоставления пользовательской компиляции теста. Это полезно, если ваш тест может быть скомпилирован в PHP-примитивы. Это используется многими встроенными в Twig тестами:

namespace App;

use Twig\Environment;
use Twig\Node\Expression\TestExpression;
use Twig\TwigTest;

$twig = new Environment($loader);
$test = new TwigTest(
    'odd',
    null,
    ['node_class' => OddTestExpression::class]);
$twig->addTest($test);

class OddTestExpression extends TestExpression
{
    public function compile(\Twig\Compiler $compiler)
    {
        $compiler
            ->raw('(')
            ->subcompile($this->getNode('node'))
            ->raw(' % 2 != 0')
            ->raw(')')
        ;
    }
}

Приведённый выше пример демонстрирует, как создавать тесты, использующие класс узла. Класс узла имеет доступ к одному подузлу, называемому node. Этот под узел содержит значение, которое тестируется. Когда используется фильтр odd в коде, например:

{% if my_value is odd %}

Под узел node будет содержать выражение my_value. Тесты на основе узлов также имеют доступ к узлу arguments. Этот узел будет содержать различные другие аргументы, предоставленные вашему тесту.

Новое в версии 2.6: Поддержка динамических тестов была добавлена в Twig 2.6.

Если вы хотите передать произвольное количество позиционных или именованных аргументов в тест, установите опцию is_variadic в true.

Теги

Одной из самых захватывающих особенностей движка шаблонов, такого как Twig, является возможность определения новых языковых конструкций. Это также самая сложная функция, так как вам нужно понять, как работают внутренности Twig.

В большинстве случаев тег не нужен:

  • Если ваш тег генерирует какой-то вывод, используйте функцию вместо этого.

  • Если ваш тег изменяет некоторое содержимое и возвращает его, используйте фильтр вместо этого.

    Например, если вы хотите создать тег, который преобразует текст в формате Markdown в HTML, создайте markdown фильтр вместо этого:

    {{ '**markdown** text'|markdown }}
    

    Если вы хотите использовать этот фильтр для больших объёмов текста, оберните его тегом apply:

    {% apply markdown %}
    Title
    =====
    
    Much better than creating a tag as you can **compose** filters.
    {% endapply %}
    

Примечание

Тег apply был введён в Twig 2.9; используйте тег filter с предыдущими версиями.

  • Если ваш тег ничего не выводит, а существует только из-за побочного эффекта, создайте функцию, которая ничего не возвращает, и вызовите её через тег filter.

    Например, если вы хотите создать тег, который регистрирует текст, создайте log функцию вместо этого и вызовите её через тег do:

    {% do log('Log some things') %}
    

Если вы всё ещё хотите создать тег для новой языковой конструкции, отлично!

Давайте создадим set тег, который позволяет определять простые переменные внутри шаблона. Тег можно использовать следующим образом:

{% set name = "value" %}

{{ name }}

{# should output value #}

Примечание

Тег set является частью ядра расширения и, таким образом, всегда доступен. Встроенная версия несколько мощнее и по умолчанию поддерживает несколько назначений.

Для определения нового тега требуется три шага:

  • Определение класса Token Parser (ответственного за разбор кода шаблона);
  • Определение класса Node (ответственного за преобразование обработанного кода в PHP);
  • Регистрация тега.

Регистрация нового тега

Добавьте тег, вызвав метод addTokenParser на экземпляре %%%CODE_BLOCK_83%%:

$twig = new \Twig\Environment($loader);
$twig->addTokenParser(new Project_Set_TokenParser());

Определение парсера токенов

Теперь давайте посмотрим на фактический код этого класса:

class Project_Set_TokenParser extends \Twig\TokenParser\AbstractTokenParser
{
    public function parse(\Twig\Token $token)
    {
        $parser = $this->parser;
        $stream = $parser->getStream();

        $name = $stream->expect(\Twig\Token::NAME_TYPE)->getValue();
        $stream->expect(\Twig\Token::OPERATOR_TYPE, '=');
        $value = $parser->getExpressionParser()->parseExpression();
        $stream->expect(\Twig\Token::BLOCK_END_TYPE);

        return new Project_Set_Node($name, $value, $token->getLine(), $this->getTag());
    }

    public function getTag()
    {
        return 'set';
    }
}

Метод getTag() должен возвращать тег, который мы хотим обработать, здесь set.

Метод parse() вызывается всякий раз, когда парсер встречает тег set. Он должен возвращать экземпляр \Twig\Node\Node, который представляет собой узел (вызовы Project_Set_Node создания объяснены в следующем разделе).

Процесс разбора упрощён благодаря множеству методов, которые вы можете вызвать из потока токенов ($this->parser->getStream()):

  • getCurrent(): Получает текущий токен в потоке.
  • next(): Переходит к следующему токену в потоке, но возвращает старый.
  • test($type), test($value) или test($type, $value): Определяет, является ли текущий токен определённого типа или значения (или того и другого). Значение может быть массивом нескольких возможных значений.
  • expect($type[, $value[, $message]]): Если текущий токен не является заданного типа/значения, выбрасывается ошибка синтаксиса. В противном случае, если тип и значение верны, токен возвращается, и поток переходит к следующему токену.
  • look(): Смотрит на следующий токен, не потребляя его.

Обработка выражений выполняется с помощью вызова parseExpression(), как мы сделали для тега set.

Подсказка

Чтение существующих классов TokenParser — лучший способ узнать все подробности процесса разбора.

Определение узла

Класс Project_Set_Node сам по себе довольно короткий:

class Project_Set_Node extends \Twig\Node\Node
{
    public function __construct($name, \Twig\Node\Expression\AbstractExpression $value, $line, $tag = null)
    {
        parent::__construct(['value' => $value], ['name' => $name], $line, $tag);
    }

    public function compile(\Twig\Compiler $compiler)
    {
        $compiler
            ->addDebugInfo($this)
            ->write('$context[\''.$this->getAttribute('name').'\'] = ')
            ->subcompile($this->getNode('value'))
            ->raw(";\n")
        ;
    }
}

Компилятор реализует удобный интерфейс и предоставляет методы, которые помогают разработчику генерировать красивый и читаемый PHP-код:

  • subcompile(): Компилирует узел.
  • raw(): Записывает заданную строку как есть.
  • write(): Записывает заданную строку, добавляя отступ в начале каждой строки.
  • string(): Записывает строку с кавычками.
  • repr(): Записывает PHP-представление заданного значения (см. \Twig\Node\ForNode для примера использования).
  • addDebugInfo(): Добавляет строку из исходного файла шаблона, связанную с текущим узлом, как комментарий.
  • indent(): Увеличивает отступ сгенерированного кода (см. \Twig\Node\BlockNode для примера использования).
  • outdent(): Уменьшает отступ сгенерированного кода (см. \Twig\Node\BlockNode для примера использования).

Создание расширения

Основная мотивация для написания расширения заключается в перемещении часто используемого кода в переиспользуемый класс, например, добавление поддержки интернационализации. Расширение может определять теги, фильтры, проверки, операторы, функции и посетители узлов.

В большинстве случаев полезно создать одно расширение для вашего проекта, чтобы разместить все специфичные теги и фильтры, которые вы хотите добавить в Twig.

Подсказка

При упаковке вашего кода в расширение, Twig достаточно умён, чтобы перекомпилировать ваши шаблоны всякий раз, когда вы вносите изменения в него (когда auto_reload включён).

Расширение — это класс, который реализует следующий интерфейс:

interface \Twig\Extension\ExtensionInterface
{
    /**
     * Returns the token parser instances to add to the existing list.
     *
     * @return \Twig\TokenParser\TokenParserInterface[]
     */
    public function getTokenParsers();

    /**
     * Returns the node visitor instances to add to the existing list.
     *
     * @return \Twig\NodeVisitor\NodeVisitorInterface[]
     */
    public function getNodeVisitors();

    /**
     * Returns a list of filters to add to the existing list.
     *
     * @return \Twig\TwigFilter[]
     */
    public function getFilters();

    /**
     * Returns a list of tests to add to the existing list.
     *
     * @return \Twig\TwigTest[]
     */
    public function getTests();

    /**
     * Returns a list of functions to add to the existing list.
     *
     * @return \Twig\TwigFunction[]
     */
    public function getFunctions();

    /**
     * Returns a list of operators to add to the existing list.
     *
     * @return array<array> First array of unary operators, second array of binary operators
     */
    public function getOperators();
}

Чтобы сохранить ваш класс расширения чистым и компактным, унаследуйте от встроенного класса \Twig\Extension\AbstractExtension вместо реализации интерфейса, так как он предоставляет пустые реализации для всех методов:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
}

Это расширение пока ничего не делает. Мы настроим его в следующих разделах.

Вы можете сохранить своё расширение в любом месте на файловой системе, так как все расширения должны быть зарегистрированы явно, чтобы быть доступными в ваших шаблонах.

Вы можете зарегистрировать расширение, используя метод addExtension() на вашем основном объекте %%%CODE_BLOCK_121%%:

$twig = new \Twig\Environment($loader);
$twig->addExtension(new Project_Twig_Extension());

Подсказка

Расширения ядра Twig — отличные примеры того, как работают расширения.

Переменные

Глобальные переменные могут быть зарегистрированы в расширении через метод %%%CODE_BLOCK_123%%:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension implements \Twig\Extension\GlobalsInterface
{
    public function getGlobals()
    {
        return [
            'text' => new Text(),
        ];
    }

    // ...
}

Функции

Функции могут быть зарегистрированы в расширении через метод %%%CODE_BLOCK_125%%:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getFunctions()
    {
        return [
            new \Twig\TwigFunction('lipsum', 'generate_lipsum'),
        ];
    }

    // ...
}

Фильтры

Чтобы добавить фильтр в расширение, вам нужно переопределить метод getFilters(). Этот метод должен возвращать массив фильтров для добавления в среду Twig:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getFilters()
    {
        return [
            new \Twig\TwigFilter('rot13', 'str_rot13'),
        ];
    }

    // ...
}

Теги

Добавление тега в расширение можно сделать, переопределив метод getTokenParsers(). Этот метод должен возвращать массив тегов для добавления в среду Twig:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getTokenParsers()
    {
        return [new Project_Set_TokenParser()];
    }

    // ...
}

В приведенном выше коде мы добавили один новый тег, определённый классом Project_Set_TokenParser. Класс Project_Set_TokenParser отвечает за разбор тега и компиляцию в PHP.

Операторы

Метод getOperators() позволяет добавлять новые операторы. Вот как добавить операторы !, ||, и %%%CODE_BLOCK_136%%:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getOperators()
    {
        return [
            [
                '!' => ['precedence' => 50, 'class' => \Twig\Node\Expression\Unary\NotUnary::class],
            ],
            [
                '||' => ['precedence' => 10, 'class' => \Twig\Node\Expression\Binary\OrBinary::class, 'associativity' => \Twig\ExpressionParser::OPERATOR_LEFT],
                '&&' => ['precedence' => 15, 'class' => \Twig\Node\Expression\Binary\AndBinary::class, 'associativity' => \Twig\ExpressionParser::OPERATOR_LEFT],
            ],
        ];
    }

    // ...
}

Проверки

Метод getTests() позволяет добавлять новые функции проверки:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getTests()
    {
        return [
            new \Twig\TwigTest('even', 'twig_test_even'),
        ];
    }

    // ...
}

Определение и время выполнения

Реализации фильтров, функций и проверок Twig могут быть определены как любой допустимый вызываемый PHP:

  • Функции/статические методы: Просты в реализации и быстры (используются всеми расширениями ядра Twig); но для времени выполнения сложно зависеть от внешних объектов;
  • Замыкания: Просты в реализации;
  • Методы объектов: Более гибкие и требуются, если ваш код времени выполнения зависит от внешних объектов.

Самый простой способ использовать методы — определить их в самом расширении:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    private $rot13Provider;

    public function __construct($rot13Provider)
    {
        $this->rot13Provider = $rot13Provider;
    }

    public function getFunctions()
    {
        return [
            new \Twig\TwigFunction('rot13', [$this, 'rot13']),
        ];
    }

    public function rot13($value)
    {
        return $this->rot13Provider->rot13($value);
    }
}

Это очень удобно, но не рекомендуется, так как это делает компиляцию шаблона зависимой от зависимостей времени выполнения, даже если они не нужны (например, зависимость, которая подключается к движку базы данных).

Вы можете отвязать определения расширения от их реализаций времени выполнения, зарегистрировав экземпляр \Twig\RuntimeLoader\RuntimeLoaderInterface в среде, которая знает, как создавать такие классы времени выполнения (классы времени выполнения должны быть загружаемы автоматически):

class RuntimeLoader implements \Twig\RuntimeLoader\RuntimeLoaderInterface
{
    public function load($class)
    {
        // implement the logic to create an instance of $class
        // and inject its dependencies
        // most of the time, it means using your dependency injection container
        if ('Project_Twig_RuntimeExtension' === $class) {
            return new $class(new Rot13Provider());
        } else {
            // ...
        }
    }
}

$twig->addRuntimeLoader(new RuntimeLoader());

Примечание

Twig поставляется с совместимым с PSR-11 загрузчиком времени выполнения (\Twig\RuntimeLoader\ContainerRuntimeLoader).

Теперь можно перенести логику времени выполнения в новый класс Project_Twig_RuntimeExtension и использовать его непосредственно в расширении:

class Project_Twig_RuntimeExtension
{
    private $rot13Provider;

    public function __construct($rot13Provider)
    {
        $this->rot13Provider = $rot13Provider;
    }

    public function rot13($value)
    {
        return $this->rot13Provider->rot13($value);
    }
}

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getFunctions()
    {
        return [
            new \Twig\TwigFunction('rot13', ['Project_Twig_RuntimeExtension', 'rot13']),
            // or
            new \Twig\TwigFunction('rot13', 'Project_Twig_RuntimeExtension::rot13'),
        ];
    }
}

Тестирование расширения

Функциональные тесты

Вы можете создать функциональные тесты для расширений, создав следующую структуру файлов в каталоге тестов:

Fixtures/
    filters/
        foo.test
        bar.test
    functions/
        foo.test
        bar.test
    tags/
        foo.test
        bar.test
IntegrationTest.php

Файл IntegrationTest.php должен выглядеть так:

class Project_Tests_IntegrationTest extends \Twig\Test\IntegrationTestCase
{
    public function getExtensions()
    {
        return [
            new Project_Twig_Extension1(),
            new Project_Twig_Extension2(),
        ];
    }

    public function getFixturesDir()
    {
        return __DIR__.'/Fixtures/';
    }
}

Примеры фикстур можно найти в каталоге тестов репозитория Twig tests/Twig/Fixtures.

Тесты узлов

Тестирование посетителей узлов может быть сложным, поэтому расширьте свои тестовые случаи из \Twig\Test\NodeTestCase. Примеры можно найти в каталоге тестов репозитория Twig tests/Twig/Node.

« Twig для разработчиков | Twig Internals »

© 2009–2018 by the Twig Team
Licensed under the three clause BSD license.
The Twig logo is © 2010–2020 Symfony
https://twig.symfony.com/doc/2.x/advanced.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API