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;
来源:https://www.cnblogs.com/eastson/p/3700718.html