剖析 `add_action` 和 `add_filter` 的源码,它们如何将钩子函数添加到全局 `Array` 数组?

各位同学,各位朋友,大家好!我是你们今天的“钩子函数挖掘机”——老码。今天咱们不讲什么高深的理论,就来聊聊WordPress里两个非常重要,但又让人觉得有点神秘的函数:add_actionadd_filter

这两个家伙,就像WordPress世界的“红娘”,专门负责把各种函数(也就是咱们的钩子函数)“嫁”接到特定的“事件”上。 听起来玄乎? 别怕,咱们今天就扒开它们的源代码,看看它们到底是怎么运作的,又是怎么把这些钩子函数“登记”到全局数组里的。准备好了吗? 咱们发车!

一、钩子函数是个啥?为什么要用它?

在深入源码之前,咱们先简单回顾一下钩子函数的概念。 想象一下,你正在组装一辆汽车。 汽车的引擎盖、车门、轮胎等等,都是一个个独立的部件。 你想在引擎盖盖上之后,播放一段音乐,怎么办?

传统的做法,你可能需要直接修改引擎盖的组装代码,在组装完成的地方加上播放音乐的代码。 但是,这样做有两个问题:

  1. 侵入性太强: 直接修改别人的代码,万一改错了,或者以后别人升级了,你的修改就可能失效了。
  2. 耦合度太高: 引擎盖组装的代码和播放音乐的代码紧密耦合在一起,修改任何一方都可能影响另一方。

钩子函数就是来解决这个问题的。 它允许你在不修改原始代码的情况下,插入自己的代码,就像在汽车上预留了一些“挂钩”,你可以在这些“挂钩”上挂上任何你想要的东西,比如播放音乐的模块。

在WordPress里,add_actionadd_filter就是用来注册这些“挂钩”的,而你提供的函数,就是挂在这些“挂钩”上的东西。

二、add_actionadd_filter 的“前世今生”

add_actionadd_filter 的作用非常相似,都是用来注册钩子函数的。 它们的主要区别在于:

  • add_action: 用于注册“动作”(Action)钩子,主要目的是执行一些操作,比如发送邮件、更新数据库等等。 它不期望钩子函数返回任何值。
  • add_filter: 用于注册“过滤器”(Filter)钩子,主要目的是修改数据,比如修改文章内容、修改标题等等。 它期望钩子函数返回修改后的数据。

简单来说,add_action 就像是“通知”一声,让钩子函数去做一些事情;而 add_filter 就像是“询问”一声,让钩子函数来修改一些数据。

三、源码探秘:add_actionadd_filter 的“庐山真面目”

好了,废话不多说,咱们直接上代码! 以下是 add_actionadd_filter 的简化版源码(为了方便讲解,我省略了一些不重要的细节):

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

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

    // 确保 $function_to_add 是一个有效的 callable (函数或者类的方法)
    if ( ! is_callable( $function_to_add ) ) {
        return false;
    }

    $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;
}

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] ) ) {
      // Object Class Calling
      if ( function_exists( 'spl_object_hash' ) ) {
          return spl_object_hash( $function[0] ) . $function[1];
      } else {
          $obj_idx = get_class( $function[0] ) . $function[1];
          if ( ! isset( $this->filter_id_instances[ $obj_idx ] ) ) {
              $this->filter_id_instances[ $obj_idx ] = $filter_id_count;
              ++$filter_id_count;
          }
          return $this->filter_id_instances[ $obj_idx ];
      }
  } elseif ( is_string( $function[0] ) ) {
      // Static Calling
      return $function[0] . '::' . $function[1];
  }
}

看到没? add_action 实际上就是调用了 add_filter! 这也印证了咱们前面说的,add_action 只是 add_filter 的一个“马甲”。

接下来,咱们重点分析 add_filter 函数:

  1. global $wp_filter;: 这行代码非常关键! 它声明了一个全局变量 $wp_filter。 这个变量就是一个多维数组,用来存储所有的钩子函数。 它就像一个巨大的“钩子函数登记表”。

  2. is_callable( $function_to_add ): 这行代码用来判断 $function_to_add 是否是一个有效的函数或方法。 如果不是,就直接返回 false,说明注册失败。

  3. $idx = _wp_filter_build_unique_id( $tag, $function_to_add, $priority );:这行代码调用 _wp_filter_build_unique_id 函数生成一个唯一的ID,用于标识这个钩子函数。

  4. $wp_filter[ $tag ][ $priority ][ $idx ] = array(...): 这行代码是核心! 它把钩子函数的信息存储到 $wp_filter 数组里。 咱们来仔细看看这个数组的结构:

    • $tag: 表示钩子的名称,比如 'the_content' (文章内容) 或者 'wp_head' (页面头部)。 这就像登记表上的“事件名称”。
    • $priority: 表示钩子的优先级,数字越小,优先级越高。 也就是说,优先级高的钩子函数会先执行。 这就像登记表上的“执行顺序”。
    • $idx: 表示钩子的唯一ID,用于区分同一个钩子上注册的多个函数。
    • array(...): 一个包含钩子函数信息的数组,包括:
      • 'function': 钩子函数本身。
      • 'accepted_args': 钩子函数接受的参数个数。

四、$wp_filter 数组的“真面目”

为了更好地理解 $wp_filter 数组,咱们来举个例子。 假设我们注册了两个钩子函数:

add_filter( 'the_content', 'my_content_filter_1', 10, 1 );
add_filter( 'the_content', 'my_content_filter_2', 20, 1 );

那么,$wp_filter 数组的结构可能如下所示(简化版):

$wp_filter = array(
    'the_content' => array(
        10 => array(
            'my_content_filter_1' => array(
                'function' => 'my_content_filter_1',
                'accepted_args' => 1
            )
        ),
        20 => array(
            'my_content_filter_2' => array(
                'function' => 'my_content_filter_2',
                'accepted_args' => 1
            )
        )
    )
);

从这个例子可以看出,$wp_filter 数组是一个三维数组,它的结构非常清晰,方便WordPress在执行钩子的时候查找和调用相应的函数。

五、_wp_filter_build_unique_id 函数:生成唯一ID的“秘密武器”

咱们再来看看 _wp_filter_build_unique_id 函数。 这个函数的作用是为每个钩子函数生成一个唯一的ID。 为什么要生成唯一ID呢? 因为同一个钩子上可能会注册多个相同的函数,如果没有唯一ID,就无法区分它们了。

这个函数的实现比较复杂,它考虑了各种情况,包括:

  • 钩子函数是一个简单的函数名(字符串)。
  • 钩子函数是一个对象的方法。
  • 钩子函数是一个静态方法。
  • 钩子函数是一个闭包函数(匿名函数)。

对于不同的情况,它会采用不同的方法生成唯一ID,确保每个钩子函数都有一个独一无二的身份。

六、表格总结:add_actionadd_filter 的参数

为了方便大家理解,我把 add_actionadd_filter 的参数用表格总结一下:

参数名称 类型 描述
$tag string 钩子的名称,也就是“事件名称”。
$function_to_add callable 要注册的钩子函数。 可以是函数名(字符串)、对象的方法、静态方法或者闭包函数。
$priority int 钩子的优先级,数字越小,优先级越高。 默认为 10。
$accepted_args int 钩子函数接受的参数个数。 默认为 1。

七、实际应用:在WordPress中如何使用 add_actionadd_filter

说了这么多理论,咱们来点实际的。 下面是一些在WordPress中使用 add_actionadd_filter 的例子:

  • 修改文章内容:
function my_custom_content( $content ) {
    // 在文章内容后面加上 "Hello World!"
    return $content . 'Hello World!';
}
add_filter( 'the_content', 'my_custom_content' );
  • 在页面头部添加自定义CSS:
function my_custom_css() {
    echo '<style>body { background-color: #f0f0f0; }</style>';
}
add_action( 'wp_head', 'my_custom_css' );
  • 发送邮件:
function my_send_email() {
    wp_mail( '[email protected]', '主题', '内容' );
}
add_action( 'publish_post', 'my_send_email' ); // 在文章发布时发送邮件

这些例子只是冰山一角,add_actionadd_filter 的应用非常广泛,几乎可以覆盖WordPress的所有方面。

八、总结:add_actionadd_filter 的“灵魂”

通过今天的源码分析,咱们可以得出以下结论:

  • add_actionadd_filter 是WordPress的核心函数,用于注册钩子函数。
  • 它们实际上都是通过操作全局变量 $wp_filter 来实现的。
  • $wp_filter 是一个多维数组,用于存储所有的钩子函数信息。
  • _wp_filter_build_unique_id 函数用于为每个钩子函数生成唯一的ID。

理解了 add_actionadd_filter 的原理,你就可以更好地掌握WordPress的钩子机制,从而更加灵活地定制和扩展WordPress的功能。

好了,今天的“钩子函数挖掘”就到这里。 希望大家有所收获,下次再见!

发表回复

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