这是@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
模板。
连接到这些钩子的回调必须返回一个array,其中的键是变量名。
例如,您可以将元数据作为模板数据进行传递,如下所示:
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
都为true时,该类在调试模式下工作。这意味着,如果未定义变量,则会引发异常。
当类不在调试模式下时(可能在生产中),访问未定义的变量将输出一个空字符串。
数据模型
组织数据的一种不错且可维护的方法是使用模型类。
它们可以是非常简单的类,可以使用上述相同的过滤器返回数据。没有需要遵循的特定界面,可以根据您的喜好进行组织。
下面,仅举一个例子,但是您可以自由地以自己的方式做。
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>
页面的部分中。
部分
WordPress具有类似get_header()
或的功能get_template_part()
,可用于将部分内容加载到主模板中。
这些功能与所有其他WordPress功能一样,可以在使用Engine
类时在模板中使用。
唯一的问题是,使用WordPress核心功能加载的局部文件无法使用使用来获取自定义模板数据的高级功能$this
。
因此,Engine
该类具有一种方法partial()
(允许完全以子主题兼容的方式加载部分内容),并且仍然能够在部分内容中使用自定义模板数据。
用法很简单。
假设有一个名为“ partials/content.php
主题(或子主题)”文件夹的文件,则可以使用以下文件将其包括在内:
<?php $this->partial('partials/content') ?>
在该部分内,可以相同的方式访问所有父主题数据。
与WordPress函数不同,Engine::partial()
method允许将特定数据传递给局部函数,只需将数据数组作为第二个参数传递即可。
<?php $this->partial('partials/content', array('greeting' => 'Welcome!')) ?>
默认情况下,局部函数可以访问父主题中可用的数据以及所传递的数据显式性。
如果显式传递给partial的某些变量与父主题变量具有相同的名称,则显式传递的变量将获胜。
但是,也可以在隔离模式下包含部分内容,即该部分内容无法访问父主题数据。为此,只需将true
第三个参数传递给partial()
:
<?php $this->partial('partials/content', array('greeting' => 'Welcome!'), true) ?>
结论
即使很简单,Engine
类也很完整,但是肯定可以进一步改进。例如,无法检查是否定义了变量。
由于它与WordPress功能和模板层次结构具有100%的兼容性,因此您可以毫无问题地将其与现有和第三方代码集成。
但是,请注意,这只是部分测试,因此可能存在我尚未发现的问题。
“我们获得了什么?”下的五点 在@kaiser 答案中:
- 无需更改数据结构即可轻松交换模板
- 容易阅读的tempaltes
- 避免全球范围
- 可以单元测试
- 可以交换模型/数据而不会损害其他组件
都对我的课程有效。