Bun Wong's Blog

专注于 Web 应用程序开发

Drupal 渲染注册流程剖析

昨天晚上遇到一个很纠结的问题,是关于 Drupal Theme 的 Template 文件路径问题。原则上只要 Theme 主题目录下存在同名的 tpl.php 的话,Drupal 将不再使用 Module 目录下的默认模板,出现的问题则不同,Drupal 找不到 Theme 目录下的文件而总是用原默认模板。问题代码如下

function taxonpage_theme()
{
  return array(
    'taxonpage_element' => array(
      'arguments' => array('content' => ''),
      'template' => 'taxonpage',
    ),
  );
}

渲染 taxonpage_element 后,Drupal 主题引擎会找 Module 目录下的 taxonpage.tpl.php,而在 garland 主题下实现了新的同名模板文件 taxonpage.tpl.php 并更新缓存后,新的模板文件并无起作用,Devel 拦截发现装载的依然是原路径上的模板文件。

借此机会,下午细看了 Drupal 的 theme.inc,问题关键定位在 theme registry 的结果,在 _theme_build_registry 函数内,程序按照四个步骤顺序生成 registry,分别是 module 级 (就是 Module 内实现的 [moduleName]_theme),base_theme 级 (父类主题,与本文问题无关,不讨论),theme_engine 级 (模板引擎,本文仅讨论基于 Drupal 的 phptemplate 模板引擎) 和 theme 级 (就是 Theme 内实现的 [themeName]_theme,与本文问题无关,不讨论)。

module 级步骤根据 [moduleName]_theme 的定义,找相应的渲染行为,若声明了 'function' 则用指定函数渲染,若声明了 'template' 则用指定模板文件渲染,缺省则调用 theme_hook 函数渲染。本例中声明为

'template' => 'taxonpage',

因此 registry 结果中 'theme path' 为 Module 所在路径,'template' 为 taxonpage。

theme_engine 级步骤则根据 phptemplate_theme 方法来修正由 module 级产生的 registry。若渲染行为声明为 'function' 时,系统会检查是否实现了 phptemplate_hook (如果是其它非 phptemplate 模板引擎,函数名为 [engineName]_hook) 和 [themeName]_hook 函数,优先级为 [themeName]_hook > [engineName]_hook > theme_hook;若声明为 'template' 时,系统会检查在当前主题下是否能找到指定的模板文件,详细可以阅读 theme.inc 内的 drupal_find_theme_templates 方法,该方法会找出所有主题根目录下的 tpl.php 文件,然后检查文件名是否 registry 里已存在的 hook,如果匹配则修正 registry 的结果,下面是关键代码

if (($pos = strpos($template, '.')) !== FALSE) {
  $template = substr($template, 0, $pos); // 截取去除 .tpl.php 后缀
}
$hook = strtr($template, '-', '_'); // 用文件名作 hook
if (isset($cache[$hook])) {
  // registry 里找 hook,问题在此出现,因本例中 hook template 名不相同
  $templates[$hook] = array(
    'template' => $template,
    'path' => dirname($file->filename),
  );
}

显然,由于本例中 hook 名为 taxonpage_element,而在主题目录中找到的 template 名为 taxonpage,因此 registry 并无修改结果。由于 Drupal 使用 hook 作为 registry 的索引,通过 template 名找 hook 需要遍历整个 registry,将会造成非常严重的性能损耗,且 template 跟 hook 也很可能是一对多的关系,因此 Drupal 这个做法也是无奈之举,这也是使我跟同事纠结很久的原因。

解决办法很简单,把 'template' 设置成 hook 同名,或主题目录下的模板文件使用 [hook].tpl.php。

杯具啊~~~~~~

看完本文应该会对 Drupal 的 theme 流程有更深入了解,下图是 Drupal Theme Flow

000002

支持

鸡蛋

路过

雷人

我晕

好帅

留言 (3)

  • haturn

    January 12th 2010 • 11:39

    最近好忙,感觉好久没有来了,今天真幸运,可以坐坐沙发啦

  • drupaler

    February 21st 2010 • 13:50

    不是很明白,还望楼主解释一下:
    通常我们命名主题的时候,的确采用taxonpage_theme这样的形式,但是楼主又说: 而在 garland 主题下实现了新的同名模板文件 taxonpage.tpl.php 并更新缓存后,新的模板文件并无起作用,Devel 拦截发现装载的依然是原路径上的模板文件。
    我不明白 这个主题到底在什么地方? 如果就是在garland下,我以为,应该garland_theme下定义你的HOOK 才对吧?也就是说 模板下应该是 garland_theme而不是楼主定义的 taxonpage_theme 。
    还希望大家继续探讨。

  • Bun Wong

    February 21st 2010 • 21:20

    @drupaler

    我的意思是,Drupal 有一个机制,可以在 Theme 目录下实现一个同名的 tpl 文件来重载由 Module 实现的 template,这个机制不关乎 template.php 的设置,是 theme.inc 内实现的。

发表留言

Google Analytics

最近一个月访问数:342

Ads