这个系列写到这里,来了一个特别好的追问,评论区原话:
“后面的是参考资料,应该后面的参数也同样可以修改的吧,难道只用于修改第一个参数的内容吗?”
问得很尖锐,也很有代表性——我见过太多人照着教程写 function my_filter($a, $b, $c),然后在函数里改 $b、改 $c,改完一脸问号:为什么没反应?这篇文章就把这件事讲到底。答案先放这儿:
改不了。而且不是“不建议改”,是机制上根本无效——既影响不了下一个函数,也影响不了 WordPress 内核。
前两篇讲过,add_filter 只是“登记”,真正干活的是 apply_filters。它的内部实现在 wp-includes/class-wp-hook.php,盯着下面这三行看:
public function apply_filters( $value, $args ) {
...
foreach ( $this->callbacks[ $priority ] as $the_ ) {
if ( ! $this->doing_action ) {
$args[0] = $value; // ①
}
...
$value = call_user_func_array( $the_['function'], $args ); // ②
}
...
}
第 ① 行的位置是关键:每次喊下一个函数之前,只覆盖 $args[0]。$args[1]、$args[2]……从头到尾都是调用点递出来的那一组原件,从来不被替换。
第 ② 行更直接:你的函数不管返回什么,只被赋给 $value——也就是接力链里的第一个包。第二个、第三个参数就算你真动了什么,没有任何代码去接。
抽象描述不如画出来。假设 wp_title 递了三个参数,链上挂着两个函数:

只有包 1 会被交棒:后续参数永远是原件,改了就扔
看明白这张图,这个问题就结束了:函数甲把包 2 改成了 'left',可函数乙领到的还是出厂原件 'right'——因为在喊函数乙之前,WordPress 只把包 1 换成了函数甲的返回值,包 2、包 3 原样递过去。函数甲改的那个副本,函数一返回就销毁了。
就算不追究 WordPress 的源码,PHP 语言本身的规矩也堵死了这条路:普通变量传进函数,拿到的是副本。
function demo( $title, $sep ) {
$sep = 'left'; // 改的是局部副本
return $title . '!';
}
$sep = 'right';
echo demo( '标题', $sep );
echo $sep; // 输出的还是 right,函数里的改动出不去
所以“后面的参数不能改”是两层原因叠加的结果:PHP 层,副本出不了函数;WordPress 层,就算出去了也没人接。双重保险,堵得严严实实。
四个字:供你判断。它们是情境信息,让你知道“现在这是个什么情况”,好决定怎么改第一个值。
拿 get_avatar 这个钩子举例最清楚。它一口气递 6 个参数:
apply_filters( 'get_avatar', $avatar, $id_or_email, $args['size'], $args['default'], $args['alt'], $args );
// ↑你改这个 ↑后面五个:这是谁的头像、多大尺寸、默认图、替代文字、完整参数
正确用法是拿 $size 做条件判断,而不是改它:
function my_avatar_filter( $avatar, $id_or_email, $size ) {
if ( $size <= 96 ) { // ← 读 $size,做判断
$avatar = str_replace( 'gravatar.com', 'cravatar.cn', $avatar ); // ← 只改 $avatar
}
return $avatar; // ← 只还第一个
}
“小头像换国内镜像、大头像保留”这个需求,全靠 if ( $size <= 96 ) 这个判断实现——这就是“读”的典型场景。你签名里写三个参数,是为了接到 $size,不是为了改它。
PHP 里有个特例必须交代:对象变量传进函数,函数内外指向同一个对象。你在函数里改对象的属性,调用方确实看得到。所以如果某个钩子递给你的是对象(比如 $post),改它的属性“好像”生效了。
但这条路不值得走,三个理由:
$value(第一个参数)的最终值接回去用,你改的对象属性不影响任何输出逻辑;另挂一个钩子。每个钩子管自己那个值:想改摘要长度就挂 excerpt_length,想拦评论就挂 preprocess_comment,想改标题就挂 wp_title。这才是这套机制的设计意图——一个函数对一个值负责,收下主角值,还回主角值。
如果你的函数想“顺便干点别的”(比如改标题的同时记条日志),那个时机应该用 add_action,而不是在 filter 里动别人的参数。filter 和 action 的区别,在前一篇专门对比过:要不要把什么东西还回去,要就用 filter,不要就用 action。
写完这篇初稿,我第一次提交时被服务器挡了,HTTP 403,正文一进后台就消失。排查下来原因很有意思,跟你有关:
建站时为了安全,多数人会装防火墙(宝塔 WAF、Wordfence、 Sucuri 之类)。这类插件内置了一条规则:凡是文章正文里出现“可能被用来执行恶意代码的 PHP 函数名”,一律拒绝保存。而 call_user_func_array 正好在这份名单里——它是 PHP 里用来“把函数名和参数数组一起执行”的函数,攻击者拿它做 webshell 后门,规则自然盯死它。
结果就是:一篇正经讲源码的文章,因为提到了一个危险函数的名字,被防火墙当成攻击代码拦下了。
绕过办法不算复杂,我用的这招:把下划线写成 HTML 实体——call_user_func_array。浏览器渲染时 _ 会还原成一个普通的下划线,读者看到的字完全一样,但防火墙的字符串匹配匹配不到了,就放行了。
如果你的博客也要发这种源码文章,碰到“明明内容没问题却存不进去”,先往这个方向想:把触发规则的函数名拆开(call_user_func 和 array 中间插一个零宽字符或用实体)、换成文字描述,或者干脆只贴官方文档链接、函数名用图片。不是什么大问题,但新手往往查半天都想不到是防火墙。
把这三篇串起来,apply_filters 的全貌就齐了:
| 问题 | 答案 |
|---|---|
| 它是怎么找到你的函数的? | 按名字查登记簿,只叫这一格的人(点名,不是广播) |
| 递几个参数? | 调用点自己定,没有统一模板(1 个到 6 个都有) |
| 后面的参数能改吗? | 不能,改了白改——只读,供你判断 |
所以以后再看到 function my_filter($a, $b),判断标准就一条:第一个参数是你改完要还回去的,其余的都是递到你手里看看的。分清楚“改”和“读”,filter 就再没有秘密了。