深入理解 WordPress `locate_template()` 函数的源码:它是如何按照特定的优先级查找模板文件的?

大家好,欢迎来到今天的WordPress源码剖析讲座!今天我们要聊聊一个WordPress主题开发里非常重要的函数:locate_template()。 别看它名字平平无奇,实际上它负责了WordPress主题文件查找的核心逻辑,是主题能够正确渲染页面的关键先生。

好,废话不多说,咱们直接扒它的源码,看看它到底是怎么运作的,又是如何按照优先级查找模板文件的。

一、locate_template() 函数的概览

首先,让我们先明确一下locate_template()函数的作用:它接收一个或多个模板文件名作为参数,然后按照一定的优先级顺序,在主题目录及其父主题目录中查找这些文件,并返回找到的第一个文件的完整路径。如果找不到任何匹配的文件,则返回一个空字符串。

它的基本语法如下:

<?php
locate_template(
    string|string[] $template_names,
    bool $load = false,
    bool $require_once = true,
    array $args = []
) : string
?>
  • $template_names (string|string[]): 要查找的模板文件名,可以是单个文件名字符串,也可以是包含多个文件名字符串的数组。这是必填参数。
  • $load (bool): 是否加载找到的模板文件。如果设置为true,则在找到文件后立即使用require()require_once()加载它。默认为false,仅返回文件路径。
  • $require_once (bool): 如果 $loadtrue,则决定使用 require_once() 还是 require() 加载文件。默认为 true,使用 require_once(),确保文件只被加载一次。
  • $args (array): 传递给模板文件的参数。 这些参数可以在模板文件中通过 $args 变量访问。WordPress 5.5 引入。

二、源码剖析:深入locate_template()的内部运作

我们来看一下locate_template()函数的具体实现 (WordPress 6.4.3):

<?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( ABSPATH . WPINC . '/template-loader.php' ) ) {
            // Check in the WP core includes directory if needed.
            // This is for theme developers who are including files in their themes that are also in the WP core includes directory.
            // For example, if a theme includes wp-includes/template-loader.php, it will be loaded from the theme directory instead of the WP core includes directory.
            // This ensures that the theme's version of the file is used.
            if ( file_exists( ABSPATH . WPINC . '/' . $template_name ) ) {
                $located = ABSPATH . WPINC . '/' . $template_name;
                break;
            }
        }
    }

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

    return $located;
}

我们来逐行分析一下这段代码:

  1. 初始化 $located 变量:

    $located = '';

    这个变量用于存储找到的模板文件的路径,初始值为空字符串。

  2. 循环遍历模板文件名:

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

    $template_names 参数会被强制转换为数组。然后,代码遍历这个数组,逐个检查每个模板文件名。如果 $template_name 为空,则跳过当前循环。

  3. 查找模板文件(优先级顺序):

    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( ABSPATH . WPINC . '/template-loader.php' ) ) {
        // Check in the WP core includes directory if needed.
        // This is for theme developers who are including files in their themes that are also in the WP core includes directory.
        // For example, if a theme includes wp-includes/template-loader.php, it will be loaded from the theme directory instead of the WP core includes directory.
        // This ensures that the theme's version of the file is used.
        if ( file_exists( ABSPATH . WPINC . '/' . $template_name ) ) {
            $located = ABSPATH . WPINC . '/' . $template_name;
            break;
        }
    }

    这是locate_template()函数的核心部分,它按照以下优先级顺序查找模板文件:

    • 第一优先级:子主题目录 (STYLESHEETPATH)

      STYLESHEETPATH 是一个常量,指向当前子主题的目录。 如果当前主题是子主题,那么它首先会在子主题目录中查找模板文件。

    • 第二优先级:父主题目录 (TEMPLATEPATH)

      TEMPLATEPATH 是一个常量,指向当前主题的目录。如果当前主题不是子主题,或者在子主题目录中没有找到模板文件,那么它会在父主题目录中查找。

    • 第三优先级: WordPress核心includes目录(ABSPATH . WPINC)

      这个是为了兼容主题开发者,他们在主题中包含了和WordPress核心目录里重名的文件。这个目录只有在父主题或者子主题目录里,已经包含了ABSPATH . WPINC . '/template-loader.php'文件之后,才会去查找,否则不会查找。

    如果找到了匹配的模板文件,$located 变量会被更新为文件的完整路径,并且 break 语句会跳出循环。

  4. 加载模板文件(如果需要):

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

    如果 $load 参数为 true,并且找到了模板文件($located 不为空),那么 load_template() 函数会被调用来加载模板文件。load_template() 函数本质上就是使用 require()require_once() 来包含模板文件。$require_once 参数决定了使用 require() 还是 require_once()$args参数会被传递给加载的模板文件。

  5. 返回模板文件路径:

    return $located;

    函数最终返回找到的模板文件的路径。如果没有找到任何匹配的模板文件,则返回空字符串。

三、深入理解优先级

locate_template() 函数的优先级查找机制是WordPress主题系统灵活性的关键。它允许子主题覆盖父主题的模板文件,从而实现定制化。

用表格来展示一下优先级:

优先级 目录 说明
1 STYLESHEETPATH (子主题目录) 如果当前主题是子主题,那么首先在这里查找。子主题可以覆盖父主题的模板文件,实现定制化。
2 TEMPLATEPATH (父主题目录) 如果当前主题不是子主题,或者在子主题目录中没有找到模板文件,那么在这里查找。
3 ABSPATH . WPINC (WordPress核心目录) 只有在父主题或者子主题目录里,已经包含了ABSPATH . WPINC . '/template-loader.php'文件之后,才会去查找,否则不会查找。这是为了兼容主题开发者,他们在主题中包含了和WordPress核心目录里重名的文件。

四、load_template() 函数

locate_template()中,如果 $load 参数设置为 true,就会调用 load_template() 函数来加载找到的模板文件。 我们也来看看它的源码:

<?php
function load_template( $template_path, $require_once = true, $args = array() ) {
    global $posts, $post, $wp_did_header, $wp_query, $wp_rewrite, $wpdb, $wp_locale, $wp_admin_bar, $wp_registered_sidebars;

    /**
     * Fires before the specified template is loaded.
     *
     * @since 1.5.0
     *
     * @param string $template_path The path to the template being loaded.
     * @param bool   $require_once Whether to require_once or require. Default true.
     * @param array  $args          Additional arguments passed to the template.
     */
    do_action( 'template_redirect', $template_path, $require_once, $args );

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

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

这个函数做了以下几件事:

  1. 引入全局变量:

    global $posts, $post, $wp_did_header, $wp_query, $wp_rewrite, $wpdb, $wp_locale, $wp_admin_bar, $wp_registered_sidebars;

    将一些常用的全局变量引入到函数的作用域中,以便在模板文件中使用。

  2. 触发 template_redirect 动作钩子:

    do_action( 'template_redirect', $template_path, $require_once, $args );

    在模板文件加载之前,触发 template_redirect 动作钩子。这允许插件或其他主题修改模板加载的行为。

  3. 提取参数:

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

    如果 $args 参数是一个数组,那么使用 extract() 函数将数组的键值对提取为变量。这样,在模板文件中就可以直接使用这些变量。

  4. 加载模板文件:

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

    根据 $require_once 参数的值,使用 require_once()require() 函数来加载模板文件。

五、实际应用场景

locate_template() 函数在WordPress主题开发中被广泛使用。以下是一些常见的应用场景:

  • 加载特定模板文件:

    <?php
    locate_template( 'my-template.php', true ); // 加载 my-template.php 文件
    ?>
  • 根据条件加载不同的模板文件:

    <?php
    if ( is_page( 'about' ) ) {
        locate_template( array( 'page-about.php', 'page.php' ), true ); // 优先加载 page-about.php,如果找不到则加载 page.php
    } else {
        locate_template( 'page.php', true ); // 加载 page.php
    }
    ?>
  • 在插件中使用主题模板:

    <?php
    $template = locate_template( 'my-plugin-template.php' );
    if ( ! empty( $template ) ) {
        include( $template ); // 在插件中使用主题的 my-plugin-template.php 文件
    }
    ?>
  • 传递参数给模板文件

    <?php
    $args = array(
        'title' => 'Hello World',
        'content' => 'This is my custom content.'
    );
    locate_template( 'my-template.php', true, $args );
    ?>

    my-template.php中:

    <?php
    echo '<h1>' . esc_html( $title ) . '</h1>';
    echo '<p>' . esc_html( $content ) . '</p>';
    ?>

六、注意事项

  • 文件路径:locate_template() 函数返回的是文件的完整路径,而不是相对路径。
  • 性能: 频繁调用 locate_template() 函数可能会影响性能,因为它需要进行文件查找。尽量避免在循环中调用它。
  • 安全性: 在使用 include()require() 加载模板文件时,要确保模板文件的来源是可信的,以防止安全漏洞。
  • 主题继承: locate_template() 函数是实现主题继承的关键。子主题可以通过覆盖父主题的模板文件来定制网站的外观和功能。
  • get_template_part() 函数: get_template_part() 函数是 locate_template() 函数的封装,它简化了模板文件的加载过程。get_template_part() 函数会自动加载模板文件,而无需手动使用 include()require()

七、get_template_part() 函数

让我们简单看下get_template_part函数,它用起来更方便一些:

<?php
function get_template_part( $slug, $name = null, $args = array() ) {
    /**
     * Fires before the specified template part 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 Name of the generic template.
     * @param string|null $name Name of the specific template.
     * @param array       $args Additional arguments passed to the template.
     */
    do_action( "get_template_part_{$slug}", $slug, $name, $args );

    // Build a list of possible parts names that can be filenames.
    $templates = array();
    if ( isset( $name ) && '' !== $name ) {
        $templates[] = "{$slug}-{$name}.php";
    }
    $templates[] = "{$slug}.php";

    // Allow some of the template names to be filtered to ease theme developers job.
    $templates = apply_filters( 'get_template_part_templates', $templates, $slug, $name );

    /**
     * Fires after the template for the part is determined.
     *
     * The dynamic portion of the hook name, `$slug`, refers to the slug
     * name for the generic template part.
     *
     * @since 5.5.0
     *
     * @param string      $slug Name of the generic template.
     * @param string|null $name Name of the specific template.
     * @param array       $templates Array of template files to search for, in order.
     * @param array       $args Additional arguments passed to the template.
     */
    do_action( "get_template_part_{$slug}_templates", $slug, $name, $templates, $args );

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

这个函数的主要功能是:

  1. 构建模板文件名数组: 它根据 $slug$name 参数构建一个包含多个可能的模板文件名的数组。例如,如果 $slug'content'$name'single',那么它会构建一个包含 'content-single.php''content.php' 的数组。
  2. 调用 locate_template() 函数: 它调用 locate_template() 函数来查找并加载模板文件。locate_template() 函数会按照优先级顺序查找模板文件,并返回找到的第一个文件的路径。
  3. 简化模板加载: get_template_part() 函数简化了模板文件的加载过程,因为它会自动构建模板文件名数组,并调用 locate_template() 函数来查找和加载模板文件。

八、总结

locate_template() 函数是WordPress主题系统的重要组成部分,它负责模板文件的查找和加载。理解它的工作原理对于主题开发至关重要。通过掌握 locate_template() 函数的用法,你可以更好地控制主题的结构和外观,并实现高度定制化的WordPress网站。

希望今天的讲座对你有所帮助!下次再见!

发表回复

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