解释 `get_template_part()` 函数的源码,它是如何根据 “ 和 “ 参数加载模板文件的?

WordPress 模板加载大法:get_template_part() 函数解剖讲座

大家好!我是你们今天的导游,带大家深入探索 WordPress 模板世界中一个非常重要的函数:get_template_part()。 别担心,今天我们不搞虚的,直接扒源码,看看这个家伙到底是怎么工作的,又是如何根据 slugname 这两个小参数,变戏法似的加载出我们需要的模板文件的。准备好了吗? 让我们开始这场有趣的探险吧!

1. 欢迎来到 get_template_part() 的世界

首先,让我们明确一下目标:get_template_part() 的作用简单来说,就是加载一个模板文件。这个模板文件可以是主题中的任何 .php 文件,通常用于组织主题结构,比如页眉、页脚、侧边栏等等。

它的基本用法是这样的:

<?php get_template_part( string $slug, string|null $name = null ); ?>

$slug 是必需的,它指定了模板的基本文件名(不包含 .php 后缀)。$name 是可选的,它是一个后缀,可以用来区分同一类型的不同模板。

举个例子,如果我们想加载 template-parts/content-page.php 这个文件,我们可以这样写:

<?php get_template_part( 'template-parts/content', 'page' ); ?>

这里,$slug'template-parts/content'$name'page'get_template_part() 会尝试加载 template-parts/content-page.php 文件。

是不是感觉有点像侦探游戏?别急,精彩的还在后面。

2. 源码剖析:一层一层揭开它的神秘面纱

现在,让我们勇敢地跳进 get_template_part() 的源码,看看它到底是怎么实现的。为了更好地理解,我们将分步骤进行,并配以代码注释。

/**
 * Loads a template part into a template.
 *
 * @since 3.0.0
 *
 * @param string      $slug The slug name for the generic template.
 * @param string|null $name The name of the specialised template.
 * @param array       $args Array of arguments to pass into the template.
 */
function get_template_part( string $slug, string|null $name = null, array $args = array() ) {
    /**
     * Fires before the specified template part file is loaded.
     *
     * The dynamic portion of the hook name, `$slug`, refers to the slug name
     * for the generic template part.
     *
     * @since 3.0.0
     *
     * @param string      $slug The slug name for the generic template.
     * @param string|null $name The name of the specialised template.
     * @param array       $args Array of arguments to pass into the template.
     */
    do_action( "get_template_part_{$slug}", $slug, $name, $args );

    $templates = array();
    $name      = (string) $name;
    if ( '' !== $name ) {
        $templates[] = "{$slug}-{$name}.php";
    }

    $templates[] = "{$slug}.php";

    /**
     * Filters the list of template filenames to search for when calling get_template_part().
     *
     * The dynamic portion of the hook name, `$slug`, refers to the slug name
     * for the generic template part.
     *
     * @since 3.0.0
     *
     * @param string[]    $templates List of template filenames.
     * @param string      $slug      Slug of the template.
     * @param string|null $name      Name of the template.
     * @param array       $args Array of arguments to pass into the template.
     */
    $templates = apply_filters( "get_template_part_{$slug}", $templates, $slug, $name, $args );

    locate_template( $templates, true, false, $args );
}

现在,让我们一步一步地拆解这段代码:

Step 1: 钩子 (Action) 的召唤

do_action( "get_template_part_{$slug}", $slug, $name, $args );

这行代码使用 do_action() 函数触发了一个 action hook。这个 hook 的名称是动态生成的,使用了 $slug 变量。这意味着我们可以通过这个 hook 在模板加载之前执行一些自定义操作,比如修改 $slug$name 的值,甚至取消加载模板。这为我们提供了极大的灵活性。

Step 2: 模板文件名的构建

$templates = array();
$name      = (string) $name;
if ( '' !== $name ) {
    $templates[] = "{$slug}-{$name}.php";
}

$templates[] = "{$slug}.php";

这段代码是 get_template_part() 的核心部分。它根据 $slug$name 构造了一组可能的模板文件名,并将它们存储在一个数组 $templates 中。

  • 首先,如果 $name 不为空,它会构造一个形如 "{$slug}-{$name}.php" 的文件名,并添加到 $templates 数组中。
  • 然后,它会构造一个形如 "{$slug}.php" 的文件名,也添加到 $templates 数组中。

这意味着 get_template_part() 会优先查找带有 $name 后缀的模板文件,如果找不到,才会查找不带后缀的基本模板文件。

例如,如果 $slug'template-parts/content'$name'page',那么 $templates 数组将包含以下两个文件名:

  1. template-parts/content-page.php
  2. template-parts/content.php

Step 3: 过滤器 (Filter) 的妙用

$templates = apply_filters( "get_template_part_{$slug}", $templates, $slug, $name, $args );

这行代码使用 apply_filters() 函数应用了一个 filter hook。这个 hook 的名称同样是动态生成的,使用了 $slug 变量。这意味着我们可以通过这个 filter hook 修改 $templates 数组,从而改变 get_template_part() 查找模板文件的顺序,甚至添加或删除模板文件名。这为我们提供了更高级的自定义能力。

Step 4: 模板文件的定位与加载

locate_template( $templates, true, false, $args );

这行代码调用了 locate_template() 函数,这是另一个 WordPress 内置函数,它的作用是在主题目录中查找指定的模板文件,并加载它。

  • $templates 参数是包含了可能的模板文件名的数组。
  • true 参数表示如果找到了模板文件,就直接加载它。
  • false 参数表示不加载子主题中的模板文件(如果存在)。
  • $args 参数是一个数组,包含了传递给模板文件的参数。

locate_template() 函数会按照 $templates 数组中的顺序,依次查找这些文件。一旦找到一个存在的文件,它就会立即加载该文件,并停止查找。如果没有找到任何文件,它将不会执行任何操作。

3. locate_template() 函数的深入解析

既然 locate_template()get_template_part() 中扮演了如此重要的角色,那么我们不妨也来深入了解一下它的工作原理。

/**
 * Retrieve the name of the highest priority template file that exists.
 *
 * Searches in the stylesheet directory before the template directory
 * so that themes which inherit from a parent theme can just define
 * the template files that they want to override.
 *
 * @since 1.5.0
 *
 * @param string|string[] $template_names Template file(s) to search for, in order.
 * @param bool            $load          If true the template file will be loaded if it is found.
 * @param bool            $require_once  Whether to require_once or require. Has no effect if $load is false.
 *                                      Default true.
 * @param array           $args          Array of arguments to pass into the template.
 * @return string The template filename if one is located.
 */
function locate_template( $template_names, $load = false, $require_once = true, $args = array() ) {
    $located = '';
    foreach ( (array) $template_names as $template_name ) {
        if ( ! $template_name ) {
            continue;
        }

        // Trim off any slashes from the template name.
        $template_name = ltrim( $template_name, '/' );

        // Check child theme first.
        if ( file_exists( get_stylesheet_directory() . '/' . $template_name ) ) {
            $located = get_stylesheet_directory() . '/' . $template_name;
            break;
        }

        // Check parent theme next.
        if ( file_exists( get_template_directory() . '/' . $template_name ) ) {
            $located = get_template_directory() . '/' . $template_name;
            break;
        }

        // Check in the plugin-templates/ directory
        $located = apply_filters( 'plugin_template_hierarchy', $located, $template_name );

        if ( $located ) {
            break;
        }
    }

    if ( $load && '' !== $located ) {
        load_template( $located, $require_once, $args );
    }

    return $located;
}

Step 1: 遍历模板文件名

foreach ( (array) $template_names as $template_name ) {
    if ( ! $template_name ) {
        continue;
    }

    // Trim off any slashes from the template name.
    $template_name = ltrim( $template_name, '/' );

    // ... (后面的查找逻辑)
}

locate_template() 函数首先遍历传入的 $template_names 数组。对于每个模板文件名,它会去除前导斜杠,并执行后面的查找逻辑。

Step 2: 优先查找子主题

if ( file_exists( get_stylesheet_directory() . '/' . $template_name ) ) {
    $located = get_stylesheet_directory() . '/' . $template_name;
    break;
}

这段代码首先检查子主题的目录中是否存在该模板文件。get_stylesheet_directory() 函数返回当前主题的样式表目录,对于子主题来说,它会返回子主题的目录。如果找到了该文件,$located 变量会被设置为该文件的完整路径,并使用 break 语句跳出循环。

Step 3: 查找父主题

if ( file_exists( get_template_directory() . '/' . $template_name ) ) {
    $located = get_template_directory() . '/' . $template_name;
    break;
}

如果子主题中没有找到该模板文件,这段代码会检查父主题的目录中是否存在该文件。get_template_directory() 函数返回当前主题的模板目录,对于子主题来说,它会返回父主题的目录。如果找到了该文件,$located 变量会被设置为该文件的完整路径,并使用 break 语句跳出循环。

Step 4: 插件模板目录

// Check in the plugin-templates/ directory
$located = apply_filters( 'plugin_template_hierarchy', $located, $template_name );

if ( $located ) {
    break;
}

该段代码允许插件通过 plugin_template_hierarchy 过滤器来扩展模板的查找位置,通常用于在插件的 plugin-templates/ 目录下查找模板文件。

Step 5: 加载模板文件

if ( $load && '' !== $located ) {
    load_template( $located, $require_once, $args );
}

如果在循环中找到了一个模板文件($located 不为空),并且 $load 参数为 true,那么这段代码会调用 load_template() 函数加载该文件。$require_once 参数决定了使用 require_once 还是 require 来加载文件。$args 参数是一个数组,包含了传递给模板文件的参数。

Step 6: 返回模板文件路径

return $located;

最后,locate_template() 函数返回找到的模板文件的路径。如果没有找到任何文件,它将返回一个空字符串。

4. load_template() 函数的简单介绍

load_template() 函数负责加载指定的模板文件,并将 $args 数组中的变量传递给该文件。它的源码比较简单:

/**
 * Require the template file with WordPress environment.
 *
 * @since 1.5.0
 *
 * @param string $template_path Path to the template file.
 * @param bool   $require_once Whether to require_once or require. Default true.
 * @param array  $args Array of arguments to pass into the template.
 */
function load_template( $template_path, $require_once = true, $args = array() ) {
    global $posts, $post, $wp_did_header, $wp_query, $wp_rewrite, $wpdb, $wp_locale, $wp, $wp_version, $wp_theme_directories, $wp_current_filter;

    if ( is_array( $wp_current_filter ) ) {
        $wp_current_filter = array_unique( $wp_current_filter );
    }

    if ( is_array( $args ) ) {
        extract( $args );
    }

    if ( $require_once ) {
        require_once $template_path;
    } else {
        require $template_path;
    }
}

这段代码主要做了两件事:

  1. 提取参数: 使用 extract() 函数将 $args 数组中的键值对提取为变量。这意味着我们可以在模板文件中直接使用这些变量,而无需使用 $args['key'] 的形式访问。

  2. 加载模板文件: 使用 require_oncerequire 语句加载指定的模板文件。

5. 总结:get_template_part() 的工作流程

现在,让我们把所有拼图碎片组合起来,总结一下 get_template_part() 的工作流程:

  1. 触发 action hook: get_template_part() 首先触发一个 action hook,允许我们在模板加载之前执行一些自定义操作。

  2. 构建模板文件名: 它根据 $slug$name 构造一组可能的模板文件名,并将它们存储在一个数组中。

  3. 应用 filter hook: 它应用一个 filter hook,允许我们修改模板文件名数组。

  4. 定位和加载模板文件: 它调用 locate_template() 函数,按照数组中的顺序查找模板文件。locate_template() 函数会优先查找子主题中的文件,然后查找父主题中的文件,最后根据 plugin_template_hierarchy 过滤器查找插件模板目录。

  5. 加载模板文件: 如果 locate_template() 找到了一个模板文件,它会调用 load_template() 函数加载该文件,并将 $args 数组中的变量传递给该文件。

6. 案例分析:实战演练

为了更好地理解 get_template_part() 的用法,让我们来看几个实际的案例。

案例 1:加载页眉和页脚

这是 get_template_part() 最常见的用法之一。我们可以在主题的 header.phpfooter.php 文件中使用 get_template_part() 加载页眉和页脚的模板文件。

// header.php
<?php get_template_part( 'template-parts/header' ); ?>

// footer.php
<?php get_template_part( 'template-parts/footer' ); ?>

在这个例子中,get_template_part() 会分别加载 template-parts/header.phptemplate-parts/footer.php 文件。

案例 2:加载不同类型的文章内容

我们可以使用 get_template_part() 根据文章的类型加载不同的内容模板。

<?php
$post_type = get_post_type();
get_template_part( 'template-parts/content', $post_type );
?>

在这个例子中,get_post_type() 函数返回当前文章的类型。然后,get_template_part() 会根据文章类型加载相应的模板文件。例如,如果文章类型是 'post',它会加载 template-parts/content-post.php 文件;如果文章类型是 'page',它会加载 template-parts/content-page.php 文件。

案例 3:传递参数给模板文件

我们可以使用 $args 参数将变量传递给模板文件。

<?php
$author_id = get_the_author_meta( 'ID' );
$args = array(
    'author_id' => $author_id,
    'author_name' => get_the_author(),
);
get_template_part( 'template-parts/author', null, $args );
?>

在这个例子中,我们创建了一个 $args 数组,包含了作者的 ID 和姓名。然后,我们将这个数组传递给 get_template_part() 函数。在 template-parts/author.php 文件中,我们可以直接使用 $author_id$author_name 变量。

7. 注意事项:一些小贴士

  • 文件名必须是 .php get_template_part() 只能加载 .php 文件。
  • slugname 的命名规范: 建议使用小写字母和连字符来命名 slugname
  • 使用 action 和 filter hook: 充分利用 action 和 filter hook,可以实现更高级的自定义功能。
  • 避免过度使用: 虽然 get_template_part() 非常方便,但过度使用可能会导致代码难以维护。

8. 总结:get_template_part() 的价值

get_template_part() 是 WordPress 主题开发中一个非常重要的函数。它提供了一种灵活而强大的方式来组织主题结构,并加载不同的模板文件。通过深入理解它的工作原理,我们可以更好地利用它来构建高质量的 WordPress 主题。

今天的讲座就到这里。希望通过今天的学习,大家对 get_template_part() 函数有了更深入的了解。 感谢大家的参与!下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注