Это альтернативный подход к ответу @kaiser , который я нашел довольно хорошим (+1 от меня), но требует дополнительной работы для использования с основными функциями WP, и он сам по себе интегрирован с иерархией шаблонов.
Подход, которым я хочу поделиться, основан на одном классе (это урезанная версия из того, над чем я работаю), который заботится о визуализации данных для шаблонов.
У него есть (IMO) интересные особенности:
- шаблоны являются стандартными файлами шаблонов WordPress (single.php, page.php), они получают немного больше мощности
- существующие шаблоны просто работают, так что вы можете легко интегрировать шаблон из существующих тем
- В отличие от подхода @kaiser , в шаблонах вы получаете доступ к переменным с помощью
$this
ключевого слова: это дает вам возможность избежать уведомлений в производстве в случае неопределенных переменных
Engine
Класс
namespace GM\Template;
class Engine
{
private $data;
private $template;
private $debug = false;
/**
* Bootstrap rendering process. Should be called on 'template_redirect'.
*/
public static function init()
{
add_filter('template_include', new static(), 99, 1);
}
/**
* Constructor. Sets debug properties.
*/
public function __construct()
{
$this->debug =
(! defined('WP_DEBUG') || WP_DEBUG)
&& (! defined('WP_DEBUG_DISPLAY') || WP_DEBUG_DISPLAY);
}
/**
* Render a template.
* Data is set via filters (for main template) or passed to method for partials.
* @param string $template template file path
* @param array $data template data
* @param bool $partial is the template a partial?
* @return mixed|void
*/
public function __invoke($template, array $data = array(), $partial = false)
{
if ($partial || $template) {
$this->data = $partial
? $data
: $this->provide(substr(basename($template), 0, -4));
require $template;
$partial or exit;
}
return $template;
}
/**
* Render a partial.
* Partial-specific data can be passed to method.
* @param string $template template file path
* @param array $data template data
* @param bool $isolated when true partial has no access on parent template context
*/
public function partial($partial, array $data = array(), $isolated = false)
{
do_action("get_template_part_{$partial}", $partial, null);
$file = locate_template("{$partial}.php");
if ($file) {
$class = __CLASS__;
$template = new $class();
$template_data = $isolated ? $data : array_merge($this->data, $data);
$template($file, $template_data, true);
} elseif ($this->debug) {
throw new \RuntimeException("{$partial} is not a valid partial.");
}
}
/**
* Used in templates to access data.
* @param string $name
* @return string
*/
public function __get($name)
{
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
if ($this->debug) {
throw new \RuntimeException("{$name} is undefined.");
}
return '';
}
/**
* Provide data to templates using two filters hooks:
* one generic and another query type specific.
* @param string $type Template file name (without extension, e.g. "single")
* @return array
*/
private function provide($type)
{
$generic = apply_filters('gm_template_data', array(), $type);
$specific = apply_filters("gm_template_data_{$type}", array());
return array_merge(
is_array($generic) ? $generic : array(),
is_array($specific) ? $specific : array()
);
}
}
(Доступно здесь как Gist .)
Как пользоваться
Единственное, что нужно, это вызвать Engine::init()
метод, вероятно, на 'template_redirect'
крючке. Это можно сделать в теме functions.php
или из плагина.
require_once '/path/to/the/file/Engine.php';
add_action('template_redirect', array('GM\Template\Engine', 'init'), 99);
Это все.
Ваши существующие шаблоны будут работать как ожидается. Но теперь у вас есть возможность доступа к данным пользовательских шаблонов.
Данные пользовательских шаблонов
Для передачи пользовательских данных в шаблоны есть два фильтра:
'gm_template_data'
'gm_template_data_{$type}'
Первый запускается для всех шаблонов, второй специфичен для шаблона, фактически, димамическая часть {$type}
- это базовое имя файла шаблона без расширения файла.
Например, фильтр 'gm_template_data_single'
может использоваться для передачи данныхsingle.php
шаблон.
Обратные вызовы, прикрепленные к этим хукам, должны возвращать массив , где ключами являются имена переменных.
Например, вы можете передавать метаданные как данные шаблона так:
add_filter('gm_template_data', function($data) {
if (is_singular()) {
$id = get_queried_object_id();
$data['extra_title'] = get_post_meta($id, "_theme_extra_title", true);
}
return $data;
};
И тогда внутри шаблона вы можете просто использовать:
<?= $this->extra_title ?>
Режим отладки
Когда обе константы WP_DEBUG
и WP_DEBUG_DISPLAY
верны, класс работает в режиме отладки. Это означает, что если переменная не определена, генерируется исключение.
Когда класс не находится в режиме отладки (возможно, в рабочей среде), доступ к неопределенной переменной выведет пустую строку.
Модели данных
Хороший и удобный способ организовать ваши данные - использовать классы моделей.
Это могут быть очень простые классы, которые возвращают данные, используя те же фильтры, что описаны выше. Нет конкретного интерфейса для подражания, они могут быть организованы в соответствии с вашими предпочтениями.
Ниже приведен только пример, но вы можете делать это по-своему.
class SeoModel
{
public function __invoke(array $data, $type = '')
{
switch ($type) {
case 'front-page':
case 'home':
$data['seo_title'] = 'Welcome to my site';
break;
default:
$data['seo_title'] = wp_title(' - ', false, 'right');
break;
}
return $data;
}
}
add_filter('gm_template_data', new SeoModel(), 10, 2);
__invoke()
Метод (который работает , когда класс используется как обратный вызов) возвращает строку , которые будут использоваться для <title>
тега шаблона.
Благодаря тому, что второй передаваемый аргумент 'gm_template_data'
является именем шаблона, метод возвращает пользовательский заголовок для домашней страницы.
Имея код выше, можно использовать что-то вроде
<title><?= $this->seo_title ?></title>
в <head>
разделе страницы.
Partials
В WordPress есть функции, подобные get_header()
или get_template_part()
которые можно использовать для загрузки партиалов в основной шаблон.
Эти функции, как и все другие функции WordPress, могут использоваться в шаблонах при использовании Engine
класса.
Единственная проблема заключается в том, что внутри партиалов, загруженных с использованием основных функций WordPress, невозможно использовать расширенную функцию получения пользовательских шаблонных данных $this
.
По этой причине в Engine
классе есть метод, partial()
который позволяет загружать частичные (полностью совместимые с дочерними темами) и по-прежнему иметь возможность использовать частичные данные пользовательских шаблонов.
Использование довольно просто.
Предполагая, что файл находится в папке partials/content.php
theme (или child theme), его можно включить с помощью:
<?php $this->partial('partials/content') ?>
Внутри этой части будет возможен доступ ко всем данным родительской темы таким же образом.
В отличие от функций WordPress, Engine::partial()
метод позволяет передавать определенные данные частям, просто передавая массив данных в качестве второго аргумента.
<?php $this->partial('partials/content', array('greeting' => 'Welcome!')) ?>
По умолчанию у партиалов есть доступ к данным, доступным в родительской теме, и к передаваемым данным.
Если некоторая переменная, явно переданная частичному, имеет то же имя родительской переменной темы, то переменная, явно переданная, побеждает.
Однако также возможно включить частичное в изолированном режиме, то есть частичное не имеет доступа к данным родительской темы. Для этого просто передайте в true
качестве третьего аргумента partial()
:
<?php $this->partial('partials/content', array('greeting' => 'Welcome!'), true) ?>
Вывод
Даже если все довольно просто, Engine
класс довольно завершен, но, безусловно, может быть улучшен. Например, нет способа проверить, определена ли переменная или нет.
Благодаря 100% совместимости с функциями WordPress и иерархией шаблонов вы можете без проблем интегрировать его с существующим и сторонним кодом.
Тем не менее, обратите внимание, что это только частично протестировано, поэтому возможно, есть проблемы, которые я еще не обнаружил.
Пять баллов в разделе «Что мы получили?» в ответе @kaiser :
- Простой обмен шаблонами без изменения структуры данных
- Легко читать темплат
- Избегайте глобального охвата
- Может юнит-тест
- Может обмениваться моделью / данными без ущерба для других компонентов
все действительны для моего класса, а также.