分析 WordPress `add_rewrite_rule()` 函数源码:新重写规则如何添加到 `WP_Rewrite` 对象。

WordPress Rewrite Rules:一场Rewrite Rules的恋爱故事

各位观众,晚上好!我是你们今晚的Rewrite Rules爱情导师,我们来聊聊WordPress的add_rewrite_rule(),以及它背后的WP_Rewrite对象,看看新来的Rewrite Rule是怎么追求到WP_Rewrite的芳心,成功加入这个大家庭的。

首先,我们得认识一下这场爱情故事的两位主角:

  • add_rewrite_rule(): 这个是我们的红娘,负责把新的Rewrite Rule介绍给WP_Rewrite。

  • WP_Rewrite: 这是我们的目标对象,一个管理所有Rewrite Rules的大家长,它掌握着WordPress URL的生杀大权。

我们开始吧!

1. add_rewrite_rule():红娘的登场

add_rewrite_rule() 函数是 WordPress 提供给开发者添加自定义 Rewrite Rules 的入口。 它的基本语法如下:

add_rewrite_rule( string $regex, string $redirect, string $where = 'top' )
  • $regex: 一个正则表达式,用于匹配请求的 URL。 这就像是Rewrite Rule的个人简历,要匹配上WP_Rewrite的“择偶标准”。
  • $redirect: 一个替换规则,用于将匹配的 URL 转换为 WordPress 可以理解的查询字符串。 这就像Rewrite Rule的甜言蜜语,告诉WP_Rewrite它能为它做什么。
  • $where: 可选参数,指定该规则应该添加到规则列表的顶部 ('top') 还是底部 ('bottom')。 决定了Rewrite Rule是想“先下手为强”还是“默默守护”。

一个简单的例子:

add_rewrite_rule(
    '^movies/([0-9]+)/?',
    'index.php?pagename=movie&movie_id=$matches[1]',
    'top'
);

这个规则的作用是:

  • 匹配:/movies/ 开头,后面跟着一个或多个数字,最后可能跟着一个斜杠的 URL (例如 /movies/123/)。
  • 转换: 将这样的 URL 转换为 index.php?pagename=movie&movie_id=$matches[1]$matches[1] 是正则表达式中第一个捕获组 (即括号中的内容) 的值,也就是电影的 ID。

2. 深入add_rewrite_rule():红娘的内部运作

add_rewrite_rule() 并没有直接操作 WP_Rewrite 对象,它只是把Rewrite Rule的信息储存起来。 那么,它是怎么把Rewrite Rule传递给WP_Rewrite的呢?

答案是: Action Hook

add_rewrite_rule() 函数会触发一个名为 rewrite_rules_array 的 Action Hook。 这个 Hook 允许其他函数修改 Rewrite Rules 数组。 WP_Rewrite 对象正是通过监听这个 Hook 来获取新的 Rewrite Rules。

add_rewrite_rule() 函数内部大致是这样的:

function add_rewrite_rule( $regex, $redirect, $where = 'top' ) {
    global $wp_rewrite;

    // 确保 WP_Rewrite 对象已经初始化
    if ( ! isset( $wp_rewrite ) || ! is_object( $wp_rewrite ) ) {
        return;
    }

    // 将规则添加到 WP_Rewrite 对象的 rules 属性中 (注意: 这是一个临时存储,最终会通过 rewrite_rules_array Hook 添加)
    if ( 'top' == $where ) {
        $wp_rewrite->rules = array_merge( array( $regex => $redirect ), $wp_rewrite->rules );
    } else {
        $wp_rewrite->rules[ $regex ] = $redirect;
    }

    // 需要刷新 Rewrite Rules
    $wp_rewrite->flush_rules();
}

注意,这里add_rewrite_rule()只是修改了$wp_rewrite->rules这个属性。 真正的添加动作,需要依赖rewrite_rules_array这个Hook。

3. WP_Rewrite:Rewrite Rules的大家长

WP_Rewrite 类是 WordPress 中负责处理 Rewrite Rules 的核心类。 它的主要职责包括:

  • 存储和管理 Rewrite Rules: 维护一个包含所有 Rewrite Rules 的数组。
  • 解析请求的 URL: 根据 Rewrite Rules 将请求的 URL 转换为 WordPress 可以理解的查询字符串。
  • 生成 Permalink: 根据文章、页面等的内容生成友好的 URL。

WP_Rewrite 对象会在 WordPress 初始化时被创建,并且可以通过全局变量 $wp_rewrite 访问。

WP_Rewrite 类有一个非常重要的属性,名为 rules。 这个属性是一个关联数组,其中键是正则表达式,值是替换规则。

4. rewrite_rules_array Hook:爱情的桥梁

add_rewrite_rule() 触发 rewrite_rules_array Hook 时,WP_Rewrite 对象会执行以下操作:

  1. 获取当前的 Rewrite Rules 数组:WP_Rewrite->rules 属性中获取当前的 Rewrite Rules 数组。 这个数组包含了 WordPress 核心、主题和插件添加的所有 Rewrite Rules。
  2. 合并新的 Rewrite Rules: 将新添加的 Rewrite Rules (通过 add_rewrite_rule() 添加的) 合并到当前的 Rewrite Rules 数组中。
  3. 更新 WP_Rewrite->rules 属性: 将合并后的 Rewrite Rules 数组保存到 WP_Rewrite->rules 属性中。

大致代码如下:

// WP_Rewrite 类中的方法 (简化版本)
public function rewrite_rules_filter( $rules ) {
    // $this->rules 包含了通过 add_rewrite_rule() 添加的新规则
    if ( ! empty( $this->rules ) ) {
        $rules = array_merge( $this->rules, $rules );
        $this->rules = array(); // 清空 $this->rules,避免重复添加
    }
    return $rules;
}

// 在 WordPress 初始化时,WP_Rewrite 对象会注册这个 filter
add_filter( 'rewrite_rules_array', array( $wp_rewrite, 'rewrite_rules_filter' ) );

可以这么理解:WP_Rewrite一直在监听rewrite_rules_array这个Hook,一旦触发,它就从$wp_rewrite->rules属性中取出新规则,合并到自己的Rewrite Rules列表中。

5. flush_rules():爱情的结晶

仅仅添加 Rewrite Rules 并不足以让它们生效。 还需要刷新 Rewrite Rules,也就是重新生成 .htaccess 文件 (或者更新 Nginx 的配置文件,如果使用的是 Nginx 服务器)。

add_rewrite_rule() 函数会调用 $wp_rewrite->flush_rules() 方法来触发 Rewrite Rules 的刷新。

flush_rules() 方法的主要作用是:

  1. 删除现有的 Rewrite Rules:.htaccess 文件 (或者 Nginx 配置文件) 中删除现有的 Rewrite Rules。
  2. 重新生成 Rewrite Rules: 根据 WP_Rewrite->rules 属性中的 Rewrite Rules 重新生成 .htaccess 文件 (或者 Nginx 配置文件)。
  3. 更新 Rewrite Rules 的缓存: 更新 Rewrite Rules 的缓存,以便 WordPress 可以更快地解析 URL。

flush_rules() 函数通常需要管理员权限才能执行,因为它会修改服务器的配置文件。

注意:频繁刷新 Rewrite Rules 会影响网站的性能,因此应该尽量避免不必要的刷新。 最好是在主题或插件激活时刷新一次,然后在停用时再次刷新。

6. 一个完整的恋爱故事:代码实战

我们来模拟一个完整的 Rewrite Rules 添加和刷新过程:

<?php
/**
 * Plugin Name: My Custom Rewrite Rules
 */

// 在插件激活时添加 Rewrite Rules
register_activation_hook( __FILE__, 'my_plugin_activate' );

function my_plugin_activate() {
    // 添加 Rewrite Rules
    add_rewrite_rule(
        '^products/([0-9]+)/?',
        'index.php?pagename=product&product_id=$matches[1]',
        'top'
    );

    // 刷新 Rewrite Rules
    flush_rewrite_rules();
}

// 在插件停用时刷新 Rewrite Rules
register_deactivation_hook( __FILE__, 'my_plugin_deactivate' );

function my_plugin_deactivate() {
    flush_rewrite_rules();
}

// 添加查询变量 (query var),否则 WordPress 会忽略 product_id
add_filter( 'query_vars', 'my_plugin_query_vars' );

function my_plugin_query_vars( $vars ) {
    $vars[] = 'product_id';
    return $vars;
}

// 模板文件中使用
// <?php
// $product_id = get_query_var( 'product_id' );
// if ( $product_id ) {
//     // 根据 product_id 显示产品信息
// }
// ?>

这个插件做了以下事情:

  1. 添加 Rewrite Rules: 在插件激活时,添加了一个 Rewrite Rule,将 /products/123/ 这样的 URL 转换为 index.php?pagename=product&product_id=123
  2. 刷新 Rewrite Rules: 在插件激活和停用时,都刷新了 Rewrite Rules。
  3. 添加查询变量: 添加了一个名为 product_id 的查询变量,以便 WordPress 可以正确地解析 URL。

解释:

  • register_activation_hook()register_deactivation_hook() 是 WordPress 提供的函数,用于在插件激活和停用时执行特定的代码。
  • flush_rewrite_rules() 是刷新 Rewrite Rules 的函数。 它会重新生成 .htaccess 文件 (或者 Nginx 配置文件)。
  • add_filter( 'query_vars', 'my_plugin_query_vars' ) 用于添加查询变量。 默认情况下,WordPress 只会解析一些预定义的查询变量,例如 s (搜索关键词) 和 p (文章 ID)。 如果需要使用自定义的查询变量,例如 product_id,就需要使用 add_filter( 'query_vars', ... ) 来添加它们。 否则,WordPress 会忽略这些查询变量。

7. WP_Rewrite的内部结构:深入了解

为了更深入地了解 WP_Rewrite,我们来看一下它的一些重要的属性和方法:

属性/方法 描述
rules 一个关联数组,包含了所有的 Rewrite Rules。 键是正则表达式,值是替换规则。
permalink_structure Permalink 结构。 例如 /blog/%postname%/
use_trailing_slashes 是否在 URL 的末尾添加斜杠。
rewrite_rules() 获取当前的 Rewrite Rules 数组。
generate_rewrite_rules() 根据 Permalink 结构生成 Rewrite Rules。
mod_rewrite_rules() 生成 .htaccess 文件 (或者 Nginx 配置文件) 的内容。
flush_rules() 刷新 Rewrite Rules。 删除现有的 Rewrite Rules,重新生成 .htaccess 文件 (或者 Nginx 配置文件),并更新 Rewrite Rules 的缓存。
wp_rewrite_rules() (已弃用) 一个全局变量,包含了所有的 Rewrite Rules。 现在应该使用 $wp_rewrite->rules 属性来获取 Rewrite Rules。

8. Rewrite Rules的优先级:先来后到?

Rewrite Rules 的顺序非常重要,因为 WordPress 会按照顺序依次匹配 Rewrite Rules。 如果多个 Rewrite Rules 都匹配同一个 URL,那么只有第一个匹配的 Rewrite Rule 会生效。

  • 'top' 参数: 通过 add_rewrite_rule(..., 'top') 添加的 Rewrite Rules 会被添加到 Rewrite Rules 数组的顶部,也就是优先级最高。
  • 'bottom' 参数: 通过 add_rewrite_rule(..., 'bottom') 添加的 Rewrite Rules 会被添加到 Rewrite Rules 数组的底部,也就是优先级最低。
  • 主题和插件的加载顺序: 主题和插件的加载顺序也会影响 Rewrite Rules 的优先级。 先加载的主题或插件添加的 Rewrite Rules 的优先级会更高。

最佳实践:

  • 尽量将自定义的 Rewrite Rules 添加到 Rewrite Rules 数组的顶部,以确保它们能够优先匹配。
  • 避免添加过于宽泛的 Rewrite Rules,以免影响 WordPress 的性能和安全性。
  • 在开发过程中,可以使用 WP_DEBUG 常量来调试 Rewrite Rules。 启用 WP_DEBUG 后,WordPress 会在页面底部显示当前的 Rewrite Rules 数组。

9. 调试Rewrite Rules:排查爱情路上的坎坷

Rewrite Rules 可能会很复杂,调试起来也比较困难。 这里提供一些调试 Rewrite Rules 的技巧:

  1. 使用 WP_DEBUG: 启用 WP_DEBUG 常量后,WordPress 会在页面底部显示当前的 Rewrite Rules 数组。 这可以帮助你检查 Rewrite Rules 是否正确添加,以及它们的顺序是否正确。
define( 'WP_DEBUG', true );
  1. 使用 var_dump()print_r(): 可以使用 var_dump()print_r() 函数来输出 WP_Rewrite->rules 属性的内容,以便更详细地了解 Rewrite Rules 的结构。
global $wp_rewrite;
echo '<pre>';
var_dump( $wp_rewrite->rules );
echo '</pre>';
  1. 使用 flush_rewrite_rules( true ): 在开发过程中,可以使用 flush_rewrite_rules( true ) 函数来强制刷新 Rewrite Rules。 这可以确保 Rewrite Rules 的修改能够立即生效。
flush_rewrite_rules( true ); // 强制刷新,不依赖于 update_option
  1. 检查 .htaccess 文件 (或者 Nginx 配置文件): 可以手动检查 .htaccess 文件 (或者 Nginx 配置文件) 的内容,以确保 Rewrite Rules 已经正确地添加到配置文件中。

  2. 使用 Rewrite Analyzer 插件: 有一些 WordPress 插件可以帮助你分析 Rewrite Rules,例如 "Rewrite Analyzer"。

10. 总结:Rewrite Rules的爱情真谛

add_rewrite_rule() 函数是 WordPress 提供给开发者添加自定义 Rewrite Rules 的入口。 它通过 rewrite_rules_array Hook 将新的 Rewrite Rules 添加到 WP_Rewrite 对象中。 WP_Rewrite 对象负责存储和管理 Rewrite Rules,并根据这些规则将请求的 URL 转换为 WordPress 可以理解的查询字符串。

理解 add_rewrite_rule() 函数和 WP_Rewrite 对象的工作原理,可以帮助你更好地自定义 WordPress 的 URL 结构,并创建更友好的用户体验。

希望今天的课程能帮助大家更好地理解 WordPress 的 Rewrite Rules。 祝大家都能找到属于自己的 Rewrite Rules 爱情! 下课!

发表回复

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