探究 WordPress `add_action()` 和 `add_filter()` 函数的源码:如何处理优先级和参数数量。

嘿,大家好!欢迎参加今天的WordPress源码探秘讲座,我是你们的向导,今天咱们一起扒一扒 add_action()add_filter() 这两个WordPress的核心函数,重点看看它们是怎么处理优先级和参数数量的。放心,保证不枯燥,咱们用最接地气的方式来搞懂它们。

开场白:WordPress的“胶水”

如果把WordPress比作一个乐高积木城堡,那么add_action()add_filter()就是连接这些积木的“胶水”。它们允许你自定义WordPress的行为,而无需直接修改核心代码,这是WordPress扩展性的关键。

add_action()add_filter():双胞胎兄弟

虽然名字不一样,但add_action()add_filter()本质上非常相似,它们都是用来注册钩子(Hook)的。钩子是WordPress在代码执行流程中预留的一些“插槽”,你可以在这些插槽里插入自己的代码。

  • add_action():用于注册动作钩子,当WordPress执行到这个钩子时,会执行你注册的函数。
  • add_filter():用于注册过滤器钩子,当WordPress执行到这个钩子时,会先执行你注册的函数,然后将函数的返回值传递给下一个函数或最终使用。

源码剖析:add_action()add_filter() 的骨架

让我们先看看add_action()的源码(wp-includes/plugin.php):

function add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
    return add_filter( $tag, $function_to_add, $priority, $accepted_args );
}

看到没?add_action() 实际上只是 add_filter() 的一个别名,它们共享相同的底层机制。这说明它们在实现上是高度一致的。 接下来,我们重点分析 add_filter() 的内部运作,因为 add_action() 也是通过它来实现的。

add_filter() 函数的定义如下:

function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
    global $wp_filter, $merged_filters, $wp_current_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
    );

    unset( $merged_filters[ $tag ] );

    if ( doing_filter( $tag ) ) {
        global $wp_actions;

        if ( ! isset( $wp_actions[ $tag ] ) ) {
            $wp_actions[ $tag ] = 1;
        }

        ++$wp_actions[ $tag ];
    }

    return true;
}

参数解读:四位一体

add_filter() 接收四个参数:

  • $tag:钩子的名称,也就是你想要插入代码的“插槽”的名字。例如,'the_content' 是一个常用的钩子,用于修改文章内容。
  • $function_to_add:你想要执行的函数名或闭包。这是你自定义逻辑的载体。
  • $priority:优先级,数值越小,优先级越高,越早执行。默认值为 10。
  • $accepted_args:函数接收的参数数量。WordPress 会根据这个值传递相应数量的参数给你的函数。默认值为 1。

优先级:先来后到有讲究

$priority 参数决定了钩子函数执行的顺序。WordPress 会按照优先级从小到大依次执行钩子函数。这意味着优先级数值小的函数会先执行。

想象一下排队买奶茶,优先级就像你的会员等级,等级高的可以插队先买。

让我们看一个例子:

add_filter( 'the_content', 'my_filter_one', 5 ); // 优先级 5
add_filter( 'the_content', 'my_filter_two', 10 ); // 优先级 10
add_filter( 'the_content', 'my_filter_three', 15 ); // 优先级 15

function my_filter_one( $content ) {
    return '<h1>Filter One</h1>' . $content;
}

function my_filter_two( $content ) {
    return '<h2>Filter Two</h2>' . $content;
}

function my_filter_three( $content ) {
    return '<h3>Filter Three</h3>' . $content;
}

在这个例子中,my_filter_one 会最先执行,然后是 my_filter_two,最后是 my_filter_three。最终的文章内容会是:

<h1>Filter One</h1><h2>Filter Two</h2><h3>Filter Three</h3>[原始文章内容]

源码解析:$wp_filter 全局变量

add_filter() 的核心在于操作全局变量 $wp_filter。这是一个多维数组,用于存储所有注册的钩子函数。

  • 第一层键:钩子的名称($tag)。
  • 第二层键:优先级($priority)。
  • 第三层键:钩子函数的唯一ID,由 _wp_filter_build_unique_id() 函数生成,确保同一个函数不会被重复注册。

每个钩子函数的信息都以数组的形式存储在 $wp_filter 中,包括:

  • 'function':要执行的函数名或闭包。
  • 'accepted_args':函数接收的参数数量。

用表格的形式总结一下 $wp_filter 的结构:

层次
第一层 钩子名称($tag 数组,包含所有与该钩子相关的优先级层
第二层 优先级($priority 数组,包含所有与该优先级相关的钩子函数
第三层 钩子函数唯一ID(_wp_filter_build_unique_id() 生成) 数组,包含钩子函数的详细信息('function''accepted_args'

_wp_filter_build_unique_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];
    }
}

这个函数会根据函数类型(字符串、对象、数组)生成不同的ID。对于闭包函数,它会使用 spl_object_hash() 函数生成一个唯一的哈希值。

参数数量:量体裁衣

$accepted_args 参数告诉 WordPress 你的函数需要接收多少个参数。WordPress 会根据这个值,从钩子函数传递过来的参数中,选择相应数量的参数传递给你的函数。

例如,'the_content' 钩子默认传递一个参数(文章内容)。如果你想接收这个参数,你需要将 $accepted_args 设置为 1。如果你想接收更多参数,你需要根据实际情况设置 $accepted_args 的值。

让我们看一个例子:

add_filter( 'comment_text', 'my_comment_filter', 10, 2 );

function my_comment_filter( $comment_text, $comment ) {
    // $comment_text: 评论内容
    // $comment: 评论对象
    return '<div>' . $comment_text . '</div>';
}

在这个例子中,my_comment_filter 函数接收两个参数:评论内容($comment_text)和评论对象($comment)。我们将 $accepted_args 设置为 2,告诉 WordPress 传递这两个参数给我们的函数。

源码解析:apply_filters()do_action() 的调用

注册钩子函数只是第一步,关键在于 WordPress 在何时、以何种方式调用这些函数。这就要涉及到 apply_filters()do_action() 函数。

  • apply_filters():用于执行过滤器钩子,并返回经过所有钩子函数处理后的值。
  • do_action():用于执行动作钩子,不返回值。

让我们看看 apply_filters() 的简化版源码(wp-includes/plugin.php):

function apply_filters( $tag, $value ) {
    global $wp_filter, $merged_filters, $wp_current_filter;

    $args = func_get_args();
    $hook_name = array_shift( $args );
    $value = array_shift( $args );

    if ( ! isset( $wp_filter[ $hook_name ] ) ) {
        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 );

    return $value;
}

这个函数做了以下几件事:

  1. $wp_filter 全局变量中获取与 $tag 相关的钩子函数。
  2. 按照优先级对钩子函数进行排序(如果尚未排序)。
  3. 遍历所有钩子函数,并使用 call_user_func_array() 函数调用它们。
  4. 将钩子函数的返回值传递给下一个钩子函数,或者作为 apply_filters() 的最终返回值。

do_action() 函数的实现类似,只是它不返回值,而是直接执行钩子函数。

call_user_func_array():幕后推手

call_user_func_array() 是一个非常重要的 PHP 函数,它可以动态地调用函数,并传递一个数组作为参数。这使得 WordPress 可以灵活地调用各种类型的钩子函数,而无需知道它们的具体签名。

apply_filters() 中,call_user_func_array() 的使用方式如下:

$value = call_user_func_array( $the_['function'], array_slice( $args, 0, (int) $the_['accepted_args'] ) );
  • $the_['function']:要调用的函数名或闭包。
  • array_slice( $args, 0, (int) $the_['accepted_args'] ):要传递给函数的参数数组。array_slice() 函数用于截取 $args 数组的一部分,确保只传递 $accepted_args 指定数量的参数。

实际案例:修改文章摘要

让我们用一个实际的例子来演示如何使用 add_filter() 修改文章摘要:

add_filter( 'get_the_excerpt', 'my_custom_excerpt', 10, 1 );

function my_custom_excerpt( $excerpt ) {
    if ( empty( $excerpt ) ) {
        $content = get_the_content();
        $excerpt = wp_trim_words( $content, 20, '...' );
    }
    return '<p>Custom Excerpt: ' . $excerpt . '</p>';
}

在这个例子中,我们使用 add_filter() 注册了一个名为 my_custom_excerpt 的函数,用于修改 'get_the_excerpt' 钩子的返回值。如果文章没有摘要,我们从文章内容中提取前 20 个词作为摘要,并添加一个自定义的前缀。

总结:add_action()add_filter() 的精髓

  • add_action()add_filter() 是WordPress扩展性的基石。
  • 它们通过操作 $wp_filter 全局变量来注册钩子函数。
  • $priority 参数决定了钩子函数的执行顺序。
  • $accepted_args 参数决定了传递给钩子函数的参数数量。
  • apply_filters()do_action() 函数负责调用已注册的钩子函数。
  • call_user_func_array() 函数用于动态地调用钩子函数。

进阶思考:性能优化

虽然 add_action()add_filter() 非常强大,但过度使用也会影响性能。以下是一些优化建议:

  • 避免注册不必要的钩子函数。
  • 使用合适的优先级,确保钩子函数在正确的时机执行。
  • 尽量减少钩子函数的执行时间。
  • 使用缓存来避免重复计算。

结语:掌握核心,玩转WordPress

希望今天的讲座能帮助你更深入地理解 add_action()add_filter() 函数的源码。掌握这些核心概念,你就能更好地自定义WordPress的行为,构建更强大的网站。记住,多动手实践,才能真正理解它们的精髓。下次有机会再和大家一起探讨WordPress的其他有趣源码!感谢大家的参与!

发表回复

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