drupal_render()只是对theme()的调用做了包装,真正做任务的还是theme()。
function theme($hook, $variables = array()) {
... ...
}
theme()的开头检查了module_load_all()是否有执行。theme()只能在所有模块装入后才能执行。
// If called before all modules are loaded, we do not necessarily have a full
// theme registry to work with, and therefore cannot process the theme
// request properly. See also _theme_load_registry().
if (!module_load_all(NULL) && !defined(‘MAINTENANCE_MODE‘)) {
throw new Exception(t(‘theme() may not be called until all modules are loaded.‘));
}
theme_get_registry()返回所有的theme hooks。
$hooks = theme_get_registry(FALSE);
参数$hook可以是一个数组,包含所有可用的备选theme hook。theme()会取第一个存在的theme hook。
// If an array of hook candidates were passed, use the first one that has an
// implementation.
if (is_array($hook)) {
foreach ($hook as $candidate) {
if (isset($hooks[$candidate])) {
break;
}
}
$hook = $candidate;
}
处理theme hook suggestion。什么是theme hook
suggestion?以请求q=node/sports/5为例,可以优先匹配node__sports__5,再匹配node__sports,最后再匹配node。如果存在node__sports__5的theme
hook,则后面的就不予考虑,后面的以此类推。注意theme hook suggestion是以双下划线分开的。
// If there‘s no implementation, check for more generic fallbacks. If there‘s
// still no implementation, log an error and return an empty string.
if (!isset($hooks[$hook])) {
// Iteratively strip everything after the last ‘__‘ delimiter, until an
// implementation is found.
while ($pos = strrpos($hook, ‘__‘)) {
$hook = substr($hook, 0, $pos);
if (isset($hooks[$hook])) {
break;
}
}
if (!isset($hooks[$hook])) {
// Only log a message when not trying theme suggestions ($hook being an
// array).
// 只有$hook是数组的时候才记录日志信息
if (!isset($candidate)) {
watchdog(‘theme‘, ‘Theme hook %hook not found.‘, array(‘%hook‘ => $hook), WATCHDOG_WARNING);
}
return ‘‘;
}
}
$info保存当前hook信息。$theme_path是一个全局变量,在theme()执行过程中会替换为当前theme
hook对应的路径,theme()后面会还原回来。
$info = $hooks[$hook];
global $theme_path;
$temp = $theme_path;
// point path_to_theme() to the currently used theme path:
$theme_path = $info[‘theme path‘];
引入$info[‘includes‘]相关文件。
// Include a file if the theme function or variable processor is held
// elsewhere.
if (!empty($info[‘includes‘])) {
foreach ($info[‘includes‘] as $include_file) {
include_once DRUPAL_ROOT . ‘/‘ . $include_file;
}
}
重新整理$variables数组。
// If a renderable array is passed as $variables, then set $variables to
// the arguments expected by the theme function.
if (isset($variables[‘#theme‘]) || isset($variables[‘#theme_wrappers‘])) {
$element = $variables;
$variables = array();
if (isset($info[‘variables‘])) {
foreach (array_keys($info[‘variables‘]) as $name) {
if (isset($element["#$name"])) {
$variables[$name] = $element["#$name"];
}
}
}
else {
$variables[$info[‘render element‘]] = $element;
}
}// Merge in argument defaults.
if (!empty($info[‘variables‘])) {
$variables += $info[‘variables‘];
}
elseif (!empty($info[‘render element‘])) {
$variables += array($info[‘render element‘] => array());
}
定义theme hook时,允许定义theme hook执行时必需的变量和默认值。可以使用render
element定义单个变量,也可以用variables定义多个变量。用render element定义单个变量的例子:
function practice_theme() {
return array(
‘flash_messages‘ => array(
‘render element‘ => ‘messages‘,
‘template‘ => ‘flash_messages‘,
),
);
}// 整理后的$variables大致是这样:
$variables = array(
‘messages‘ => $element,
);
用variables定义多个变量的例子:
function practice_theme() {
return array(
‘flash_messages‘ => array(
‘variables‘ => array(
‘foo‘ => array(),
‘bar‘ => array()
),
‘template‘ => ‘flash_messages‘,
),
);
}// 整理后的$variables大致是这样:
$variables = array(
‘foo‘ => isset($element[‘foo‘]) ? $element[‘foo‘] : array(),
‘bar‘ => isset($element[‘bar‘]) ? $element[‘bar‘] : array(),
);
如果$info[‘base hook‘]不为空,则后面调用的preprocess functions和process
functions要是base hook的,不能是当前$hook的。但还是要保证theme_hook_suggestion是当前$hook。
// If the hook is a suggestion of a base hook, invoke the variable processors of
// the base hook, but retain the suggestion as a high priority suggestion to
// be used unless overridden by a variable processor function.
if (isset($info[‘base hook‘])) {
$base_hook = $info[‘base hook‘];
$base_hook_info = $hooks[$base_hook];
// Include files required by the base hook, since its variable processors
// might reside there.
if (!empty($base_hook_info[‘includes‘])) {
foreach ($base_hook_info[‘includes‘] as $include_file) {
include_once DRUPAL_ROOT . ‘/‘ . $include_file;
}
}
if (isset($base_hook_info[‘preprocess functions‘]) || isset($base_hook_info[‘process functions‘])) {
$variables[‘theme_hook_suggestion‘] = $hook;
$hook = $base_hook;
$info = $base_hook_info;
}
}
调用preprocess functions和process
functions。这两类函数有两个目的,一是处理$variables,二是处理theme hook
suggestion。theme_hook_suggestion的优先级大于theme_hook_suggestions(注意是suggestion复数),theme_hook_suggestions再安装FILO先进后出的原则匹配,最后加入的优先级最高。
// Invoke the variable processors, if any. The processors may specify
// alternate suggestions for which hook‘s template/function to use.
if (isset($info[‘preprocess functions‘]) || isset($info[‘process functions‘])) {
$variables[‘theme_hook_suggestions‘] = array();
foreach (array(‘preprocess functions‘, ‘process functions‘) as $phase) {
if (!empty($info[$phase])) {
foreach ($info[$phase] as $processor_function) {
if (function_exists($processor_function)) {
// We don‘t want a poorly behaved process function changing $hook.
$hook_clone = $hook; // 不要让某些人将$hook搞坏了
$processor_function($variables, $hook_clone);
}
}
}
}
// If the preprocess/process functions specified hook suggestions, and the
// suggestion exists in the theme registry, use it instead of the hook that
// theme() was called with. This allows the preprocess/process step to
// route to a more specific theme hook. For example, a function may call
// theme(‘node‘, ...), but a preprocess function can add ‘node__article‘ as
// a suggestion, enabling a theme to have an alternate template file for
// article nodes. Suggestions are checked in the following order:
// - The ‘theme_hook_suggestion‘ variable is checked first. It overrides
// all others.
// - The ‘theme_hook_suggestions‘ variable is checked in FILO order, so the
// last suggestion added to the array takes precedence over suggestions
// added earlier.
$suggestions = array();
if (!empty($variables[‘theme_hook_suggestions‘])) {
$suggestions = $variables[‘theme_hook_suggestions‘];
}
if (!empty($variables[‘theme_hook_suggestion‘])) {
$suggestions[] = $variables[‘theme_hook_suggestion‘];
}
foreach (array_reverse($suggestions) as $suggestion) {
if (isset($hooks[$suggestion])) {
$info = $hooks[$suggestion];
break;
}
}
}
产生输出可以用函数也可以用模版。$info[‘function‘]表示调用函数产生输出。
if (isset($info[‘function‘])) {
if (function_exists($info[‘function‘])) {
$output = $info[‘function‘]($variables);
}
}
$info[‘template‘]表示使用模板产生输出。theme_render_template()是默认的模板输出函数,.tpl.php是默认的模板文件扩展名。
// Default render function and extension.
$render_function = ‘theme_render_template‘;
$extension = ‘.tpl.php‘;
不同的theme engine允许有不同的模板输出函数和模板文件扩展名。
// The theme engine may use a different extension and a different renderer.
global $theme_engine;
if (isset($theme_engine)) {
if ($info[‘type‘] != ‘module‘) {
if (function_exists($theme_engine . ‘_render_template‘)) {
$render_function = $theme_engine . ‘_render_template‘;
}
$extension_function = $theme_engine . ‘_extension‘;
if (function_exists($extension_function)) {
$extension = $extension_function();
}
}
}
通过template_preprocess()为$varialbes添加默认变量。使用$variables[‘directory‘]判断template_preprocess()有没有执行过。
// In some cases, a template implementation may not have had
// template_preprocess() run (for example, if the default implementation is
// a function, but a template overrides that default implementation). In
// these cases, a template should still be able to expect to have access to
// the variables provided by template_preprocess(), so we add them here if
// they don‘t already exist. We don‘t want to run template_preprocess()
// twice (it would be inefficient and mess up zebra striping), so we use the
// ‘directory‘ variable to determine if it has already run, which while not
// completely intuitive, is reasonably safe, and allows us to save on the
// overhead of adding some new variable to track that.
if (!isset($variables[‘directory‘])) {
$default_template_variables = array();
template_preprocess($default_template_variables, $hook);
$variables += $default_template_variables;
}
调用模板输出函数$render_function产生输出内容。
// Render the output using the template file.
$template_file = $info[‘template‘] . $extension;
if (isset($info[‘path‘])) {
$template_file = $info[‘path‘] . ‘/‘ . $template_file;
}
$output = $render_function($template_file, $variables);
Drupal使用PHPTemplate作为默认的主题引擎。PHPTemplate的输出函数theme_render_template()很简单,include模板文件就OK。
/**
* Renders a system default template, which is essentially a PHP template.
*
* @param $template_file
* The filename of the template to render.
* @param $variables
* A keyed array of variables that will appear in the output.
*
* @return
* The output generated by the template.
*/
function theme_render_template($template_file, $variables) {
// Extract the variables to a local namespace
extract($variables, EXTR_SKIP);// Start output buffering
ob_start();// Include the template file
include DRUPAL_ROOT . ‘/‘ . $template_file;// End buffering and return its contents
return ob_get_clean();
}
最后,theme()还原$theme_path,返回输出$output。
// restore path_to_theme()
$theme_path = $temp;
return $output;
解析theme(),码迷,mamicode.com