探讨 `get_template_part()` 函数的源码,它是如何实现主题模板的可重用性的?

各位观众老爷,晚上好!我是今天的主讲人,咱们今天聊聊WordPress主题开发里一个非常实用,甚至可以说不可或缺的函数——get_template_part()。 这玩意儿就像乐高积木,能让你把主题模板拆成一块块,然后想怎么拼就怎么拼,大大提高代码复用率,让你的主题结构更清晰、更容易维护。

咱们先来个热身,用人话描述一下get_template_part()的功能:

get_template_part():主题模板的模块化利器,它允许你加载并包含指定名称的模板片段文件。

是不是很简单?别急,更精彩的还在后面。

一、get_template_part() 的基本用法

最简单的用法是这样的:

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

这行代码会尝试加载以下文件(按优先级从高到低):

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

也就是说,get_template_part() 函数接受两个参数:

  • $slug (必需):模板片段的基本名称(例如,template-parts/content)。
  • $name (可选):模板片段的额外名称(例如,page)。

函数会根据这两个参数,按特定的顺序搜索模板文件,找到第一个匹配的文件就加载它。 如果 $name 参数为空,则只会搜索 $slug.php

二、为什么要用 get_template_part()

说了这么多,你可能要问了:直接 include 或者 require 不香吗? 为啥非要用这个 get_template_part()

原因在于:

  • 可维护性: 将主题拆分成小的、可重用的模块,更容易理解和修改。
  • 可扩展性: 方便子主题覆盖父主题的模板片段。
  • 代码复用: 避免重复编写相同的代码。
  • 主题结构清晰: 更好地组织主题文件。

举个例子,假设你的主题首页和存档页都需要显示文章列表,文章的显示方式基本相同,只有一些细微的差别。 如果不用 get_template_part(),你可能需要在 index.phparchive.php 里都写一遍文章列表的代码,一旦需要修改,就要改两处地方,简直是噩梦。

如果用 get_template_part(),你可以把文章列表的代码提取到一个单独的模板片段文件(例如 template-parts/content.php),然后在 index.phparchive.php 里都调用 get_template_part( 'template-parts/content' ),这样就只需要维护一个文件了。

三、get_template_part() 源码分析

理论讲完了,咱们来扒一扒 get_template_part() 的源码,看看它到底是怎么工作的。 (友情提示:前方代码高能,请系好安全带!)

get_template_part() 函数位于 wp-includes/template.php 文件中。 为了方便大家理解,我把源码简化了一下,去掉了部分不常用的参数和代码:

function get_template_part( $slug, $name = null, $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 part.
     * @param string|null $name The name of the specialized template part.
     */
    do_action( "get_template_part_{$slug}", $slug, $name );

    $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 4.0.0
     *
     * @param string[]    $templates List of template filenames to search for.
     * @param string      $slug      Slug of the template.
     * @param string|null $name      Name of the template.
     */
    $templates = apply_filters( "get_template_part_{$slug}", $templates, $slug, $name );

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

咱们一行一行地分析:

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

    这是一个 action hook,允许开发者在加载模板片段之前执行一些自定义操作。 例如,你可以根据 $slug$name 的值,动态地修改一些全局变量,或者执行一些权限检查。

  2. $templates = array();

    创建一个空数组,用来存储要搜索的模板文件路径。

  3. $name = (string) $name;

    $name 强制转换为字符串类型。

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

    如果 $name 不为空,则将 {$slug}-{$name}.php 添加到 $templates 数组中。 这就是为什么 get_template_part( 'template-parts/content', 'page' ) 会首先搜索 template-parts/content-page.php 的原因。

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

    {$slug}.php 添加到 $templates 数组中。 这就是为什么 get_template_part( 'template-parts/content', 'page' ) 在找不到 template-parts/content-page.php 时,会搜索 template-parts/content.php 的原因。

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

    这是一个 filter hook,允许开发者修改 $templates 数组。 你可以通过这个 hook,添加、删除或修改要搜索的模板文件路径。 这为主题的定制提供了极大的灵活性。

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

    这是最关键的一步,它调用了 locate_template() 函数来查找并加载模板文件。

    • $templates:要搜索的模板文件路径数组。
    • true:如果找到模板文件,则加载它。
    • false:不返回模板文件的路径。
    • $args:传递给模板文件的参数数组。

四、locate_template() 源码分析

locate_template() 函数位于 wp-includes/template.php 文件中,它的作用是在主题目录及其父主题目录中查找指定的模板文件。

function locate_template( $template_names, $load = false, $require_once = true, $args = array() ) {
    $located = '';
    foreach ( (array) $template_names as $template_name ) {
        if ( ! $template_name ) {
            continue;
        }

        if ( file_exists( STYLESHEETPATH . '/' . $template_name ) ) {
            $located = STYLESHEETPATH . '/' . $template_name;
            break;
        } elseif ( file_exists( TEMPLATEPATH . '/' . $template_name ) ) {
            $located = TEMPLATEPATH . '/' . $template_name;
            break;
        } elseif ( file_exists( WPINC . '/theme-compat/' . $template_name ) ) {
            $located = WPINC . '/theme-compat/' . $template_name;
            break;
        }
    }

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

    return $located;
}

咱们一行一行地分析:

  1. $located = '';

    初始化一个空字符串,用来存储找到的模板文件的路径。

  2. foreach ( (array) $template_names as $template_name ) { ... }

    遍历 $template_names 数组,依次查找每个模板文件。

  3. if ( ! $template_name ) { continue; }

    如果 $template_name 为空,则跳过本次循环。

  4. if ( file_exists( STYLESHEETPATH . '/' . $template_name ) ) { ... }

    检查当前主题目录中是否存在 $template_name 文件。 STYLESHEETPATH 常量指向当前主题的目录。

  5. elseif ( file_exists( TEMPLATEPATH . '/' . $template_name ) ) { ... }

    如果当前主题目录中不存在 $template_name 文件,则检查父主题目录中是否存在 $template_name 文件。 TEMPLATEPATH 常量指向父主题的目录。

  6. elseif ( file_exists( WPINC . '/theme-compat/' . $template_name ) ) { ... }

    如果父主题目录中也不存在 $template_name 文件,则检查 WordPress 核心的 theme-compat 目录中是否存在 $template_name 文件。 这个目录包含一些默认的模板文件,用于在主题缺少某些必要文件时提供兼容性支持。

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

    如果 $loadtrue 且找到了模板文件,则调用 load_template() 函数来加载模板文件。

  8. return $located;

    返回找到的模板文件的路径。

五、load_template() 源码分析

load_template() 函数位于 wp-includes/template.php 文件中,它的作用是加载指定的模板文件,并将其中的代码包含到当前文件中。

function load_template( $_template_file, $require_once = true, $args = array() ) {
    global $posts, $post, $wp_did_header, $wp_query, $wp_rewrite, $wpdb, $wp_version, $wp, $id, $comment, $user_ID;

    if ( is_array( $wp_query->query_vars ) ) {
        extract( $wp_query->query_vars, EXTR_SKIP );
    }

    if ( isset( $args ) && is_array( $args ) ) {
        extract( $args, EXTR_SKIP );
    }

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

咱们一行一行地分析:

  1. global ...

    声明一些全局变量,使模板文件可以访问 WordPress 的核心对象和数据。

  2. if ( is_array( $wp_query->query_vars ) ) { extract( $wp_query->query_vars, EXTR_SKIP ); }

    $wp_query->query_vars 数组中的键值对提取为变量。 这使得模板文件可以访问 URL 中的查询参数。

  3. if ( isset( $args ) && is_array( $args ) ) { extract( $args, EXTR_SKIP ); }

    $args 数组中的键值对提取为变量。 这使得模板文件可以访问通过 get_template_part() 函数传递的参数。

  4. if ( $require_once ) { require_once( $_template_file ); } else { require( $_template_file ); }

    根据 $require_once 的值,使用 require_once()require() 函数来加载模板文件。 require_once() 函数只会加载一次模板文件,避免重复加载;require() 函数则每次都会加载模板文件。

六、get_template_part() 的高级用法

除了基本用法之外,get_template_part() 还有一些高级用法,可以让你更灵活地控制模板片段的加载。

  • 传递参数:

    可以通过 $args 参数,将数据传递给模板片段文件。

    <?php
    $args = array(
        'title' => 'Hello World!',
        'content' => 'This is some content.',
    );
    get_template_part( 'template-parts/content', null, $args );
    ?>

    然后在 template-parts/content.php 文件中,你可以通过 $title$content 变量来访问这些数据。

    <h1><?php echo esc_html( $title ); ?></h1>
    <p><?php echo esc_html( $content ); ?></p>
  • 使用 Filter Hook 修改模板路径:

    可以通过 get_template_part_{$slug} filter hook,动态地修改要搜索的模板文件路径。

    add_filter( 'get_template_part_template-parts/content', 'my_custom_template_part', 10, 3 );
    
    function my_custom_template_part( $templates, $slug, $name ) {
        if ( is_page( 'about' ) ) {
            $templates = array( 'template-parts/content-about.php', 'template-parts/content.php' );
        }
        return $templates;
    }

    这段代码的意思是:当加载 template-parts/content 模板片段时,如果当前页面是 "about" 页面,则优先搜索 template-parts/content-about.php 文件。

  • 在子主题中覆盖父主题的模板片段:

    子主题可以覆盖父主题的模板片段,只需要在子主题中创建与父主题相同路径和名称的模板文件即可。 例如,如果父主题有一个 template-parts/content.php 文件,你可以在子主题中创建一个 template-parts/content.php 文件,子主题的这个文件就会覆盖父主题的同名文件。

七、get_template_part() 的使用建议

为了更好地使用 get_template_part() 函数,我给大家一些建议:

  • 合理划分模板片段:

    将主题拆分成小的、功能单一的模板片段,例如:

    • template-parts/header.php:页眉
    • template-parts/footer.php:页脚
    • template-parts/content.php:文章内容
    • template-parts/sidebar.php:侧边栏
    • template-parts/navigation.php:导航菜单
  • 规范命名模板片段:

    使用一致的命名规范,例如:template-parts/content-{$post_type}.php,这样可以更容易地找到和理解模板片段。

  • 善用参数传递:

    通过 $args 参数,将数据传递给模板片段,避免在模板片段中直接访问全局变量。

  • 利用 Filter Hook 进行定制:

    使用 get_template_part_{$slug} filter hook,动态地修改模板路径,实现更灵活的定制。

八、get_template_part() 的替代方案

虽然 get_template_part() 非常实用,但它并不是唯一的选择。 在某些情况下,你可以考虑使用其他的替代方案:

  • includerequire

    如果不需要子主题覆盖父主题的功能,可以直接使用 includerequire 函数来加载模板文件。 但是,这种方式不如 get_template_part() 灵活,可维护性也较差。

  • 自定义函数:

    可以创建自定义函数来加载模板片段,例如:

    function my_get_content_template( $post_type ) {
        $template = locate_template( array( "template-parts/content-{$post_type}.php", 'template-parts/content.php' ) );
        if ( $template ) {
            include( $template );
        }
    }

    这种方式可以更灵活地控制模板片段的加载逻辑,但需要编写更多的代码。

九、总结

get_template_part() 函数是 WordPress 主题开发中一个非常重要的工具,它可以帮助你将主题拆分成小的、可重用的模块,提高代码复用率,让你的主题结构更清晰、更容易维护。 掌握 get_template_part() 的用法,对提高你的 WordPress 主题开发效率非常有帮助。

今天就讲到这里,希望大家有所收获! 下课!

发表回复

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