DevGang
Авторизоваться

Twig

Русскоязычная документация по Twig - PHP шаблонизатору. Руководство по Твиг на русском языке

Создание условной разметки

Работа с Ajax означает, что одно и то же содержание иногда выводится на экран как есть и иногда снабжается разметкой. Поскольку названия шаблонов разметки Twig могут быть любым допустимым выражением, вы можете передавать переменную, которая принимает значение true, когда запрос сделан через Ajax и вы можете выбрать, соответственно, разметку:

{% extends request.ajax ? "base_ajax.html" : "base.html" %}

{% block content %}
    This is the content to be displayed.
{% endblock %}

Создание динамических включений

При включении шаблона не нужно чтобы его название было строкой. Например, название может зависеть от значения переменной:

{% include var ~ '_foo.html' %}

Если var принимает значение index, то шаблон index_foo.html будет переведен. По сути дела, название шаблона может быть любым допустимым выражением, таким как следующее:

{% include var|default('index') ~ '_foo.html' %}

Замещения шаблона, который является саморасширяющимся

Шаблон может быть настроен двумя разными способами:

  • Inheritance (наследование): Шаблон расширяет родительский шаблон и переопределяет несколько блоков.
  • Replacement (замещение): Если вы используете загрузчик файловой системы, Twig загружает первый шаблон, который он находит в списке сконфигурированных каталогов; шаблон, найденный в каталоге, замещает другой из каталога, идущий далее по списку.

Но как вы скомбинируете оба: замену шаблона, который также расширяется (как шаблон в каталоге следующий в списке)? Давайте предположим, что ваши шаблоны загружены из .../templates/mysite и .../templates/default в этом порядке. Шаблон page.twig, сохраняемый в .../templates/default гласит:

{# page.twig #}
{% extends "layout.twig" %}

{% block content %}
{% endblock %}

Вы можете заменить этот шаблон, вставив файл с таким же названием в .../templates/mysite. Если вы хотите расширить исходный шаблон, вы возможно хотели написать следующее:

{# page.twig in .../templates/mysite #}
{% extends "page.twig" %} {# from .../templates/default #}

Конечно, это не сработает, поскольку Twig будет всегда загружать шаблон из .../templates/mysite.

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

{# page.twig in .../templates/mysite #}
{% extends "default/page.twig" %} {# from .../templates #}

Этот способ был вдохновлен следующей страницей Django wiki page: http://code.djangoproject.com/wiki/ExtendingTemplates

Настройка синтаксиса

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

Чтобы изменить разделители блока, вам нужно создать свой собственный объект лексического анализатора:

$twig = new Twig_Environment();

$lexer = new Twig_Lexer($twig, array(
    'tag_comment'   => array('{#', '#}'),
    'tag_block'     => array('{%', '%}'),
    'tag_variable'  => array('{{', '}}'),
    'interpolation' => array(', '),
));
$twig->setLexer($lexer);

Вот пример конфигурации, который воспроизводит синтаксис некоторых других шаблонных механизмов:

// Ruby erb syntax
$lexer = new Twig_Lexer($twig, array(
    'tag_comment'  => array('<%#', '%>'),
    'tag_block'    => array('<%', '%>'),
    'tag_variable' => array('<%=', '%>'),
));

// SGML Comment Syntax
$lexer = new Twig_Lexer($twig, array(
    'tag_comment'  => array('<!--#', '-->'),
    'tag_block'    => array('<!--', '-->'),
    'tag_variable' => array('${', '}'),
));

// Smarty like
$lexer = new Twig_Lexer($twig, array(
    'tag_comment'  => array('{*', '*}'),
    'tag_block'    => array('{', '}'),
    'tag_variable' => array('{$', '}'),
));

Использование свойств динамического объекта

Когда Twig обнаруживает переменную article.title, он пытается найти общедоступное свойство title в объекте article.

Это также работает, если свойство не существует, но довольно таки определяется динамически, благодаря магическому методу __get(); вам просто нужно также применить магический метод __isset(), как показано на следующем отрывке кода:

class Article
{
    public function __get($name)
    {
        if ('title' == $name) {
            return 'The title';
        }

        // throw some kind of error
    }

    public function __isset($name)
    {
        if ('title' == $name) {
            return true;
        }

        return false;
    }
}

Получения доступа к родительскому контексту во вложенных циклах

Иногда, когда используют вложенные циклы, необходимо получить доступ к родительскому контексту. Родительский контекст всегда доступен через переменную loop.parent. Например, если вы имеете следующие данные шаблона:

$data = array(
    'topics' => array(
        'topic1' => array('Message 1 of topic 1', 'Message 2 of topic 1'),
        'topic2' => array('Message 1 of topic 2', 'Message 2 of topic 2'),
    ),
);

И следующий шаблон, чтобы изобразить на экране все сообщения по всем темам:

{% for topic, messages in topics %}
    * {{ loop.index }}: {{ topic }}
  {% for message in messages %}
      - {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }}
  {% endfor %}
{% endfor %}

Результат будет схож с:

* 1: topic1
  - 1.1: The message 1 of topic 1
  - 1.2: The message 2 of topic 1
* 2: topic2
  - 2.1: The message 1 of topic 2
  - 2.2: The message 2 of topic 2

Во внутреннем цикле переменная используется loop.parent для того, чтобы получить доступ к внешнему контексту. Таким образом, индекс текущего topic, определенный во внешнем для цикла, доступен через переменную loop.parent.loop.index.

Определение неопределенных функций и фильтров на лету

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

Для фильтров обратные вызовы регистра с registerUndefinedFilterCallback(). Для функций используйте registerUndefinedFunctionCallback():

// auto-register all native PHP functions as Twig functions
// don't try this at home as it's not secure at all!
$twig->registerUndefinedFunctionCallback(function ($name) {
    if (function_exists($name)) {
        return new Twig_Function_Function($name);
    }

    return false;
});

Если вызываемый не способен вернуть допустимую функцию (или фильтр), он должен вернуть false.

Если вы регистрируете более чем один обратный вызов, Twig будет вызывать их по очереди пока он не вернет false.

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

Проверка шаблона на валидность

Когда код шаблона поддерживается третьим лицом (например, через веб- интерфейс), может быть интересным проверить синтаксис шаблона перед тем, как сохранить его. Если код шаблона хранится в переменной $template, вот как вы можете сделать это:

try {
    $twig->parse($twig->tokenize($template));

    // the $template is valid
} catch (Twig_Error_Syntax $e) {
    // $template contains one or more syntax errors
}

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

foreach ($files as $file) {
    try {
        $twig->parse($twig->tokenize($template, $file));

        // the $template is valid
    } catch (Twig_Error_Syntax $e) {
        // $template contains one or more syntax errors
    }
}

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

Обновление измененных шаблонов когда APC включена и apc.stat = 0

При использовании АРС с apc.stat , установленном на 0, и включенном кэше Twig , очистка кэша не будет обновлять кэш АРС. Чтобы обойти эту проблему, можно расширить Twig_Environment, и вызвать обновление кэша АРС, когда кэш Twig переписывает кэш:

class Twig_Environment_APC extends Twig_Environment
{
    protected function writeCacheFile($file, $content)
    {
        parent::writeCacheFile($file, $content);

        // Compile cached file into bytecode cache
        apc_compile_file($file);
    }
}

Повторное использование с сохранением состояния Node Visitor

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

Этого можно легко достигнуть с помощью следующего кода:

protected $someTemplateState = array();

public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
{
    if ($node instanceof Twig_Node_Module) {
        // reset the state as we are entering a new template
        $this->someTemplateState = array();
    }

    // ...

    return $node;
}

Использование именованного шаблона для установки дефолтного экранирования

Данный функционал требует Twig версии 1.8 или выше.

Опция autoescape определяет использовании стратегии сохранения по умолчанию, когда никакого сохранения не применяется для переменной. Когда Twig используется, чтобы главным образом генерировать файлы HTML, вы можете установить его на html и явно поменять его на js, когда вы имеете несколько динамических файлов JavaScript благодаря тегу autoescape:

{% autoescape 'js' %}
    ... some JS ...
{% endautoescape %}

Но если вы имеете много файлов HTML и JS, и если ваши имена шаблонов следуют некоторым соглашениям, вы вместо этого можете определить стратегию сохранения по умолчанию, основанную на имени шаблона. Скажем, что имена ваших шаблонов всегда оканчиваются на .html для файлов HTML, а .js для таковых JavaScript и .css для таблиц стилей, то вот как вы можете конфигурировать Twig:

class TwigEscapingGuesser
{
    function guess($filename)
    {
        // get the format
        $format = substr($filename, strrpos($filename, '.') + 1);

        switch ($format) {
            case 'js':
                return 'js';
            case 'css':
                return 'css';
            case 'html':
            default:
                return 'html';
        }
    }
}

$loader = new Twig_Loader_Filesystem('/path/to/templates');
$twig = new Twig_Environment($loader, array(
    'autoescape' => array(new TwigEscapingGuesser(), 'guess'),
));

Эта динамическая стратегия не приводит к накладным расходам во время выполнения, поскольку автосохранение происходит во время компиляции.

Хранение шаблонов в базе данных

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

Для начала, давайте создадим временную базу данных в памяти SQLite3 чтобы в дальнейшем с ней работать:

$dbh = new PDO('sqlite::memory:');
$dbh->exec('CREATE TABLE templates (name STRING, source STRING, last_modified INTEGER)');
$base = '{% block content %}{% endblock %}';
$index = '
{% extends "base.twig" %}
{% block content %}Hello {{ name }}{% endblock %}
';
$now = time();
$dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('base.twig', '$base', $now)");
$dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('index.twig', '$index', $now)");

Мы создали простую таблицу templates, которая размещает два шаблона: base.twig и index.twig.

Теперь давайте определим загрузчик, способный использовать эту базу данных:

class DatabaseTwigLoader implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
{
    protected $dbh;

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

    public function getSource($name)
    {
        if (false === $source = $this->getValue('source', $name)) {
            throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
        }

        return $source;
    }

    // Twig_ExistsLoaderInterface as of Twig 1.11
    public function exists($name)
    {
        return $name === $this->getValue('name', $name);
    }

    public function getCacheKey($name)
    {
        return $name;
    }

    public function isFresh($name, $time)
    {
        if (false === $lastModified = $this->getValue('last_modified', $name)) {
            return false;
        }

        return $lastModified <= $time;
    }

    protected function getValue($column, $name)
    {
        $sth = $this->dbh->prepare('SELECT '.$column.' FROM templates WHERE name = :name');
        $sth->execute(array(':name' => (string) $name));

        return $sth->fetchColumn();
    }
}

И, наконец, вот пример того, как вы можете ее использовать:

$loader = new DatabaseTwigLoader($dbh);
$twig = new Twig_Environment($loader);

echo $twig->render('index.twig', array('name' => 'Fabien'));

Использование различных частей шаблона

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

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

$loader1 = new DatabaseTwigLoader($dbh);
$loader2 = new Twig_Loader_Array(array(
    'base.twig' => '{% block content %}{% endblock %}',
));
$loader = new Twig_Loader_Chain(array($loader1, $loader2));

$twig = new Twig_Environment($loader);

echo $twig->render('index.twig', array('name' => 'Fabien'));

Теперь, когда шаблон base.twig определен в загрузчике массива, вы можете удалить его из базы данных, а все остальное будет работать как раньше.

Комментарии
Чтобы оставить комментарий, необходимо авторизоваться