Как подружить WordPress и Twig
Twig — компилирующий обработчик шаблонов с открытым исходным кодом, написанный на языке программирования PHP
Что такое шаблонизатор?
Шаблонизатор (в web) — программное обеспечение, позволяющее использовать html-шаблоны для генерации конечных html-страниц. Основная цель использования шаблонизаторов — это отделение представления данных от исполняемого кода. Часто это необходимо для обеспечения возможности параллельной работы программиста и дизайнера-верстальщика. Использование шаблонизаторов часто улучшает читаемость кода и внесение изменений во внешний вид, когда проект целиком выполняет один человек.
Wikipedia
Самая важная функция шаблонизатора это именно отделение представления данных от исполняемого кода. Именно этого в темах WordPress не хватает совсем. В файлах шаблонов страниц можно добавить какой угодно код и это сильно соблазняет написать говнокод.
Кроме всего прочего большинство шаблонизаторов сокращают синтаксис кода.
Шаблонизаторы для PHP
На момент написания статьи самые популярные шаблонизаторы для php были:
- Twig (Symfony)
- Blade (Laravel)
- Smarty (просто старый :))
Все они достаточно похожи между собой. Вы можете выбрать тот, который вам нравится больше. Мой выбор пал на Twig.
Установка и подключение twig
В теме устанавливаем twig через composer:
composer require "twig/twig:^2.0"
Затем в functions.php где-то вначале добавляем
require_once get_template_directory() . '/vendor/autoload.php';
Использование twig-шаблонов
Для удобства я сделал абстактный класс, который дает возможность работать с twig-шаблонами. Данный в конструкторе описывает как должен работать twig и в каких директориях искать шаблоны. Также он имеет единственный публичный метод render, который и подключает сам шаблон.
<?php use Twig\Environment; use Twig\Error\LoaderError; use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; use Twig\Loader\FilesystemLoader; use Twig\TwigFunction; abstract class Twig_Controller { protected $environment; public function __construct( array $directories ) { $loader = new FilesystemLoader( $directories ); $this->environment = new Environment( $loader, [ 'debug' => true ] ); if ( current_user_can( 'administrator' ) ) { $this->twig->addExtension( new DebugExtension() ); } } public function render( string $template, array $params ) { echo $this->environment->render( $template . '.twig', $params ); } }
Теперь для работы с шаблонами нам нужно просто наследовать этот Twig_Controller.
Заменить php-шаблоны на twig-шаблоны
Мы можем для этого использовать два хука template_redirect или template_include. Первый отрабатывает раньше и затем появляются данные в global $wp_query, $post и т.д. Попробуем переопределить шаблоны для всех страниц:
class Controller extends Twig_Controller { public function __construct() { parent::__construct( [ get_template_directory() . '/views/' ] ); add_filter( 'template_include', [ $this, 'template_include' ] ); } public function template_include( string $template ) { if ( is_page() ) { global $post; $this->render( 'page', [ 'post' => $post, ] ); } } }
Теперь вместо page.php будет подключаться page.twig. Как поменялся синтаксис?
Было:
<?php get_header() ?> <div id="primary" class="content-area"> <main id="main" class="site-main"> <div class="entry-header"> <h1><?php the_title() ?></h1> <div class="corner__bottom corner--double"></div> </div><!-- .entry-header --> <article id="post-{{ post.ID }}" <?php post_class( $class ) ?>> <div class="entry-content"> <?php the_content() ?> </div><!-- .entry-content --> </article><!-- #post-<?php the_ID() ?> --> </main> </div> <?php get_footer() ?>
Стало:
{% include 'header.twig' %} <div id="primary" class="content-area"> <main id="main" class="site-main"> <div class="entry-header"> <h1>{{ post.post_title }}</h1> </div><!-- .entry-header --> <article id="post-{{ post.ID }}" {% do action('mx_post_class') %}> <div class="entry-content"> {% do filter('the_content', post.post_content ) %} </div><!-- .entry-content --> </article><!-- #post-{{ post.ID }} --> </main> </div> {% include 'footer.twig' %}
Особо ничего не изменилось, но теперь данные во вьюшку могут попасть только через наш контроллер. Никаких лишних данных там не будет и соответсвенно основная задача шаблонизаторов выполняется.
Все нужные данные на фронте можно добавить в template_include. Т.к. наш контроллер мы добавили на хук template_include у нас уже есть global $wp_query, $post и остальные «прелести» WordPress’а.
Расширяем Twig под WordPress
Все же для полноценной работы не хватает хуков. Для того чтобы их добавить нужно в twig зарегистрировать необходимые функции.
В Twig_Controller создаем метод register_functions и вызываем его в конце конструктора:
public function register_functions() { $this->twig->addFunction( new TwigFunction( 'action', function ( $context ) { $args = func_get_args(); array_shift( $args ); $args[] = $context; call_user_func_array( 'do_action', $args ); }, array( 'needs_context' => true ) ) ); $this->twig->addFunction( new TwigFunction( 'filter', function ( $context ) { $args = func_get_args(); array_shift( $args ); $args[] = $context; echo call_user_func_array( 'apply_filters', $args ); }, array( 'needs_context' => true ) ) ); }
Конфликт версий
Будьте готовы к тому, что версия 1.x.x не совместима с версией 2.x.x. Решить проблему можно путем добавления неймспейсов в twig-шаблон и вынесением библиотеки из vendor’a. Неймспейсы можно добавить путем утилы: https://github.com/OnTheGoSystems/twig-scoper.