WordPress函数:add_filter 添加函数到过滤器钩子

编辑文章

简介

add_filter 是 WordPress 插件机制(钩子与过滤器)的核心函数之一,用于“修改”或“过滤”数据。它允许你在某个数据(如文章标题、内容、查询参数)被使用(显示、保存、查询)之前,先“钩住”这个数据点,并用你自定义的函数对其进行处理,最后将修改后的数据返回给 WordPress 继续执行。

语法

函数位于 wp-includes/plugin.php 文件中。其内部通过全局变量 $wp_filter 来管理所有注册的过滤器。

add_filter( string $tag, callable $function_to_add, int $priority = 10, int $accepted_args = 1 ): bool
参数 类型 说明
$tag string 过滤器钩子的名称。它对应一个 WordPress 核心、主题或插件预留的“数据修改点”,例如 the_content(文章内容)、wp_title(页面标题)。
$function_to_add callable 回调函数。当 $tag 对应的过滤器被执行时,你的这个函数将被调用。它需要接收并返回经过处理的数据。
$priority int 优先级。默认值为 10。数字越小,该回调函数越早执行;数字越大,则越晚执行。当多个函数挂载到同一个钩子时,此参数决定它们的执行顺序。
$accepted_args int 接受的参数数量。默认值为 1。回调函数将从钩子处接收多少个参数。部分过滤器会传递多个参数(如原始值、参数A、参数B)。
返回值 bool 总是返回 true

用法

基础用法

最常见的使用场景是修改即将输出的内容。以下示例为所有文章标题末尾添加一个“(原创)”标识。

代码片段:通常添加在主题的 functions.php 文件或自定义插件文件中。

function mytheme_add_original_tag( $title ) {
    // $title 是原始标题数据,我们对其进行修改
    $modified_title = $title . '(原创)';
    // 必须将修改后的值返回,否则数据会“丢失”
    return $modified_title;
}
// 将我们的函数挂载到 `the_title` 这个过滤器钩子上
add_filter( 'the_title', 'mytheme_add_original_tag' );

为什么是 the_title 因为在 WordPress 模板中调用 the_title() 函数输出标题时,其内部会执行 apply_filters( 'the_title', $title ),从而触发所有挂载在此处的过滤器函数。

进阶用法

过滤器可以用来处理更复杂的逻辑和决策。以下示例展示如何根据文章类型和条件,修改文章查询参数。

场景:在网站前台,不让“项目”类型的文章出现在主循环中。

function mytheme_exclude_project_from_main_query( $query_args ) {
    // 1. 确保只在主查询且非管理后台进行修改
    if ( is_admin() || ! $query_args->is_main_query() ) {
        return $query_args; // 不是目标查询,直接返回,不做任何改变
    }

    // 2. 获取当前被排除的文章类型数组,并添加“project”类型
    $current_post_types = $query_args->get( ‘post_type’ );
    $excluded_types = array( ‘project’ );

    // 如果当前查询的是数组形式的 post_type,进行合并排除
    if ( is_array( $current_post_types ) ) {
        $query_args->set( ‘post_type’, array_diff( $current_post_types, $excluded_types ) );
    }
    // 如果查询的是单个 post_type 字符串且恰好是 ‘project’,则重置为默认的 ‘post’
    elseif ( $current_post_types === ‘project’ ) {
        $query_args->set( ‘post_type’, ‘post’ );
    }

    // 3. 必须返回修改后的查询参数对象
    return $query_args;
}
// `pre_get_posts` 是一个在查询参数构建后、实际执行数据库查询前触发的过滤器。
// 它传递整个 WP_Query 对象作为参数,因此 accepted_args 设为 1(默认值即可)。
add_filter( ‘pre_get_posts’, ‘mytheme_exclude_project_from_main_query’ );

为什么用 pre_get_posts 因为它允许你在查询被执行前修改 WP_Query 对象的参数,是控制文章列表最高效的方式,直接作用于数据库查询层,而非结果输出层。

易错点

忘记返回值

这是新手最常犯的错误。过滤器的回调函数必须返回一个值(通常是修改后的 $value)。如果忘记返回,函数默认返回 null,这会导致预期的数据被清空,可能引发白屏、内容消失等问题。

// 错误示例
function bad_filter( $content ) {
    $content = ‘<div>’ . $content . ‘</div>’;
    // 这里没有 return $content;
}
add_filter( ‘the_content’, ‘bad_filter’ ); // 应用此过滤器后,文章内容将显示为空!

优先级与执行顺序理解不清

错误地估计了多个过滤器的执行顺序,导致最终结果不符合预期。例如,你想在某个插件修改完标题后追加内容,但如果你的优先级设置得比插件早(数字更小),你的修改就会被插件的覆盖。

// 插件代码可能这样写(你不知道):
add_filter( ‘the_title’, ‘plugin_title_filter’, 15 );

// 你的代码:
add_filter( ‘the_title’, ‘my_title_filter’ ); // 优先级默认是10
// 结果:你的函数(优先级10)先执行,插件函数(优先级15)后执行,会覆盖你的结果。

在过滤器函数中执行“动作”

过滤器的目的是修改并返回数据。如果在其中执行了本应由 add_action 负责的“动作”(如发送邮件、写入日志),会造成逻辑混乱和潜在的性能问题,因为这些操作可能在非预期的上下文(如后台列表、REST API 请求)中被重复触发。

// 不佳实践
function confusing_filter( $content ) {
    // 修改内容是过滤器的本职
    $content .= ‘<p>Footer Note</p>’;
    // 但记录日志是一个“动作”,更适合用 `wp_insert_post` 相关的 action 钩子
    error_log( ‘Post content filtered’ ); // 这行代码会被执行很多次!
    return $content;
}

最佳实践

单一职责与清晰的命名

确保每个过滤器回调函数只做一件事,并为其起一个清晰、包含命名空间前缀的名称,避免与核心或其他插件冲突。

// 良好实践
function myplugin_enhance_excerpt_length( $length ) {
    return 30;
}
add_filter( ‘excerpt_length’, ‘myplugin_enhance_excerpt_length’ );

// 另一个独立的过滤器做另一件事
function myplugin_add_readmore_to_excerpt( $excerpt ) {
    if ( ! is_single() ) {
        $excerpt .= ‘ <a href=“’ . get_permalink() . ‘”>Read More</a>’;
    }
    return $excerpt;
}
add_filter( ‘get_the_excerpt’, ‘myplugin_add_readmore_to_excerpt’ );

显式声明优先级

永远不要假设默认优先级(10)符合你的需求。根据业务逻辑明确指定 $priority,使代码意图更清晰,也便于后续调整。

// 明确声明优先级,使执行顺序一目了然
add_filter( ‘the_content’, ‘mytheme_wrap_content_first’, 5 );
add_filter( ‘the_content’, ‘mytheme_auto_embed_links’, 8 );
add_filter( ‘the_content’, ‘mytheme_add_share_buttons_last’, 20 );

谨慎使用匿名函数

在主题的 functions.php 中使用匿名函数(闭包)很方便,但它使得你无法在后期用 remove_filter() 移除这个过滤器。在插件开发中,如果需要提供可卸载/停用的功能,应避免使用。

// 在主题中,可以这样用(但需知晓其不可移除)
add_filter( ‘body_class’, function( $classes ) {
    $classes[] = ‘custom-class’;
    return $classes;
});

// 在需要提供卸载功能的插件中,应使用具名函数

安全性是首要考虑

任何接收外部数据或向浏览器输出的过滤器,都必须考虑安全。

// 场景:修改评论内容,同时要安全输出
function mytheme_safe_comment_filter( $comment_text ) {
    // 1. 执行你的自定义逻辑(例如,高亮某些关键词)
    $modified_text = str_replace( ‘WordPress’, ‘<mark>WordPress</mark>’, $comment_text );

    // 2. 安全输出:如果这个过滤器用于前端显示,返回前应使用KSES或转义函数。
    // 由于我们添加了 `<mark>` 标签,我们使用 wp_kses_post 来允许安全的 HTML 标签。
    return wp_kses_post( $modified_text );
}
add_filter( ‘comment_text’, ‘mytheme_safe_comment_filter’ );

// 场景:过滤器接收用户输入的参数用于查询
function myplugin_filter_posts_by_user( $query_args, $request_param ) {
    // $request_param 可能来自 URL 参数或 REST API 请求
    if ( ! empty( $request_param[‘user_id’] ) ) {
        $user_id = absint( $request_param[‘user_id’] ); // 使用 absint 进行清理和类型转换
        if ( $user_id > 0 ) {
            $query_args[‘author’] = $user_id;
        }
    }
    return $query_args;
}
// 假设这个过滤器用于 REST API 端点,接受两个参数
add_filter( ‘rest_post_query’, ‘myplugin_filter_posts_by_user’, 10, 2 );