各位代码界的段子手们,早上好/下午好/晚上好!我是你们今天的WordPress源码解析师,人称“代码挖掘机”。今天咱们不挖矿,挖WordPress的源码,目标直指apply_filters()
这个小妖精,顺便把它孪生兄弟do_action()
拉出来遛遛。
咱们要像剥洋葱一样,一层一层地扒开它的源码,看看它到底是个什么玩意儿,以及它和do_action()
之间那些不得不说的故事,最后再聊聊它那让人头疼的返回值。准备好了吗?发车!
第一站:初识 apply_filters()
和 do_action()
在开始深入源码之前,我们先简单了解一下这两位爷是干嘛的。
apply_filters()
: 主要用于修改数据。想象一下,你正在往一个水杯里倒水,apply_filters()
就像是一个过滤器,水经过它之后,可能会变得更纯净,或者被染上颜色,最终进入你的肚子。do_action()
: 主要用于执行动作。 比如点击一个按钮,触发一系列事件,do_action()
就像是一个触发器,它会通知所有监听这个事件的函数,让它们赶紧开始干活。
用一个表格来概括一下:
功能 | apply_filters() |
do_action() |
---|---|---|
主要用途 | 修改数据 | 执行动作 |
返回值 | 修改后的数据 | 无返回值 |
核心理念 | 数据过滤 | 事件触发 |
第二站:apply_filters()
源码剖析
好了,现在我们来扒一扒apply_filters()
的源码。以下代码基于WordPress 6.x版本。
function apply_filters( $tag, $value, ...$args ) {
global $wp_filter, $wp_current_filter;
$wp_current_filter[] = $tag;
$args = array_slice( func_get_args(), 1 );
$priority = has_filter( $tag );
if ( false !== $priority && isset( $wp_filter[ $tag ] ) ) {
reset( $wp_filter[ $tag ] );
if ( true === $priority ) {
$priority = key( $wp_filter[ $tag ] );
}
do {
foreach ( (array) $wp_filter[ $tag ][ $priority ] as $function => $args_num ) {
if ( ! has_filter( $tag, $function, $priority ) ) {
continue;
}
$args[0] = $value;
$the_ = call_user_func_array( $function, array_slice( $args, 0, (int) $args_num ) );
if ( null !== $the_ ) {
$value = $the_;
}
}
$priority++;
} while ( isset( $wp_filter[ $tag ][ $priority ] ) );
}
array_pop( $wp_current_filter );
return $value;
}
别害怕,我们来一行一行地解读:
-
global $wp_filter, $wp_current_filter;
-
$wp_filter
:这是一个全局变量,存储了所有注册的过滤器(filter)。它是一个多维数组,结构大概是这样的:$wp_filter = [ 'the_content' => [ // filter 的名称 (tag) 10 => [ // 优先级 'wpautop' => [ // 函数名 'function' => 'wpautop', // 函数本身 'accepted_args' => 1 // 接受的参数个数 ], 'shortcode_unautop' => [ 'function' => 'shortcode_unautop', 'accepted_args' => 1 ] ], 20 => [ 'convert_chars' => [ 'function' => 'convert_chars', 'accepted_args' => 1 ] ] ], 'the_title' => [ 10 => [ 'trim' => [ 'function' => 'trim', 'accepted_args' => 1 ] ] ] ];
$wp_current_filter
:这是一个数组,用于跟踪当前正在执行的过滤器。这主要是为了防止循环调用,避免无限递归。
-
-
$wp_current_filter[] = $tag;
- 将当前的过滤器名称(
$tag
)添加到$wp_current_filter
数组中。
- 将当前的过滤器名称(
-
$args = array_slice( func_get_args(), 1 );
- 获取传递给
apply_filters()
的所有参数,除了第一个参数($tag
,过滤器名称)之外,都放到$args
数组中。 func_get_args()
获取所有参数。array_slice()
从数组中提取一部分。
- 获取传递给
-
$priority = has_filter( $tag );
- 检查是否存在名为
$tag
的过滤器。has_filter()
函数会返回过滤器的优先级(如果存在),否则返回false
。
- 检查是否存在名为
-
if ( false !== $priority && isset( $wp_filter[ $tag ] ) ) { ... }
- 如果存在名为
$tag
的过滤器,并且$wp_filter
数组中也存在该过滤器,则执行以下代码。
- 如果存在名为
-
reset( $wp_filter[ $tag ] );
- 将
$wp_filter[$tag]
数组的内部指针重置到第一个元素。
- 将
-
if ( true === $priority ) { $priority = key( $wp_filter[ $tag ] ); }
- 如果
has_filter()
返回true
,表示存在过滤器,但是没有指定优先级,那么就使用$wp_filter[$tag]
的第一个键作为优先级。
- 如果
-
do { ... } while ( isset( $wp_filter[ $tag ][ $priority ] ) );
- 这是一个循环,它会遍历所有具有相同
$tag
的过滤器,按照优先级从小到大依次执行。
- 这是一个循环,它会遍历所有具有相同
-
foreach ( (array) $wp_filter[ $tag ][ $priority ] as $function => $args_num ) { ... }
- 遍历当前优先级下的所有过滤器函数。
$function
:是函数名。$args_num
:是函数接受的参数个数。
-
if ( ! has_filter( $tag, $function, $priority ) ) { continue; }
- 再次检查是否存在名为
$tag
、函数名为$function
、优先级为$priority
的过滤器。这可能是为了处理动态添加/删除过滤器的情况。如果不存在,则跳过当前循环。
- 再次检查是否存在名为
-
$args[0] = $value;
- 将原始值
$value
放到$args
数组的第一个位置。这是因为过滤器函数通常需要接收原始值作为第一个参数。
- 将原始值
-
$the_ = call_user_func_array( $function, array_slice( $args, 0, (int) $args_num ) );
- 这是最关键的一行代码!
call_user_func_array()
:用于调用一个函数,并将一个数组作为参数传递给它。$function
:是要调用的函数名。array_slice( $args, 0, (int) $args_num )
:从$args
数组中提取前$args_num
个参数,作为传递给$function
的参数。$the_
:存储函数调用的返回值。
-
if ( null !== $the_ ) { $value = $the_; }
- 如果过滤器函数返回了非
null
的值,则将$value
更新为该返回值。这就是apply_filters()
修改数据的核心机制。
- 如果过滤器函数返回了非
-
$priority++;
- 增加优先级,以便处理下一个优先级的过滤器。
-
array_pop( $wp_current_filter );
- 从
$wp_current_filter
数组中移除当前的过滤器名称。
- 从
-
return $value;
- 返回最终修改后的值。
第三站:do_action()
源码剖析
接下来,我们看看do_action()
的源码。
function do_action( $tag, ...$args ) {
global $wp_filter, $wp_actions, $wp_current_filter, $wp_did_action;
$wp_current_filter[] = $tag;
if ( isset( $wp_actions[ $tag ] ) ) {
++$wp_actions[ $tag ];
} else {
$wp_actions[ $tag ] = 1;
}
if ( isset( $wp_filter[ $tag ] ) ) {
$priority = has_action( $tag );
if ( true === $priority ) {
$priority = key( $wp_filter[ $tag ] );
}
do {
foreach ( (array) $wp_filter[ $tag ][ $priority ] as $function => $args_num ) {
if ( ! has_action( $tag, $function, $priority ) ) {
continue;
}
call_user_func_array( $function, array_slice( $args, 0, (int) $args_num ) );
}
$priority++;
} while ( isset( $wp_filter[ $tag ][ $priority ] ) );
}
array_pop( $wp_current_filter );
return;
}
是不是感觉似曾相识?没错,do_action()
的源码和 apply_filters()
非常相似,主要区别在于:
-
$wp_actions
全局变量:do_action()
使用$wp_actions
数组来记录某个 action 被触发的次数。 -
没有返回值:
do_action()
不返回任何值。它只是触发一系列函数,这些函数执行一些操作,但不修改任何数据。 -
call_user_func_array()
的处理:do_action()
直接调用$function
,不关心返回值,也不更新$value
。
第四站:apply_filters()
和 do_action()
的核心区别
现在,我们可以更清晰地总结一下 apply_filters()
和 do_action()
的核心区别:
特性 | apply_filters() |
do_action() |
---|---|---|
目的 | 修改数据 | 执行动作 |
返回值 | 修改后的数据 | 无返回值 |
核心操作 | 通过调用过滤器函数,逐个修改 $value ,最终返回修改后的 $value |
触发一系列函数,不关心返回值 |
全局变量 | $wp_filter , $wp_current_filter |
$wp_filter , $wp_current_filter , $wp_actions |
使用场景 | 修改文章内容、修改标题、修改选项值等 | 插件激活、主题切换、用户登录等 |
第五站:apply_filters()
的返回值处理
apply_filters()
的返回值是它最重要也是最让人头疼的地方。记住以下几点:
-
返回值必须是非
null
: 如果你的过滤器函数返回null
,apply_filters()
会忽略这个返回值,继续使用之前的$value
。 -
返回值类型要一致: 虽然 PHP 是弱类型语言,但最好保持返回值类型的一致性。比如,如果你要修改一个字符串,那么所有过滤器函数都应该返回字符串。否则,可能会出现意想不到的错误。
-
注意优先级: 过滤器函数的执行顺序由优先级决定。优先级越小,越先执行。这意味着,先执行的过滤器函数可能会影响后执行的过滤器函数的输入。
代码示例:修改文章内容
// 注册一个过滤器,用于在文章内容末尾添加版权信息
add_filter( 'the_content', 'add_copyright_notice' );
function add_copyright_notice( $content ) {
$copyright = '<p>Copyright 2023. All rights reserved.</p>';
return $content . $copyright;
}
// 注册另一个过滤器,用于将文章内容中的敏感词替换为 "***"
add_filter( 'the_content', 'censor_sensitive_words', 11 ); // 优先级比 add_copyright_notice 高
function censor_sensitive_words( $content ) {
$sensitive_words = array( '坏蛋', '笨蛋' );
$replacement = '***';
return str_replace( $sensitive_words, $replacement, $content );
}
// 使用 apply_filters() 应用过滤器
$content = "这是一篇包含 坏蛋 的文章。";
$filtered_content = apply_filters( 'the_content', $content );
echo $filtered_content;
// 输出:这是一篇包含 *** 的文章。<p>Copyright 2023. All rights reserved.</p>
在这个例子中,censor_sensitive_words()
的优先级高于 add_copyright_notice()
,所以它会先执行,将文章内容中的敏感词替换为 "***",然后再执行 add_copyright_notice()
,在文章内容末尾添加版权信息。
第六站:注意事项和最佳实践
-
不要滥用过滤器: 虽然过滤器很强大,但不要滥用。只有在需要修改数据时才使用过滤器。如果只是想执行一些操作,应该使用 action。
-
合理设置优先级: 优先级非常重要,它决定了过滤器函数的执行顺序。要仔细考虑每个过滤器函数的优先级,确保它们按照正确的顺序执行。
-
做好类型检查: 虽然 PHP 是弱类型语言,但最好在过滤器函数中做好类型检查,确保输入和输出的类型一致。
-
编写可测试的代码: 使用过滤器时,要编写可测试的代码。这意味着,你需要能够独立测试每个过滤器函数,确保它们能够正确地修改数据。
-
使用明确的命名: 为过滤器函数和过滤器名称使用明确的命名,以便于理解和维护。
-
避免循环依赖: 避免在过滤器函数中调用
apply_filters()
本身,否则可能会导致循环依赖,最终导致程序崩溃。
总结
好了,今天的讲座就到这里。我们深入剖析了 WordPress 的 apply_filters()
函数源码,了解了它和 do_action()
的核心区别,以及如何处理返回值。希望这些知识能帮助你更好地理解 WordPress 的工作原理,编写更健壮、更可维护的代码。记住,代码就像段子,写得好就能让人捧腹大笑,写得不好就只能让人尴尬挠头。所以,努力成为代码界的段子手吧!
散会!