深入理解 WordPress `add_filter()` 函数的源码:插件如何修改核心功能。

各位观众老爷,晚上好!今天咱们来聊聊WordPress里一个重量级的函数——add_filter()。 这玩意儿,说是WordPress插件的灵魂都不为过。 掌握了它,你就掌握了修改WordPress核心功能的钥匙,能让你的网站变得独一无二,骚气十足。

一、什么是Filter? 别跟我说滤镜啊!

首先,我们要搞清楚什么是Filter。 别跟我扯照片滤镜,那个是美颜用的,我们这是改变网站行为用的!

在WordPress的世界里,Filter就像一个检查站。 WordPress的核心代码在执行到某些关键节点的时候,会触发一个Filter。 这个时候,所有注册到这个Filter上的函数(也就是你通过add_filter()添加的函数)都会被依次调用,它们可以修改WordPress传递给Filter的数据,然后再把修改后的数据传回给WordPress。

简单来说,就是:

  1. WordPress运行到某个地方,想问问大家意见(触发Filter)。
  2. 注册到这个Filter的函数们纷纷发表意见,修改数据。
  3. WordPress最终采纳修改后的数据,继续运行。

举个例子,假设WordPress要显示文章的标题,它会触发一个名为the_title的Filter。 你的插件可以通过add_filter('the_title', 'my_custom_title')来注册一个名为my_custom_title的函数。 这个函数就可以修改文章标题,比如给标题加上一个前缀或者后缀,或者直接把标题改成“秘密”。

二、add_filter()函数: 插件的魔法棒

add_filter()函数是把你的自定义函数注册到WordPress Filter上的关键。 它的语法如下:

add_filter( string $tag, callable $function_to_add, int $priority = 10, int $accepted_args = 1 )
  • $tag (string, required): Filter的名称,也就是WordPress定义的“检查站”的名字。比如the_titlethe_contentwp_mail等等。 你要修改哪个地方,就填哪个Filter的名字。
  • $function_to_add (callable, required): 你要注册的函数名。 这个函数就是你的自定义逻辑,用来修改数据的。
  • $priority (int, optional): 优先级。 数字越小,优先级越高。 如果有多个函数注册到同一个Filter上,优先级高的函数会先执行。 默认值是10。 想象一下,一群大臣给皇帝提意见,优先级高的先说。
  • $accepted_args (int, optional): 你的函数接收的参数个数。 也就是WordPress会传递给你的函数多少个参数。 默认值是1。

三、源码剖析: 看看幕后发生了什么

为了更深入地理解add_filter(),我们来扒一扒它的源码,看看WordPress内部是怎么实现的。 (简化版,忽略了一些错误处理和兼容性代码)

// 大概的实现,实际源码更复杂
function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
    global $wp_filter, $wp_actions;

    // 确保 $wp_filter 存在
    if ( ! isset( $wp_filter[ $tag ] ) ) {
        $wp_filter[ $tag ] = array();
    }

    // 确保优先级存在
    if ( ! isset( $wp_filter[ $tag ][ $priority ] ) ) {
        $wp_filter[ $tag ][ $priority ] = array();
    }

    // 创建一个唯一的标识符,避免重复添加同一个函数
    $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
    );

    // 排序,保证优先级生效
    ksort( $wp_filter[ $tag ] );

    // 如果是新的action,记录一下
    if ( ! isset( $wp_actions[ $tag ] ) ) {
        $wp_actions[ $tag ] = 0;
    }
    return true;
}

// 生成唯一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];
    }
    return false;
}

简单解释一下:

  1. global $wp_filter: $wp_filter是一个全局数组,用来存储所有的Filter。 它的结构是这样的:

    $wp_filter = array(
        'filter_name' => array(
            priority => array(
                'unique_id' => array(
                    'function' => 'function_name',
                    'accepted_args' => 1
                )
            )
        )
    );
    • filter_name:Filter的名字,比如the_title
    • priority:优先级,数字越小优先级越高。
    • unique_id:函数的唯一标识符,防止重复添加同一个函数。
    • function:要执行的函数名。
    • accepted_args:函数接收的参数个数。
  2. 检查Filter和优先级是否存在: 如果Filter或优先级不存在,就创建它们。

  3. 生成唯一ID: 使用_wp_filter_build_unique_id()函数生成一个唯一的ID,用来区分不同的函数。

  4. 存储函数信息: 把函数名和参数个数存储到$wp_filter数组中。

  5. 排序: 使用ksort()函数对优先级进行排序,保证优先级高的函数先执行。

  6. 记录Action: 如果是新的action,记录一下。 (Action和Filter类似,但是Action不修改数据,只是执行一些操作。 后面会讲到。)

总的来说,add_filter()函数的作用就是把你的函数信息存储到$wp_filter全局数组中,供WordPress在需要的时候调用。

四、apply_filters()函数: 触发Filter的开关

apply_filters()函数是真正触发Filter的地方。 它的语法如下:

apply_filters( string $tag, mixed $value, mixed ...$args )
  • $tag (string, required): Filter的名称,和add_filter()中的$tag要对应。
  • $value (mixed, required): 要传递给Filter的初始值。 也就是WordPress想要修改的数据。
  • ...$args (mixed, optional): 其他要传递给Filter的参数。

看个例子:

$title = '原始标题';
$title = apply_filters( 'the_title', $title );
echo $title;

在这个例子中,apply_filters('the_title', $title)会触发the_title这个Filter。 所有注册到the_title上的函数都会被依次调用,它们可以修改$title的值,最终apply_filters()会返回修改后的$title

源码剖析:

// 大概的实现,实际源码更复杂
function apply_filters( $tag, $value, ...$args ) {
    global $wp_filter, $wp_current_filter;

    // 如果没有注册任何函数到这个Filter上,直接返回原始值
    if ( ! isset( $wp_filter[ $tag ] ) ) {
        return $value;
    }

    // 记录当前的Filter,用于调试
    $wp_current_filter[] = $tag;

    // 获取所有注册到这个Filter上的函数
    $filters = $wp_filter[ $tag ];

    // 对优先级进行排序
    ksort( $filters );

    // 遍历所有函数,依次调用
    foreach ( $filters as $priority => $functions ) {
        foreach ( $functions as $function ) {
            // 根据 accepted_args 的值,传递不同数量的参数
            $num_args = $function['accepted_args'];
            switch ( $num_args ) {
                case 1:
                    $value = call_user_func( $function['function'], $value );
                    break;
                case 2:
                    $value = call_user_func( $function['function'], $value, $args[0] );
                    break;
                case 3:
                    $value = call_user_func( $function['function'], $value, $args[0], $args[1] );
                    break;
                default:
                    $value = call_user_func_array( $function['function'], array_slice( array_merge( array( $value ), $args ), 0, $num_args ) );
                    break;
            }
        }
    }

    // 移除当前的Filter
    array_pop( $wp_current_filter );

    return $value;
}

简单解释一下:

  1. global $wp_filter:add_filter()一样,需要访问全局的$wp_filter数组。
  2. 检查Filter是否存在: 如果没有注册任何函数到这个Filter上,直接返回原始值。
  3. 记录当前的Filter: 记录当前的Filter,用于调试。
  4. 获取所有函数:$wp_filter数组中获取所有注册到这个Filter上的函数。
  5. 排序: 对优先级进行排序,保证优先级高的函数先执行。
  6. 遍历函数: 遍历所有函数,依次调用。
  7. 传递参数: 根据accepted_args的值,传递不同数量的参数给函数。
  8. 移除当前的Filter: 移除当前的Filter,用于调试。
  9. 返回最终值: 返回经过所有函数修改后的最终值。

总的来说,apply_filters()函数的作用就是从$wp_filter数组中找到所有注册到指定Filter上的函数,然后依次调用它们,并把修改后的值返回。

五、Action: 只执行,不修改

除了Filter,WordPress还有Action。 Action和Filter很像,但是Action不修改数据,只是执行一些操作。

比如,你想在文章发布之后发送一封邮件,就可以注册一个Action到publish_post这个钩子上。

add_action()do_action()分别用来注册Action和触发Action。 它们的用法和add_filter()apply_filters()非常相似,这里就不再赘述了。

六、实战演练: 修改文章标题

现在,我们来写一个简单的插件,修改文章标题,给标题加上一个前缀“【骚气】”。

<?php
/**
 * Plugin Name:  骚气标题插件
 * Plugin URI:   https://www.example.com/
 * Description:  给文章标题加上一个前缀“【骚气】”。
 * Version:      1.0.0
 * Author:       骚气程序员
 * Author URI:   https://www.example.com/
 */

// 注册一个Filter到the_title上
add_filter('the_title', 'add_sauqi_prefix');

// 自定义函数,给标题加上前缀
function add_sauqi_prefix($title) {
    return '【骚气】' . $title;
}

把这段代码保存为sauqi-title.php,上传到wp-content/plugins/目录下,然后在WordPress后台激活这个插件。 刷新一下你的文章页面,看看是不是所有文章的标题都加上了“【骚气】”前缀?

七、优先级: 谁说了算?

如果多个插件都注册到同一个Filter上,谁说了算? 这就要看优先级了。 优先级数字越小,优先级越高。

比如,你有两个插件,一个插件给文章标题加上前缀“【骚气】”,另一个插件给文章标题加上后缀“(已修改)”。

// 插件1:骚气标题插件
add_filter('the_title', 'add_sauqi_prefix', 10);
function add_sauqi_prefix($title) {
    return '【骚气】' . $title;
}

// 插件2:修改后缀插件
add_filter('the_title', 'add_suffix', 20);
function add_suffix($title) {
    return $title . '(已修改)';
}

在这个例子中,add_sauqi_prefix的优先级是10,add_suffix的优先级是20。 因此,add_sauqi_prefix会先执行,然后add_suffix再执行。 最终的文章标题会是“【骚气】原始标题(已修改)”。

如果你想让add_suffix先执行,只需要把它的优先级设置为小于10的数字即可。

八、参数个数: 要多少给多少

accepted_args参数决定了WordPress会传递给你的函数多少个参数。 默认值是1,也就是只传递第一个参数(也就是apply_filters()中的$value)。

如果你的函数需要接收更多的参数,就需要设置accepted_args的值。

比如,the_content这个Filter会传递两个参数:文章内容和文章对象。 如果你想在你的函数中访问文章对象,就需要把accepted_args设置为2。

add_filter('the_content', 'add_post_id', 10, 2);
function add_post_id($content, $post) {
    return $content . '<p>文章ID:' . $post->ID . '</p>';
}

在这个例子中,add_post_id函数接收两个参数:$content(文章内容)和$post(文章对象)。 通过$post->ID可以访问文章的ID。

九、一些常用的Filter和Action

| 钩子类型 | 钩子名称 | 描述 and so on…



**十、 调试技巧:  让BUG无处遁形**

*   **`doing_filter()`和`doing_action()`:**  这两个函数可以用来判断当前是否正在执行某个Filter或Action。
*   **`current_filter()`和`current_action()`:**  这两个函数可以用来获取当前正在执行的Filter或Action的名称。
*   **`remove_filter()`和`remove_action()`:**  这两个函数可以用来移除已经注册的Filter或Action。

**十一、注意事项:  别玩脱了!**

*   **不要修改核心代码:**  永远不要直接修改WordPress的核心代码。 所有的修改都应该通过插件或者主题来实现。
*   **谨慎使用Filter和Action:**  过度使用Filter和Action可能会导致性能问题。
*   **注意优先级:**  确保你的函数的优先级是正确的。
*   **测试你的代码:**  在发布你的插件之前,一定要进行充分的测试。

**十二、总结:  骚起来吧!**

`add_filter()`函数是WordPress插件开发中最核心的函数之一。 掌握了它,你就可以修改WordPress的核心功能,让你的网站变得独一无二,骚气十足。 但是,也要注意谨慎使用,避免玩脱了。

希望今天的讲座对大家有所帮助。 谢谢大家!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注