阐述 `add_action` 和 `add_filter` 函数的源码,解释它们如何将钩子函数添加到全局 `Array` 数组中。

咳咳,大家好!今天咱们来聊聊 WordPress 里两个超级重要的家伙—— add_actionadd_filter。它们就像 WordPress 的神经系统,连接着各个功能模块,让插件和主题可以自由地扩展和修改核心行为。 准备好了吗?系好安全带,咱们要深入源码,揭开它们神秘的面纱了!

一、钩子:WordPress 的灵活之源

在深入 add_actionadd_filter 之前,先要理解“钩子 (Hook)”这个概念。你可以把钩子想象成 WordPress 代码中的一些预留的“插槽”,允许你在特定的时间点插入自己的代码,改变 WordPress 的默认行为。

WordPress 主要有两种类型的钩子:

  • Actions (动作): 在特定的事件发生时执行你的代码。比如,在文章发布之后,你可以用一个 action 钩子来发送邮件通知。
  • Filters (过滤器): 允许你修改数据。比如,你可以用一个 filter 钩子来修改文章的内容,或者改变主题的标题。

二、add_action:执行你的代码

add_action 函数的作用是注册一个函数,让它在特定的 action 钩子被触发时执行。 让我们先看看 add_action 的简化版原型:

function add_action( string $tag, callable $function_to_add, int $priority = 10, int $accepted_args = 1 ): bool;
  • $tag: 要挂载的 action 钩子的名称 (字符串)。
  • $function_to_add: 要执行的函数名 (可调用类型)。
  • $priority: 执行优先级 (整数)。数值越小,优先级越高,越先执行。默认值是 10。
  • $accepted_args: 传递给函数的参数个数 (整数)。默认值是 1。

现在,让我们看看 WordPress 核心代码中 add_action 的实际实现(简化版,重点突出):

global $wp_filter, $wp_actions, $merged_filters;

function add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
    global $wp_filter, $wp_actions;

    $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( $wp_actions[ $tag ] ); // May be doing it for the first time, don't overwrite!

    return true;
}

代码解读:

  1. 全局变量: global $wp_filter, $wp_actions; 声明了两个全局变量 $wp_filter$wp_actions$wp_filter 是一个多维数组,用于存储所有的 action 和 filter 钩子,而 $wp_actions 则用于记录 action 钩子被触发的次数(用于调试和性能分析)。

  2. _wp_filter_build_unique_id(): 这个函数(我们稍后会详细讨论)的作用是为每个 action/filter 函数生成一个唯一的 ID。 即使你多次用相同的函数挂载到同一个钩子上,它也会确保每个实例都有唯一的 ID。

  3. 存储钩子信息: $wp_filter[ $tag ][ $priority ][ $idx ] = array(...) 这行代码是关键。它将钩子信息存储到 $wp_filter 数组中。 $wp_filter 的结构是这样的:

    $wp_filter = array(
        'hook_name' => array(
            priority_value => array(
                'unique_id' => array(
                    'function' => 'the_callback_function',
                    'accepted_args' => 1
                )
            )
        )
    );
    • hook_name: 钩子的名称 (例如,'publish_post', 'wp_head')。
    • priority_value: 优先级 (例如,10, 20)。
    • unique_id: 函数的唯一 ID (例如,'my_function_10')。
    • function: 要执行的函数 (例如,'my_function')。
    • accepted_args: 函数接受的参数个数。
  4. unset( $wp_actions[ $tag ] );: 清除 $wp_actions 数组中对应 $tag 键的值。这行代码确保如果 $tag 是第一次被添加到 action 中,不会覆盖 $wp_actions 中可能存在的其他值。

三、add_filter:修改你的数据

add_filter 函数与 add_action 非常相似,但它用于注册一个函数,该函数接收一个值作为输入,修改它,然后返回修改后的值。 让我们先看看 add_filter 的简化版原型:

function add_filter( string $tag, callable $function_to_add, int $priority = 10, int $accepted_args = 1 ): bool;

参数与 add_action 完全相同。 现在,让我们看看 WordPress 核心代码中 add_filter 的实际实现(简化版,重点突出):

global $wp_filter, $wp_actions, $merged_filters;

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

    return true;
}

代码解读:

你会发现 add_filter 的代码几乎与 add_action 完全相同! 关键的区别在于它们的使用方式。add_filter 主要用于修改数据,而 add_action 主要用于执行代码。

四、_wp_filter_build_unique_id():生成唯一的 ID

现在,让我们深入了解一下 _wp_filter_build_unique_id() 函数,它负责生成 action/filter 函数的唯一 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];
    }
}

代码解读:

  1. 静态变量: static $filter_id_count = 0; 使用静态变量来跟踪已经生成的 ID 数量。 这确保了每次调用该函数时,都会生成一个新的唯一 ID。 但是,现代 WordPress 已经不再依赖此变量。

  2. 处理不同类型的函数: 该函数可以处理以下类型的函数:

    • 字符串 (函数名): 如果 $function 是一个字符串,则直接返回该字符串作为 ID。
    • 对象 (闭包): 如果 $function 是一个对象 (通常是闭包),则使用 spl_object_hash() 函数生成该对象的唯一哈希值,并将其作为 ID。
    • 数组 (类方法/静态方法): 如果 $function 是一个数组,则它表示一个类方法或静态方法。在这种情况下,函数会将类名和方法名连接起来,形成 ID。
  3. 生成 ID: 根据函数的类型,函数会生成一个唯一的 ID,并将其返回。

五、do_actionapply_filters:触发钩子

现在我们知道如何使用 add_actionadd_filter 来注册钩子函数了,接下来让我们看看如何触发这些钩子。

  • do_action(): 用于触发 action 钩子。
  • apply_filters(): 用于触发 filter 钩子。

do_action()

function do_action( string $hook_name, mixed ...$args ) {
    global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;

    $wp_actions[ $hook_name ] = isset( $wp_actions[ $hook_name ] ) ? absint( $wp_actions[ $hook_name ] ) + 1 : 1;

    // Do 'all' actions first.
    if ( isset( $wp_filter['all'] ) ) {
        $wp_current_filter[] = $hook_name;
        _wp_call_all_hook( $args );
    }

    if ( ! isset( $wp_filter[ $hook_name ] ) ) {
        if ( isset( $wp_filter['all'] ) ) {
            array_pop( $wp_current_filter );
        }
        return;
    }

    if ( ! isset( $wp_filter['all'] ) ) {
        $wp_current_filter[] = $hook_name;
    }

    // Sort.
    if ( ! isset( $merged_filters[ $hook_name ] ) ) {
        ksort( $wp_filter[ $hook_name ] );
        $merged_filters[ $hook_name ] = true;
    }

    reset( $wp_filter[ $hook_name ] );

    foreach ( (array) current( $wp_filter[ $hook_name ] ) as $the_ ) {
        if ( ! is_null( $the_['function'] ) ) {
            call_user_func_array( $the_['function'], $args );
        }
    }

    array_pop( $wp_current_filter );
}

代码解读:

  1. 全局变量: 声明了多个全局变量,包括 $wp_filter (存储钩子信息), $wp_actions (记录 action 钩子被触发的次数), $merged_filters (用于优化排序), 和 $wp_current_filter (用于跟踪当前正在执行的钩子,用于防止循环调用)。

  2. $wp_actions 更新: $wp_actions[ $hook_name ] = ... 这行代码递增了 $wp_actions 数组中对应 $hook_name 键的值,用于记录 action 钩子被触发的次数。

  3. _wp_call_all_hook(): 如果存在 all 钩子,则调用 _wp_call_all_hook() 函数,将所有参数传递给它。 all 钩子是一个特殊的钩子,它会在所有其他 action 钩子被触发之前被触发。

  4. 检查钩子是否存在: if ( ! isset( $wp_filter[ $hook_name ] ) ) { return; } 如果 $wp_filter 数组中不存在 $hook_name 键,则表示没有函数挂载到该钩子上,直接返回。

  5. 排序: if ( ! isset( $merged_filters[ $hook_name ] ) ) { ksort( $wp_filter[ $hook_name ] ); ... } 如果 $merged_filters 数组中不存在 $hook_name 键,则表示该钩子的函数尚未排序。 ksort() 函数用于按照优先级对 $wp_filter[ $hook_name ] 数组进行排序。

  6. 循环执行函数: foreach ( (array) current( $wp_filter[ $hook_name ] ) as $the_ ) { ... } 这行代码循环遍历 $wp_filter[ $hook_name ] 数组中的所有函数,并使用 call_user_func_array() 函数调用它们。 call_user_func_array() 函数允许你使用一个数组作为参数来调用一个函数。

apply_filters()

function apply_filters( string $hook_name, mixed $value, mixed ...$args ) {
    global $wp_filter, $merged_filters, $wp_current_filter;

    $wp_current_filter[] = $hook_name;

    $args = array_slice( func_get_args(), 1 );

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

    array_pop( $wp_current_filter );

    return $value;
}

代码解读:

  1. 全局变量:do_action() 类似,声明了多个全局变量。

  2. 参数处理: $args = array_slice( func_get_args(), 1 ); 这行代码获取传递给 apply_filters() 函数的所有参数,并将它们存储到 $args 数组中。 array_slice() 函数用于从一个数组中提取一部分。

  3. 检查钩子是否存在: if ( ! isset( $wp_filter[ $hook_name ] ) ) { return $value; } 如果 $wp_filter 数组中不存在 $hook_name 键,则表示没有函数挂载到该钩子上,直接返回原始值 $value

  4. 排序:do_action() 类似,如果该钩子的函数尚未排序,则使用 ksort() 函数按照优先级对 $wp_filter[ $hook_name ] 数组进行排序。

  5. 循环执行函数: foreach ( (array) current( $wp_filter[ $hook_name ] ) as $the_ ) { ... } 这行代码循环遍历 $wp_filter[ $hook_name ] 数组中的所有函数,并使用 call_user_func_array() 函数调用它们。 关键的区别在于,apply_filters 会将前一个函数返回的值作为下一个函数的输入。

六、总结:钩子的魔力

add_actionadd_filter 是 WordPress 插件和主题开发的核心。 它们允许你以一种非常灵活的方式扩展和修改 WordPress 的核心行为,而无需直接修改核心代码。 通过理解它们的内部工作原理,你可以更好地利用它们,构建更强大、更灵活的 WordPress 应用。

函数 用途 核心机制
add_action 注册一个函数,在特定的 action 钩子被触发时执行。 将函数信息存储到全局 $wp_filter 数组中,按照钩子名称和优先级进行组织。
add_filter 注册一个函数,修改数据。 add_action 类似,将函数信息存储到全局 $wp_filter 数组中。
do_action 触发 action 钩子,执行挂载到该钩子上的所有函数。 $wp_filter 数组中检索与指定钩子名称匹配的所有函数,按照优先级顺序执行它们。
apply_filters 触发 filter 钩子,并允许挂载的函数修改传递给钩子的值。 $wp_filter 数组中检索与指定钩子名称匹配的所有函数,按照优先级顺序执行它们。每个函数接收前一个函数返回的值作为输入,并将修改后的值传递给下一个函数。最后,apply_filters 返回经过所有函数修改后的最终值。

希望今天的讲解对你有所帮助! 如果还有其他问题,随时提问。下次再见!

发表回复

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