解析theme()

Deadly 提交于 2019-12-23 05:13:35

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;
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!