咳咳,大家好!今天咱们来聊聊 WordPress 里两个超级重要的家伙—— add_action
和 add_filter
。它们就像 WordPress 的神经系统,连接着各个功能模块,让插件和主题可以自由地扩展和修改核心行为。 准备好了吗?系好安全带,咱们要深入源码,揭开它们神秘的面纱了!
一、钩子:WordPress 的灵活之源
在深入 add_action
和 add_filter
之前,先要理解“钩子 (Hook)”这个概念。你可以把钩子想象成 WordPress 代码中的一些预留的“插槽”,允许你在特定的时间点插入自己的代码,改变 WordPress 的默认行为。
WordPress 主要有两种类型的钩子:
- Actions (动作): 在特定的事件发生时执行你的代码。比如,在文章发布之后,你可以用一个 action 钩子来发送邮件通知。
- Filters (过滤器): 允许你修改数据。比如,你可以用一个 filter 钩子来修改文章的内容,或者改变主题的标题。
二、add_action
:执行你的代码
add_action
函数的作用是注册一个函数,让它在特定的 action 钩子被触发时执行。 让我们先看看 add_action
的简化版原型:
function add_action( string $tag, callable $function_to_add, int $priority = 10, int $accepted_args = 1 ): bool;
$tag
: 要挂载的 action 钩子的名称 (字符串)。$function_to_add
: 要执行的函数名 (可调用类型)。$priority
: 执行优先级 (整数)。数值越小,优先级越高,越先执行。默认值是 10。$accepted_args
: 传递给函数的参数个数 (整数)。默认值是 1。
现在,让我们看看 WordPress 核心代码中 add_action
的实际实现(简化版,重点突出):
global $wp_filter, $wp_actions, $merged_filters;
function add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
global $wp_filter, $wp_actions;
$idx = _wp_filter_build_unique_id( $tag, $function_to_add, $priority );
$wp_filter[ $tag ][ $priority ][ $idx ] = array(
'function' => $function_to_add,
'accepted_args' => $accepted_args
);
unset( $wp_actions[ $tag ] ); // May be doing it for the first time, don't overwrite!
return true;
}
代码解读:
-
全局变量:
global $wp_filter, $wp_actions;
声明了两个全局变量$wp_filter
和$wp_actions
。$wp_filter
是一个多维数组,用于存储所有的 action 和 filter 钩子,而$wp_actions
则用于记录 action 钩子被触发的次数(用于调试和性能分析)。 -
_wp_filter_build_unique_id()
: 这个函数(我们稍后会详细讨论)的作用是为每个 action/filter 函数生成一个唯一的 ID。 即使你多次用相同的函数挂载到同一个钩子上,它也会确保每个实例都有唯一的 ID。 -
存储钩子信息:
$wp_filter[ $tag ][ $priority ][ $idx ] = array(...)
这行代码是关键。它将钩子信息存储到$wp_filter
数组中。$wp_filter
的结构是这样的:$wp_filter = array( 'hook_name' => array( priority_value => array( 'unique_id' => array( 'function' => 'the_callback_function', 'accepted_args' => 1 ) ) ) );
hook_name
: 钩子的名称 (例如,'publish_post'
,'wp_head'
)。priority_value
: 优先级 (例如,10
,20
)。unique_id
: 函数的唯一 ID (例如,'my_function_10'
)。function
: 要执行的函数 (例如,'my_function'
)。accepted_args
: 函数接受的参数个数。
-
unset( $wp_actions[ $tag ] );
: 清除$wp_actions
数组中对应$tag
键的值。这行代码确保如果$tag
是第一次被添加到 action 中,不会覆盖$wp_actions
中可能存在的其他值。
三、add_filter
:修改你的数据
add_filter
函数与 add_action
非常相似,但它用于注册一个函数,该函数接收一个值作为输入,修改它,然后返回修改后的值。 让我们先看看 add_filter
的简化版原型:
function add_filter( string $tag, callable $function_to_add, int $priority = 10, int $accepted_args = 1 ): bool;
参数与 add_action
完全相同。 现在,让我们看看 WordPress 核心代码中 add_filter
的实际实现(简化版,重点突出):
global $wp_filter, $wp_actions, $merged_filters;
function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
global $wp_filter;
$idx = _wp_filter_build_unique_id( $tag, $function_to_add, $priority );
$wp_filter[ $tag ][ $priority ][ $idx ] = array(
'function' => $function_to_add,
'accepted_args' => $accepted_args
);
return true;
}
代码解读:
你会发现 add_filter
的代码几乎与 add_action
完全相同! 关键的区别在于它们的使用方式。add_filter
主要用于修改数据,而 add_action
主要用于执行代码。
四、_wp_filter_build_unique_id()
:生成唯一的 ID
现在,让我们深入了解一下 _wp_filter_build_unique_id()
函数,它负责生成 action/filter 函数的唯一 ID。 这是一个非常重要的函数,因为它确保即使你多次用相同的函数挂载到同一个钩子上,每个实例都有唯一的 ID。
function _wp_filter_build_unique_id( $tag, $function, $priority ) {
static $filter_id_count = 0;
if ( is_string( $function ) ) {
return $function;
}
if ( is_object( $function ) ) {
// Closures are currently implemented as objects
$function = array( $function, '' );
} else {
$function = (array) $function;
}
if ( is_object( $function[0] ) ) {
return spl_object_hash( $function[0] ) . $function[1];
} elseif ( is_string( $function[0] ) ) {
return $function[0] . '::' . $function[1];
}
}
代码解读:
-
静态变量:
static $filter_id_count = 0;
使用静态变量来跟踪已经生成的 ID 数量。 这确保了每次调用该函数时,都会生成一个新的唯一 ID。 但是,现代 WordPress 已经不再依赖此变量。 -
处理不同类型的函数: 该函数可以处理以下类型的函数:
- 字符串 (函数名): 如果
$function
是一个字符串,则直接返回该字符串作为 ID。 - 对象 (闭包): 如果
$function
是一个对象 (通常是闭包),则使用spl_object_hash()
函数生成该对象的唯一哈希值,并将其作为 ID。 - 数组 (类方法/静态方法): 如果
$function
是一个数组,则它表示一个类方法或静态方法。在这种情况下,函数会将类名和方法名连接起来,形成 ID。
- 字符串 (函数名): 如果
-
生成 ID: 根据函数的类型,函数会生成一个唯一的 ID,并将其返回。
五、do_action
和 apply_filters
:触发钩子
现在我们知道如何使用 add_action
和 add_filter
来注册钩子函数了,接下来让我们看看如何触发这些钩子。
do_action()
: 用于触发 action 钩子。apply_filters()
: 用于触发 filter 钩子。
do_action()
function do_action( string $hook_name, mixed ...$args ) {
global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
$wp_actions[ $hook_name ] = isset( $wp_actions[ $hook_name ] ) ? absint( $wp_actions[ $hook_name ] ) + 1 : 1;
// Do 'all' actions first.
if ( isset( $wp_filter['all'] ) ) {
$wp_current_filter[] = $hook_name;
_wp_call_all_hook( $args );
}
if ( ! isset( $wp_filter[ $hook_name ] ) ) {
if ( isset( $wp_filter['all'] ) ) {
array_pop( $wp_current_filter );
}
return;
}
if ( ! isset( $wp_filter['all'] ) ) {
$wp_current_filter[] = $hook_name;
}
// Sort.
if ( ! isset( $merged_filters[ $hook_name ] ) ) {
ksort( $wp_filter[ $hook_name ] );
$merged_filters[ $hook_name ] = true;
}
reset( $wp_filter[ $hook_name ] );
foreach ( (array) current( $wp_filter[ $hook_name ] ) as $the_ ) {
if ( ! is_null( $the_['function'] ) ) {
call_user_func_array( $the_['function'], $args );
}
}
array_pop( $wp_current_filter );
}
代码解读:
-
全局变量: 声明了多个全局变量,包括
$wp_filter
(存储钩子信息),$wp_actions
(记录 action 钩子被触发的次数),$merged_filters
(用于优化排序), 和$wp_current_filter
(用于跟踪当前正在执行的钩子,用于防止循环调用)。 -
$wp_actions
更新:$wp_actions[ $hook_name ] = ...
这行代码递增了$wp_actions
数组中对应$hook_name
键的值,用于记录 action 钩子被触发的次数。 -
_wp_call_all_hook()
: 如果存在all
钩子,则调用_wp_call_all_hook()
函数,将所有参数传递给它。all
钩子是一个特殊的钩子,它会在所有其他 action 钩子被触发之前被触发。 -
检查钩子是否存在:
if ( ! isset( $wp_filter[ $hook_name ] ) ) { return; }
如果$wp_filter
数组中不存在$hook_name
键,则表示没有函数挂载到该钩子上,直接返回。 -
排序:
if ( ! isset( $merged_filters[ $hook_name ] ) ) { ksort( $wp_filter[ $hook_name ] ); ... }
如果$merged_filters
数组中不存在$hook_name
键,则表示该钩子的函数尚未排序。ksort()
函数用于按照优先级对$wp_filter[ $hook_name ]
数组进行排序。 -
循环执行函数:
foreach ( (array) current( $wp_filter[ $hook_name ] ) as $the_ ) { ... }
这行代码循环遍历$wp_filter[ $hook_name ]
数组中的所有函数,并使用call_user_func_array()
函数调用它们。call_user_func_array()
函数允许你使用一个数组作为参数来调用一个函数。
apply_filters()
function apply_filters( string $hook_name, mixed $value, mixed ...$args ) {
global $wp_filter, $merged_filters, $wp_current_filter;
$wp_current_filter[] = $hook_name;
$args = array_slice( func_get_args(), 1 );
if ( ! isset( $wp_filter[ $hook_name ] ) ) {
array_pop( $wp_current_filter );
return $value;
}
if ( ! isset( $merged_filters[ $hook_name ] ) ) {
ksort( $wp_filter[ $hook_name ] );
$merged_filters[ $hook_name ] = true;
}
reset( $wp_filter[ $hook_name ] );
do {
foreach ( (array) current( $wp_filter[ $hook_name ] ) as $the_ ) {
if ( ! is_null( $the_['function'] ) ) {
$args[0] = $value;
$value = call_user_func_array( $the_['function'], array_slice( $args, 0, (int) $the_['accepted_args'] ) );
}
}
} while ( next( $wp_filter[ $hook_name ] ) !== false );
array_pop( $wp_current_filter );
return $value;
}
代码解读:
-
全局变量: 与
do_action()
类似,声明了多个全局变量。 -
参数处理:
$args = array_slice( func_get_args(), 1 );
这行代码获取传递给apply_filters()
函数的所有参数,并将它们存储到$args
数组中。array_slice()
函数用于从一个数组中提取一部分。 -
检查钩子是否存在:
if ( ! isset( $wp_filter[ $hook_name ] ) ) { return $value; }
如果$wp_filter
数组中不存在$hook_name
键,则表示没有函数挂载到该钩子上,直接返回原始值$value
。 -
排序: 与
do_action()
类似,如果该钩子的函数尚未排序,则使用ksort()
函数按照优先级对$wp_filter[ $hook_name ]
数组进行排序。 -
循环执行函数:
foreach ( (array) current( $wp_filter[ $hook_name ] ) as $the_ ) { ... }
这行代码循环遍历$wp_filter[ $hook_name ]
数组中的所有函数,并使用call_user_func_array()
函数调用它们。 关键的区别在于,apply_filters
会将前一个函数返回的值作为下一个函数的输入。
六、总结:钩子的魔力
add_action
和 add_filter
是 WordPress 插件和主题开发的核心。 它们允许你以一种非常灵活的方式扩展和修改 WordPress 的核心行为,而无需直接修改核心代码。 通过理解它们的内部工作原理,你可以更好地利用它们,构建更强大、更灵活的 WordPress 应用。
函数 | 用途 | 核心机制 |
---|---|---|
add_action |
注册一个函数,在特定的 action 钩子被触发时执行。 | 将函数信息存储到全局 $wp_filter 数组中,按照钩子名称和优先级进行组织。 |
add_filter |
注册一个函数,修改数据。 | 与 add_action 类似,将函数信息存储到全局 $wp_filter 数组中。 |
do_action |
触发 action 钩子,执行挂载到该钩子上的所有函数。 | 从 $wp_filter 数组中检索与指定钩子名称匹配的所有函数,按照优先级顺序执行它们。 |
apply_filters |
触发 filter 钩子,并允许挂载的函数修改传递给钩子的值。 | 从 $wp_filter 数组中检索与指定钩子名称匹配的所有函数,按照优先级顺序执行它们。每个函数接收前一个函数返回的值作为输入,并将修改后的值传递给下一个函数。最后,apply_filters 返回经过所有函数修改后的最终值。 |
希望今天的讲解对你有所帮助! 如果还有其他问题,随时提问。下次再见!