剖析 WordPress `do_action_ref_array()` 和 `apply_filters_ref_array()` 函数源码:如何通过引用传递参数。

各位观众,早上好!今天咱们来聊聊 WordPress 里两个有点儿“神秘”但又非常实用的函数:do_action_ref_array()apply_filters_ref_array()。 别怕名字长,其实搞明白它们的工作原理,你就能更好地理解 WordPress 的钩子机制,还能写出更灵活、更强大的插件和主题。

咱们今天就来扒一扒它们的源码,看看它们是如何通过引用传递参数的,顺便也聊聊引用传递的好处和需要注意的地方。准备好了吗?咱们开始!

一、 钩子机制:WordPress 的灵魂

在深入 do_action_ref_array()apply_filters_ref_array() 之前,先简单回顾一下 WordPress 的钩子机制。 钩子,简单来说,就是 WordPress 预留的一些“接口”,允许插件和主题在特定的时间点插入自己的代码,从而改变 WordPress 的行为或输出。

钩子分为两种:

  • 动作(Actions): 允许你执行一些操作,比如在文章发布后发送邮件,或者在页面底部添加一段自定义的 HTML 代码。
  • 过滤器(Filters): 允许你修改数据,比如修改文章标题,或者过滤评论内容。

do_action()apply_filters() 是 WordPress 中最常用的触发钩子的函数。 但是,当我们需要传递多个参数,尤其是需要修改这些参数时,do_action_ref_array()apply_filters_ref_array() 就派上用场了。

二、do_action_ref_array():触发动作,引用传递

do_action_ref_array() 函数用于触发一个动作钩子,并将参数以引用的方式传递给挂载到该钩子上的函数。

源码如下(简化版,方便理解):

function do_action_ref_array( $hook_name, $args ) {
    global $wp_filter, $wp_actions, $wp_current_filter;

    if ( ! isset( $wp_filter[ $hook_name ] ) ) {
        return;
    }

    $wp_current_filter[] = $hook_name;

    $wp_actions[ $hook_name ] = isset( $wp_actions[ $hook_name ] ) ? ++$wp_actions[ $hook_name ] : 1;

    foreach ( (array) $wp_filter[ $hook_name ]->callbacks as $priority => $functions ) {
        foreach ( (array) $functions as $function ) {
            $args_to_pass = $args; // 关键点1:复制参数数组

            if ( is_callable( $function['function'] ) ) {
                call_user_func_array( $function['function'], $args_to_pass ); // 关键点2:调用函数,参数数组
            }
        }
    }

    array_pop( $wp_current_filter );
}

源码解读:

  1. 检查钩子是否存在: 首先,函数会检查 $wp_filter 全局变量中是否存在 $hook_name 对应的钩子。如果不存在,说明没有函数挂载到这个钩子上,直接返回。
  2. 记录当前钩子: $wp_current_filter 用于记录当前正在执行的钩子,方便调试。
  3. 统计动作执行次数: $wp_actions 用于统计每个动作被执行的次数。
  4. 遍历回调函数: 遍历挂载到该钩子上的所有回调函数,按照优先级顺序执行。
  5. 复制参数数组: $args_to_pass = $args; 这是关键的一步!do_action_ref_array 内部复制了传入的参数数组 $args。 虽然传递的是数组,但数组中的元素传递给回调函数时,默认是值传递
  6. 调用回调函数: call_user_func_array( $function['function'], $args_to_pass ); 使用 call_user_func_array() 函数调用回调函数,并将参数数组 $args_to_pass 传递给它。 注意: 即使使用了 call_user_func_array,默认情况下,传递给回调函数的仍然是参数的副本

问题来了:引用传递在哪里?

你可能要问了,明明函数名里有 "ref"(reference,引用的意思),为什么源码里看起来还是值传递呢? 这是因为 do_action_ref_array() 本身并没有直接实现引用传递。 它只是将参数数组传递给了回调函数,而回调函数内部如何处理这些参数,取决于回调函数的定义。

要实现引用传递,需要在回调函数的参数列表中使用 & 符号。

举个例子:

// 假设我们有一个动作钩子 'my_action'

// 定义一个回调函数,使用引用传递
function my_callback( &$arg1, &$arg2 ) {
    $arg1 = 'Modified Arg1';
    $arg2 = 'Modified Arg2';
}

// 添加动作
add_action( 'my_action', 'my_callback', 10, 2 ); // 10 是优先级, 2 是参数个数

// 定义参数
$arg1 = 'Original Arg1';
$arg2 = 'Original Arg2';

$args = array( &$arg1, &$arg2 );  // 注意这里,将参数以引用传递到数组中

// 触发动作
do_action_ref_array( 'my_action', $args );

// 输出结果
echo $arg1; // 输出:Modified Arg1
echo $arg2; // 输出:Modified Arg2

代码解释:

  1. 回调函数: my_callback() 函数的参数列表中使用了 & 符号,表示 $arg1$arg2 是引用传递。 这意味着在函数内部修改 $arg1$arg2 的值,会直接影响到函数外部的 $arg1$arg2 变量。
  2. 参数数组: 创建参数数组 $args 时,需要使用 &$arg1&$arg2 将变量以引用的方式放入数组中。
  3. 触发动作: do_action_ref_array() 函数将 $args 数组传递给 my_callback() 函数。
  4. 结果: 由于使用了引用传递,my_callback() 函数修改了 $arg1$arg2 的值,所以最终输出的是 "Modified Arg1" 和 "Modified Arg2"。

表格总结:do_action_ref_array() 的使用要点

要点 说明
函数本身 do_action_ref_array() 只是负责触发动作钩子,并将参数数组传递给回调函数。
引用传递的实现 要实现引用传递,需要在回调函数的参数列表中使用 & 符号,并且在创建参数数组时,将变量以引用的方式放入数组中
默认是值传递 即使使用了 do_action_ref_array(),默认情况下,传递给回调函数的仍然是参数的副本。 只有在回调函数参数列表中使用了 & 符号,才能实现引用传递。
应用场景 需要在回调函数中修改参数的值,并且希望这些修改能够影响到函数外部的变量。
注意事项 滥用引用传递可能会导致代码难以理解和维护。 只有在确实需要修改参数的值,并且需要在函数外部访问这些修改后的值时,才应该使用引用传递。

三、apply_filters_ref_array():应用过滤器,引用传递

apply_filters_ref_array() 函数用于应用一个过滤器钩子,并将参数以引用的方式传递给挂载到该钩子上的函数。 与 do_action_ref_array() 类似,它也需要回调函数配合才能实现真正的引用传递。

源码如下(简化版,方便理解):

function apply_filters_ref_array( $hook_name, $args ) {
    global $wp_filter, $wp_current_filter, $merged_filters;

    if ( ! isset( $wp_filter[ $hook_name ] ) ) {
        return $args[0]; // 关键点:如果没有过滤器,直接返回第一个参数
    }

    $wp_current_filter[] = $hook_name;

    $filtered = $args[0]; // 初始值

    foreach ( (array) $wp_filter[ $hook_name ]->callbacks as $priority => $functions ) {
        foreach ( (array) $functions as $function ) {
            $args_to_pass = $args; // 关键点1:复制参数数组

            $result = call_user_func_array( $function['function'], $args_to_pass );  // 关键点2:调用函数,参数数组

            if ( ! is_null( $result ) ) {
                $filtered = $result;
                $args[0] = $result; // 更新第一个参数
            }
        }
    }

    array_pop( $wp_current_filter );

    return $filtered; // 返回最终的过滤结果
}

源码解读:

  1. 检查钩子是否存在:do_action_ref_array() 类似,首先检查 $wp_filter 全局变量中是否存在 $hook_name 对应的钩子。 如果不存在,说明没有函数挂载到这个钩子上,直接返回第一个参数
  2. 记录当前钩子: $wp_current_filter 用于记录当前正在执行的钩子,方便调试。
  3. 初始值: $filtered = $args[0]; 将参数数组的第一个元素作为初始值,后续的过滤器函数会依次修改这个值。
  4. 遍历回调函数: 遍历挂载到该钩子上的所有回调函数,按照优先级顺序执行。
  5. 复制参数数组: $args_to_pass = $args; 同样,apply_filters_ref_array() 内部也复制了传入的参数数组 $args。 数组中的元素传递给回调函数时,默认是值传递
  6. 调用回调函数: $result = call_user_func_array( $function['function'], $args_to_pass ); 使用 call_user_func_array() 函数调用回调函数,并将参数数组 $args_to_pass 传递给它。
  7. 更新过滤结果: if ( ! is_null( $result ) ) { $filtered = $result; $args[0] = $result; } 如果回调函数返回了非 null 的值,则将该值作为新的过滤结果,并更新 $args[0] 的值。 注意: 这里只更新了 $args 数组的第一个元素,也就是说,只有第一个参数会被传递给下一个过滤器函数。
  8. 返回最终结果: return $filtered; 返回最终的过滤结果。

do_action_ref_array() 一样, apply_filters_ref_array() 本身并没有直接实现引用传递。 要实现引用传递,需要在回调函数的参数列表中使用 & 符号。

举个例子:

// 假设我们有一个过滤器钩子 'my_filter'

// 定义一个回调函数,使用引用传递
function my_filter_callback( &$arg1, &$arg2 ) {
    $arg1 = 'Filtered Arg1';
    $arg2 = 'Filtered Arg2';
    return $arg1; // 返回过滤后的第一个参数
}

// 添加过滤器
add_filter( 'my_filter', 'my_filter_callback', 10, 2 );

// 定义参数
$arg1 = 'Original Arg1';
$arg2 = 'Original Arg2';

$args = array( &$arg1, &$arg2 ); // 注意这里,将参数以引用传递到数组中

// 应用过滤器
$filtered_arg1 = apply_filters_ref_array( 'my_filter', $args );

// 输出结果
echo $filtered_arg1; // 输出:Filtered Arg1
echo $arg1; // 输出:Filtered Arg1  (因为是引用传递)
echo $arg2; // 输出:Filtered Arg2  (因为是引用传递)

代码解释:

  1. 回调函数: my_filter_callback() 函数的参数列表中使用了 & 符号,表示 $arg1$arg2 是引用传递。
  2. 参数数组: 创建参数数组 $args 时,需要使用 &$arg1&$arg2 将变量以引用的方式放入数组中。
  3. 应用过滤器: apply_filters_ref_array() 函数将 $args 数组传递给 my_filter_callback() 函数。
  4. 结果: 由于使用了引用传递,my_filter_callback() 函数修改了 $arg1$arg2 的值,并且 $arg1 被作为过滤结果返回。 所以最终输出的是 "Filtered Arg1"、"Filtered Arg1" 和 "Filtered Arg2"。

表格总结:apply_filters_ref_array() 的使用要点

要点 说明
函数本身 apply_filters_ref_array() 只是负责应用过滤器钩子,并将参数数组传递给回调函数。
引用传递的实现 要实现引用传递,需要在回调函数的参数列表中使用 & 符号,并且在创建参数数组时,将变量以引用的方式放入数组中
默认是值传递 即使使用了 apply_filters_ref_array(),默认情况下,传递给回调函数的仍然是参数的副本。 只有在回调函数参数列表中使用了 & 符号,才能实现引用传递。
应用场景 需要在回调函数中修改参数的值,并且希望这些修改能够影响到函数外部的变量,并且需要将修改后的值作为过滤结果返回。
返回值 过滤器函数必须返回一个值,这个值将被作为下一个过滤器函数的输入,最终 apply_filters_ref_array() 函数会返回经过所有过滤器函数处理后的值。 如果过滤器函数返回 null,则会使用上一个过滤器函数返回的值。
注意事项 滥用引用传递可能会导致代码难以理解和维护。 只有在确实需要修改参数的值,并且需要在函数外部访问这些修改后的值时,才应该使用引用传递。
只更新第一个参数 apply_filters_ref_array() 只会更新 $args 数组的第一个元素,也就是说,只有第一个参数会被传递给下一个过滤器函数。 如果需要传递多个参数,可以在回调函数中修改 $args 数组的其他元素,但需要谨慎处理,避免出现意料之外的结果。

四、引用传递的优缺点:

优点:

  • 节省内存: 引用传递避免了复制参数的开销,尤其是在处理大型数据结构时,可以显著节省内存。
  • 修改数据: 允许在函数内部修改参数的值,并且这些修改会反映到函数外部。

缺点:

  • 代码可读性降低: 引用传递可能会使代码更难理解,因为变量的值可能会在函数内部被意外修改。
  • 潜在的副作用: 如果多个函数都通过引用传递访问同一个变量,一个函数对变量的修改可能会影响到其他函数,导致难以调试的错误。

五、总结:

do_action_ref_array()apply_filters_ref_array() 是 WordPress 中用于触发动作和应用过滤器的高级函数。 它们本身并没有直接实现引用传递,而是依赖于回调函数的参数列表中使用 & 符号来实现引用传递。

在使用这两个函数时,需要注意以下几点:

  • 明确是否真的需要引用传递: 只有在确实需要修改参数的值,并且需要在函数外部访问这些修改后的值时,才应该使用引用传递。
  • 谨慎使用引用传递: 滥用引用传递可能会导致代码难以理解和维护。
  • 注意参数数组的创建: 创建参数数组时,需要使用 &$arg 将变量以引用的方式放入数组中。
  • apply_filters_ref_array() 的返回值: 过滤器函数必须返回一个值,这个值将被作为下一个过滤器函数的输入。
  • apply_filters_ref_array() 只更新第一个参数: 注意 apply_filters_ref_array() 只会更新 $args 数组的第一个元素。

希望今天的讲解能够帮助你更好地理解 do_action_ref_array()apply_filters_ref_array() 函数,并在 WordPress 开发中更加得心应手! 谢谢大家!

发表回复

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