大家好,欢迎来到今天的“WordPress 钩子大冒险”讲座!我是你们的导游,老码农一枚,今天带大家深入 WordPress 的核心,扒一扒 do_action()
和 apply_filters()
这两个家伙的底裤,看看它们到底是怎么运作的。
准备好了吗? Let’s go!
第一幕:钩子的前世今生 (概念回顾,务必搞懂)
在开始源码探险之前,我们先快速回顾一下什么是 Action 和 Filter。你可以把 WordPress 想象成一个大型乐高玩具,Action 和 Filter 就是乐高上的连接点,允许你修改或扩展它的功能,而无需修改核心代码。
- Action (动作钩子): 像一个事件触发器。当 WordPress 执行到
do_action()
函数时,它会执行所有挂载到该 Action 上的函数。 你可以理解为 “当发生 X 事件时,执行 Y 个函数”。 - Filter (过滤器钩子): 允许你修改数据。 当 WordPress 执行到
apply_filters()
函数时,它会传递一个值,然后所有挂载到该 Filter 上的函数都会接收这个值,并有机会修改它,最后返回修改后的值。 你可以理解为 “用 Y 个函数来修改 X 的值”。
简单来说,Action 是“做事”,Filter 是“改东西”。
第二幕:do_action()
源码解剖 (Action 钩子的执行流程)
好,概念复习完毕,我们直接上代码,看看 do_action()
到底干了些啥:
// wp-includes/plugin.php
/**
* Execute functions hooked on a specific action hook.
*
* @since 0.71
*
* @param string $tag The name of the action to be executed.
* @param mixed ...$arg Optional. Additional arguments which are passed on to the
* functions hooked to the action.
* @return null Will return null if $tag does not exist in $wp_actions.
*/
function do_action( $tag, ...$arg ) {
global $wp_filter, $wp_actions, $wp_current_filter;
$wp_actions[ $tag ] = isset( $wp_actions[ $tag ] ) ? absint( $wp_actions[ $tag ] ) + 1 : 1;
// Do nothing if there's no hooks.
if ( ! isset( $wp_filter[ $tag ] ) ) {
return;
}
if ( empty( $wp_filter[ $tag ]->callbacks ) ) {
return;
}
// Prevents recursion if the action calls itself.
$wp_current_filter[] = $tag;
/**
* Fires functions attached to a specific action hook.
*
* @param mixed ...$arg Arguments passed to the functions.
*/
do_action_ref_array( $tag, $arg );
array_pop( $wp_current_filter );
}
别害怕,我们一行一行来解读:
-
global $wp_filter, $wp_actions, $wp_current_filter;
: 这行代码声明了三个全局变量。它们是do_action()
和apply_filters()
的核心数据结构:$wp_filter
: 一个超级数组,存储了所有已注册的 Action 和 Filter 的回调函数(也就是你用add_action()
和add_filter()
注册的函数)。它的结构大致是:['action_name' => WP_Hook object]
$wp_actions
: 一个简单的数组,记录了每个 Action 被触发的次数。 比如,$wp_actions['wp_head']
的值可能是 3,表示wp_head
这个 Action 已经被触发了 3 次。$wp_current_filter
: 一个数组,记录了当前正在执行的 Action 或 Filter 的名字。 用于防止递归调用(比如,一个 Action 的回调函数又触发了同一个 Action,就会形成死循环)。
-
$wp_actions[ $tag ] = isset( $wp_actions[ $tag ] ) ? absint( $wp_actions[ $tag ] ) + 1 : 1;
: 这行代码更新了$wp_actions
数组,记录了当前 Action 被触发的次数。 如果$wp_actions[$tag]
已经存在,就加 1;否则,就设置为 1。 -
if ( ! isset( $wp_filter[ $tag ] ) ) { return; }
: 这个判断至关重要。它检查$wp_filter
数组中是否存在以$tag
为键的元素。 如果不存在,说明没有任何函数挂载到这个 Action 上,所以直接返回,不做任何事情。 -
if ( empty( $wp_filter[ $tag ]->callbacks ) ) { return; }
: 如果存在$wp_filter[$tag]
,但其callbacks
属性为空,说明虽然定义了这个 Action ,但是没有注册任何回调函数。 直接返回。 -
$wp_current_filter[] = $tag;
: 将当前 Action 的名字添加到$wp_current_filter
数组中,用于防止递归调用。 -
do_action_ref_array( $tag, $arg );
: 这行代码才是真正执行 Action 的地方。 它调用了do_action_ref_array()
函数,并将 Action 的名字$tag
和传递给do_action()
的参数$arg
传递给它。 -
array_pop( $wp_current_filter );
: 执行完毕后,从$wp_current_filter
数组中移除当前 Action 的名字。
你看,do_action()
本身并没有做什么复杂的事情。 它主要负责检查 Action 是否存在,以及防止递归调用,然后把真正的执行任务交给 do_action_ref_array()
。
do_action_ref_array()
源码深度挖掘 (Action 钩子的执行内核)
接下来,我们深入 do_action_ref_array()
的源码:
// wp-includes/plugin.php
/**
* Execute functions hooked on a specific action hook, specifying arguments in an array.
*
* @since 2.1.0
*
* @param string $tag The name of the action to be executed.
* @param array $args The arguments supplied to the functions hooked to {@see $tag}.
* @return null
*/
function do_action_ref_array( $tag, $args ) {
global $wp_filter, $wp_actions, $wp_current_filter;
if ( ! isset( $wp_filter[ $tag ] ) ) {
return;
}
$wp_filter[ $tag ]->do_action( $args );
}
简直不敢相信,do_action_ref_array()
函数居然这么简单! 它只做了两件事:
-
if ( ! isset( $wp_filter[ $tag ] ) ) { return; }
: 再次检查 Action 是否存在,如果不存在,直接返回。 -
$wp_filter[ $tag ]->do_action( $args );
: 调用$wp_filter[$tag]
对象的do_action()
方法,并将参数$args
传递给它。
等等,$wp_filter[$tag]
是什么? 前面说过,它是 WP_Hook
对象。 所以,真正的 Action 执行逻辑,其实藏在 WP_Hook
类的 do_action()
方法里!
WP_Hook::do_action()
源码探秘 (Action 钩子的终极真相)
现在,我们来到 WP_Hook
类的 do_action()
方法:
// wp-includes/class-wp-hook.php
/**
* Executes the hook.
*
* @param array $args The arguments to pass to the callbacks.
*/
public function do_action( $args ) {
$res = $this->apply_filters( '', $args );
// `apply_filters()` always returns a value.
// We're only using it for its side-effects.
if ( null !== $res ) {
return;
}
}
我没看错吧? WP_Hook::do_action()
竟然调用了 apply_filters()
方法!
等等,这有点绕。 Action 怎么会调用 Filter 的方法呢?
别忘了,Action 的本质是“做事”,而“做事”其实也是一种“修改”——修改了程序的运行状态。 WP_Hook
类把 Action 和 Filter 的底层实现统一起来,用 apply_filters()
来执行回调函数,只是 Action 的 apply_filters()
不会修改任何数据(它传递的是空字符串 ''
)。
Action 钩子的执行流程总结
为了方便大家理解,我们用一个表格来总结 Action 钩子的执行流程:
步骤 | 函数/方法 | 作用 |
---|---|---|
1 | do_action() |
检查 Action 是否存在,记录 Action 被触发的次数,防止递归调用,然后调用 do_action_ref_array() 。 |
2 | do_action_ref_array() |
再次检查 Action 是否存在,然后调用 $wp_filter[$tag] 对象的 do_action() 方法。 |
3 | WP_Hook::do_action() |
调用 apply_filters() 方法,将空字符串 '' 和参数传递给它。 |
4 | WP_Hook::apply_filters() |
遍历所有挂载到该 Action 上的回调函数,依次执行它们,并将参数传递给它们。(具体细节我们稍后在 Filter 的部分详细讲解) |
第三幕:apply_filters()
源码剖析 (Filter 钩子的执行流程)
了解了 Action 的执行流程,Filter 就简单多了。 我们直接上 apply_filters()
的源码:
// wp-includes/plugin.php
/**
* Calls the functions that have been added to a filter hook.
*
* @since 0.71
*
* @param string $tag The name of the filter hook.
* @param mixed $value The value to filter.
* @param mixed ...$arg Optional. Additional parameters to pass to the functions hooked to {@see $tag}.
* @return mixed The filtered value after all hooked functions are applied to it.
*/
function apply_filters( $tag, $value, ...$arg ) {
global $wp_filter, $wp_current_filter;
if ( ! isset( $wp_filter[ $tag ] ) ) {
return $value;
}
if ( empty( $wp_filter[ $tag ]->callbacks ) ) {
return $value;
}
$wp_current_filter[] = $tag;
$args = array_merge( array( $value ), $arg );
$filtered = $wp_filter[ $tag ]->apply_filters( $value, $args );
array_pop( $wp_current_filter );
return $filtered;
}
是不是感觉似曾相识? apply_filters()
的结构和 do_action()
非常相似:
-
global $wp_filter, $wp_current_filter;
: 声明全局变量,和do_action()
一样。 -
if ( ! isset( $wp_filter[ $tag ] ) ) { return $value; }
: 检查 Filter 是否存在。如果不存在,直接返回原始值$value
。 -
if ( empty( $wp_filter[ $tag ]->callbacks ) ) { return $value; }
: 如果存在$wp_filter[$tag]
,但其callbacks
属性为空,说明虽然定义了这个 Filter ,但是没有注册任何回调函数。 直接返回原始值$value
。 -
$wp_current_filter[] = $tag;
: 将当前 Filter 的名字添加到$wp_current_filter
数组中,用于防止递归调用。 -
$args = array_merge( array( $value ), $arg );
: 将原始值$value
和其他参数$arg
合并成一个数组$args
,传递给回调函数。 注意,原始值$value
始终是数组的第一个元素。 -
$filtered = $wp_filter[ $tag ]->apply_filters( $value, $args );
: 调用$wp_filter[$tag]
对象的apply_filters()
方法,并将原始值$value
和参数数组$args
传递给它。 -
array_pop( $wp_current_filter );
: 执行完毕后,从$wp_current_filter
数组中移除当前 Filter 的名字。 -
return $filtered;
: 返回经过所有回调函数过滤后的值$filtered
。
你看,apply_filters()
的核心逻辑也是调用 WP_Hook
类的 apply_filters()
方法。
WP_Hook::apply_filters()
源码揭秘 (Filter 钩子的核心算法)
现在,我们来看看 WP_Hook
类的 apply_filters()
方法:
// wp-includes/class-wp-hook.php
/**
* Runs the functions hooked on a specific filter hook.
*
* @param mixed $value The value to filter.
* @param array $args The array of parameters passed to the functions.
* @return mixed The filtered value after all hooked functions are applied to it.
*/
public function apply_filters( $value, $args ) {
$res = $value;
$this->iterations++;
foreach ( $this->callbacks as $priority => $functions ) {
ksort( $functions );
foreach ( $functions as $function ) {
$res = call_user_func_array( $function['function'], $args );
}
}
$this->iterations--;
return $res;
}
这才是 Filter 的核心算法! 我们来仔细分析:
-
$res = $value;
: 将原始值$value
赋值给变量$res
,用于存储过滤后的值。 -
$this->iterations++;
: 增加迭代次数,用于防止递归调用。 -
foreach ( $this->callbacks as $priority => $functions ) { ... }
: 遍历$this->callbacks
数组。$this->callbacks
数组存储了所有挂载到该 Filter 上的回调函数,按照优先级($priority
)进行分组。 -
ksort( $functions );
: 对同一优先级的回调函数进行排序。ksort()
函数按照键名(也就是回调函数的唯一标识符)对数组进行排序,确保同一优先级的回调函数按照注册顺序执行。 -
foreach ( $functions as $function ) { ... }
: 遍历同一优先级的回调函数。 -
$res = call_user_func_array( $function['function'], $args );
: 调用回调函数。$function['function']
存储了回调函数的名称或对象方法,$args
存储了传递给回调函数的参数数组。call_user_func_array()
函数会调用回调函数,并将参数数组传递给它,然后将回调函数的返回值赋值给$res
。 注意,每个回调函数都会接收上一个回调函数的返回值作为输入,这就是 Filter 的核心思想:通过一系列的函数,逐步修改原始值。 -
$this->iterations--;
: 减少迭代次数。 -
return $res;
: 返回经过所有回调函数过滤后的值$res
。
Filter 钩子的执行流程总结
同样,我们用一个表格来总结 Filter 钩子的执行流程:
步骤 | 函数/方法 | 作用 |
---|---|---|
1 | apply_filters() |
检查 Filter 是否存在,防止递归调用,将原始值和参数合并成数组,然后调用 $wp_filter[$tag] 对象的 apply_filters() 方法。 |
2 | WP_Hook::apply_filters() |
遍历所有挂载到该 Filter 上的回调函数,按照优先级和注册顺序依次执行它们,并将上一个回调函数的返回值作为输入传递给下一个回调函数。 最后,返回经过所有回调函数过滤后的值。 |
第四幕:实例演示 (用代码说话)
光说不练假把式,我们来写几个简单的例子,加深理解:
Action 示例:
// 注册一个 Action
add_action( 'wp_footer', 'my_footer_function' );
function my_footer_function() {
echo '<p>This is my custom footer message.</p>';
}
// 在 WordPress 的某个地方触发该 Action
do_action( 'wp_footer' );
这段代码会在页面的 <footer>
标签之前输出一段文字。 当 WordPress 执行到 do_action( 'wp_footer' )
时,它会找到所有挂载到 wp_footer
Action 上的函数,然后依次执行它们。
Filter 示例:
// 注册一个 Filter
add_filter( 'the_content', 'my_content_filter' );
function my_content_filter( $content ) {
$content .= '<p>This is my custom content message.</p>';
return $content;
}
// 在 WordPress 的某个地方应用该 Filter
$content = apply_filters( 'the_content', $content );
这段代码会在文章内容的末尾添加一段文字。 当 WordPress 执行到 apply_filters( 'the_content', $content )
时,它会将文章内容 $content
传递给所有挂载到 the_content
Filter 上的函数,然后依次执行它们,并将上一个函数的返回值作为输入传递给下一个函数。 最后,返回经过所有函数过滤后的文章内容。
第五幕:总结与思考 (知识点梳理)
今天我们深入剖析了 WordPress 的 do_action()
和 apply_filters()
函数的源码,了解了它们的执行流程。 希望通过今天的讲座,大家能够对 WordPress 的钩子机制有更深入的理解。
以下是一些关键知识点:
do_action()
用于触发 Action,apply_filters()
用于修改数据。$wp_filter
数组存储了所有已注册的 Action 和 Filter 的回调函数。WP_Hook
类是 Action 和 Filter 的核心实现,它负责存储和执行回调函数。- Filter 的核心思想是通过一系列的函数,逐步修改原始值。
思考题:
- 为什么 Action 的
WP_Hook::do_action()
方法要调用apply_filters()
? - 如何利用 Action 和 Filter 来扩展 WordPress 的功能?
- 如果多个函数挂载到同一个 Action 或 Filter 上,它们的执行顺序是什么?
- 如何防止 Action 或 Filter 形成递归调用?
希望大家能够带着这些问题,继续探索 WordPress 的源码,发现更多有趣的秘密!
今天的讲座就到这里,谢谢大家!