咳咳,各位同学,早上好!我是你们今天的WordPress源码小讲师,咱们今天来聊聊WordPress的add_action()
和add_filter()
这两个神器的内部机制。
咱们都知道,WordPress 的插件机制非常强大,很大程度上归功于 action 和 filter 这两个钩子。它们允许我们在不修改 WordPress 核心代码的情况下,插入我们自己的代码,改变程序的行为。而 add_action()
和 add_filter()
就是用来注册这些钩子的函数。
今天咱们就扒开它们的源码,看看它们到底是怎么工作的,特别是那个神秘的 $wp_filter
全局数组。
1. add_action()
和 add_filter()
:表面兄弟,实则一家
首先,我们来看看add_action()
和add_filter()
的源码(为了方便阅读,我简化了一些错误处理和注释):
// wp-includes/plugin.php
function add_action( $hook, $function_to_add, $priority = 10, $accepted_args = 1 ) {
return add_filter( $hook, $function_to_add, $priority, $accepted_args );
}
function add_filter( $hook, $function_to_add, $priority = 10, $accepted_args = 1 ) {
global $wp_filter, $wp_actions, $merged_filters;
$idx = _wp_filter_build_unique_id( $hook, $function_to_add, $priority );
$wp_filter[ $hook ][ $priority ][ $idx ] = array(
'function' => $function_to_add,
'accepted_args' => $accepted_args
);
unset( $merged_filters[ $hook ] );
return true;
}
看到没? add_action()
其实就是 add_filter()
的一个马甲!它们最终都调用了 add_filter()
函数。add_action()
存在的意义,仅仅是为了语义上的区分,让开发者更清楚地知道自己是在添加一个动作(action)还是一个过滤器(filter)。
它们接收的参数也很相似:
$hook
: 钩子的名称,也就是你要挂载代码的地方。$function_to_add
: 你要执行的函数或方法。$priority
: 优先级,数字越小,优先级越高,越先执行。默认是 10。$accepted_args
: 你的函数期望接收的参数数量。默认是 1。
2. 神秘的 $wp_filter
全局数组:钩子的藏身之所
现在,我们来重点关注 $wp_filter
这个全局数组。它是 WordPress 存储所有已注册钩子的核心数据结构。 说它神秘,是因为它是个多维数组,层层嵌套,刚开始看容易头晕。 咱们来拆解一下它的结构:
- 第一层 (Key:
$hook
): 以钩子的名称作为键名。比如'wp_head'
,'the_content'
,'save_post'
等等。 每个钩子名称对应的值,是一个数组,包含了所有注册到该钩子的函数信息。 - 第二层 (Key:
$priority
): 以优先级作为键名。比如1
,5
,10
,15
等等。 每个优先级对应的值,又是一个数组,包含了所有具有相同优先级的函数信息。 - 第三层 (Key:
$idx
): 这是一个唯一的ID,用于标识注册的函数。这个ID是通过_wp_filter_build_unique_id()
函数生成的,确保即使同一个函数多次注册到同一个钩子,也能被区分开。 - 第四层 (Value: Array): 这是一个数组,包含了两个关键信息:
'function'
: 你要执行的函数或方法。'accepted_args'
: 你的函数期望接收的参数数量。
用表格来表示,会更清晰:
层次 | 键 (Key) | 值 (Value) |
---|---|---|
第一层 | $hook |
Array (包含所有注册到该钩子的函数信息) |
第二层 | $priority |
Array (包含所有具有相同优先级的函数信息) |
第三层 | $idx |
Array (包含了函数信息) |
第四层 | 'function' |
callable (要执行的函数或方法) |
第四层 | 'accepted_args' |
int (函数期望接收的参数数量) |
3. _wp_filter_build_unique_id()
:生成唯一ID的幕后英雄
我们再来看看_wp_filter_build_unique_id()
这个函数,它负责为每个注册的函数生成一个唯一的ID。
// wp-includes/plugin.php
function _wp_filter_build_unique_id( $hook_name, $function_to_add, $priority ) {
static $filter_id_count = 0;
if ( is_string( $function_to_add ) ) {
return $function_to_add;
}
if ( is_object( $function_to_add ) ) {
// Closures are currently implemented as objects
$function_to_add = array( $function_to_add, '' );
} else {
$function_to_add = (array) $function_to_add;
}
if ( is_object( $function_to_add[0] ) ) {
return spl_object_hash( $function_to_add[0] ) . $function_to_add[1];
} elseif ( is_string( $function_to_add[0] ) ) {
return $function_to_add[0] . '::' . $function_to_add[1];
}
}
这个函数的作用是:
- 如果
$function_to_add
是一个字符串(函数名),直接返回函数名作为ID。 - 如果
$function_to_add
是一个对象(通常是闭包),使用spl_object_hash()
函数生成一个唯一的哈希值作为ID。 - 如果
$function_to_add
是一个数组(通常是类的方法),将类名和方法名拼接起来作为ID。
4. 案例分析:理解 $wp_filter
的数据结构
为了更好地理解 $wp_filter
的数据结构,我们来看一个例子。假设我们有以下代码:
add_action( 'wp_head', 'my_wp_head_function', 10 );
add_action( 'wp_head', 'another_wp_head_function', 5 );
add_action( 'wp_footer', 'my_wp_footer_function', 10 );
执行完这些代码后,$wp_filter
数组的结构大概是这样的(简化版):
$wp_filter = array(
'wp_head' => array(
5 => array(
'another_wp_head_function' => array(
'function' => 'another_wp_head_function',
'accepted_args' => 1
)
),
10 => array(
'my_wp_head_function' => array(
'function' => 'my_wp_head_function',
'accepted_args' => 1
)
)
),
'wp_footer' => array(
10 => array(
'my_wp_footer_function' => array(
'function' => 'my_wp_footer_function',
'accepted_args' => 1
)
)
)
);
可以看到,$wp_filter
数组的键名是钩子的名称('wp_head'
, 'wp_footer'
),每个钩子名称对应的值是一个数组,包含了所有注册到该钩子的函数信息。在 'wp_head'
钩子下,我们有两个优先级:5
和 10
。优先级 5
对应的是 another_wp_head_function
函数,优先级 10
对应的是 my_wp_head_function
函数。
5. 参数数量 $accepted_args
的作用
$accepted_args
参数指定了你的函数期望接收的参数数量。 这个参数在 WordPress 调用你的函数时会用到。 WordPress 会根据这个值,传递相应数量的参数给你的函数。
例如,如果你注册一个 filter 如下:
add_filter( 'the_content', 'my_content_filter', 10, 2 );
function my_content_filter( $content, $post_id ) {
// ...
return $content;
}
那么,WordPress 在调用 my_content_filter
函数时,会传递两个参数:$content
和 $post_id
。
如果 $accepted_args
的值大于实际传递给钩子的参数数量,你的函数将收到 NULL
值作为额外的参数。 如果 $accepted_args
的值小于实际传递给钩子的参数数量,你的函数将忽略多余的参数。
6. 如何利用 $wp_filter
调试
了解了 $wp_filter
的结构,我们就可以利用它来进行调试,查看某个钩子上注册了哪些函数,以及它们的优先级。
你可以使用以下代码来打印 $wp_filter
数组:
global $wp_filter;
echo '<pre>';
print_r( $wp_filter );
echo '</pre>';
将这段代码放在你的主题或插件中,刷新页面,你就可以看到 $wp_filter
数组的内容了。
7. remove_action()
和 remove_filter()
:钩子的卸载工
既然有注册钩子的函数,自然也有卸载钩子的函数,那就是remove_action()
和remove_filter()
。
function remove_action( $hook_name, $function_to_be_removed, $priority = 10 ) {
return remove_filter( $hook_name, $function_to_be_removed, $priority );
}
function remove_filter( $hook_name, $function_to_be_removed, $priority = 10 ) {
global $wp_filter, $merged_filters;
$idx = _wp_filter_build_unique_id( $hook_name, $function_to_be_removed, $priority );
unset( $wp_filter[ $hook_name ][ $priority ][ $idx ] );
if ( empty( $wp_filter[ $hook_name ][ $priority ] ) ) {
unset( $wp_filter[ $hook_name ][ $priority ] );
}
if ( empty( $wp_filter[ $hook_name ] ) ) {
unset( $wp_filter[ $hook_name ] );
}
unset( $merged_filters[ $hook_name ] );
return true;
}
这两个函数的作用是从 $wp_filter
数组中移除指定的函数。它们接收的参数和 add_action()
和 add_filter()
类似:
$hook_name
: 要移除的钩子的名称。$function_to_be_removed
: 要移除的函数或方法。$priority
: 优先级。
remove_filter()
函数会根据这些参数,在 $wp_filter
数组中找到对应的函数,并将其移除。
8. has_action()
和 has_filter()
:钩子的侦察兵
有时候,我们需要知道某个钩子上是否已经注册了函数,这时就可以使用 has_action()
和 has_filter()
函数。
function has_action( $hook_name, $function_to_check = false ) {
return has_filter( $hook_name, $function_to_check );
}
function has_filter( $hook_name, $function_to_check = false ) {
global $wp_filter;
$has = false;
if ( isset( $wp_filter[ $hook_name ] ) ) {
if ( false === $function_to_check ) {
$has = ! empty( $wp_filter[ $hook_name ] );
} else {
$idx = _wp_filter_build_unique_id( $hook_name, $function_to_check, false );
foreach ( (array) array_keys( $wp_filter[ $hook_name ] ) as $priority ) {
if ( isset( $wp_filter[ $hook_name ][ $priority ][ $idx ] ) ) {
$has = true;
break;
}
}
}
}
return $has;
}
这两个函数的作用是:
- 如果只传递
$hook_name
参数,它们会检查该钩子上是否注册了任何函数。 - 如果传递了
$hook_name
和$function_to_check
参数,它们会检查该钩子上是否注册了指定的函数。
9. do_action()
和 apply_filters()
:钩子的执行者
注册了钩子,最终目的是要执行它们。 do_action()
和 apply_filters()
就是负责执行钩子的函数。
// wp-includes/plugin.php
function do_action( $hook, ...$args ) {
global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
$wp_actions[ $hook ] = isset( $wp_actions[ $hook ] ) ? ++$wp_actions[ $hook ] : 1;
// Do 'all' actions first
if ( isset( $wp_filter['all'] ) ) {
$wp_current_filter[] = $hook;
_wp_call_all_hook( $args );
}
if ( ! isset( $wp_filter[ $hook ] ) ) {
return;
}
if ( ! isset( $merged_filters[ $hook ] ) ) {
ksort( $wp_filter[ $hook ] );
$merged_filters[ $hook ] = true;
}
reset( $wp_filter[ $hook ] );
if ( empty( $args ) ) {
$args = array('');
}
do {
foreach ( (array) current( $wp_filter[ $hook ] ) as $the_ )
if ( !is_null( $the_['function'] ) )
call_user_func_array( $the_['function'], $args );
} while ( next( $wp_filter[ $hook ] ) !== false );
array_pop( $wp_current_filter );
}
function apply_filters( $hook, $value, ...$args ) {
global $wp_filter, $merged_filters, $wp_current_filter;
$wp_current_filter[] = $hook;
if ( isset( $wp_filter['all'] ) ) {
$wp_current_filter[] = $hook;
_wp_call_all_hook( array_merge( array( $value ), $args ) );
}
if ( ! isset( $wp_filter[ $hook ] ) ) {
array_pop( $wp_current_filter );
return $value;
}
if ( ! isset( $merged_filters[ $hook ] ) ) {
ksort( $wp_filter[ $hook ] );
$merged_filters[ $hook ] = true;
}
reset( $wp_filter[ $hook ] );
do {
foreach ( (array) current( $wp_filter[ $hook ] ) as $the_ ) {
if ( !is_null( $the_['function'] ) ) {
$value = call_user_func_array( $the_['function'], array_merge( array( $value ), $args ) );
}
}
} while ( next( $wp_filter[ $hook ] ) !== false );
array_pop( $wp_current_filter );
return $value;
}
它们的主要区别是:
do_action()
: 执行一个动作,不返回任何值。通常用于在某个事件发生时,执行一些操作。apply_filters()
: 应用一个过滤器,返回修改后的值。通常用于修改某个变量的值。
这两个函数都会从 $wp_filter
数组中获取所有注册到指定钩子的函数,并按照优先级顺序执行它们。
10. 总结:钩子的本质
总的来说,WordPress 的 action 和 filter 机制,本质上是一种事件驱动的设计模式。 通过 add_action()
和 add_filter()
,我们可以将自己的代码注册到 WordPress 的特定事件(钩子)上。 当这些事件发生时,WordPress 会自动执行我们注册的代码。
$wp_filter
数组是这个机制的核心数据结构,它存储了所有已注册的钩子和函数信息。 理解了 $wp_filter
的结构,我们就能更好地理解 WordPress 的插件机制,编写更高效、更可靠的插件。
好了,今天的讲座就到这里。希望大家对 WordPress 的 add_action()
和 add_filter()
函数有了更深入的了解。 课后记得复习,下次考试要考的哦!
(鞠躬)