深入理解 `do_action()` 和 `apply_filters()` 的区别,以及它们在源码中的具体实现。

大家好!今天咱们来聊聊WordPress里一对儿好基友,do_action()apply_filters()。 这俩家伙,一个负责“发布消息”,一个负责“改头换面”,在WordPress插件开发中那是相当的重要。 搞明白它们的区别和用法,你的WordPress技能就能更上一层楼。

第一节:Action(动作)——“广播站”的那些事儿

想象一下,do_action() 就是个大型广播站,它会发出各种“广播”,告诉大家现在发生了什么事。 比如说,“文章发布了!”,“主题初始化完成了!” 等等。 插件们就像是收音机,可以选择接收自己感兴趣的“广播”。 听到广播后,插件可以执行相应的操作,比如发送邮件,更新数据库,或者做其他任何事情。

1.1 do_action() 的基本用法

do_action() 的基本语法是这样的:

do_action( string $tag, mixed ...$arg );
  • $tag: 广播的“频道名称”,也就是钩子的名称。 这很重要,插件就是通过这个频道来“收听”广播的。
  • $arg: 广播的内容,可以是一个或多个参数,传递给监听这个动作的函数。

举个例子,WordPress在文章发布后会执行 do_action('publish_post', $post_id, $post)

do_action( 'publish_post', $post_id, $post );

这里,publish_post 就是广播的频道名称, $post_id 是文章的ID,$post 是文章对象。

1.2 插件如何“收听” Action

插件使用 add_action() 函数来“收听”特定的广播频道。

add_action( string $tag, callable $function_to_add, int $priority = 10, int $accepted_args = 1 );
  • $tag: 要收听的广播频道名称。
  • $function_to_add: 当听到广播后,要执行的函数。
  • $priority: 优先级,数字越小,优先级越高,越先执行。
  • $accepted_args: 你的函数希望接收到的参数个数。

例如,如果你想在文章发布后发送邮件,可以这样做:

function my_awesome_function( $post_id, $post ) {
  // 发送邮件的代码
  wp_mail( '[email protected]', 'New Post Published', 'Post ID: ' . $post_id . ', Title: ' . $post->post_title );
}

add_action( 'publish_post', 'my_awesome_function', 10, 2 );

这段代码的意思是:当 publish_post 广播响起时,执行 my_awesome_function 函数,并且接收两个参数($post_id$post)。

1.3 do_action() 的源码分析(简化版)

虽然WordPress的源码很复杂,但 do_action() 的核心逻辑其实很简单:

function do_action( $tag, ...$args ) {
  global $wp_filter, $wp_actions, $wp_current_filter;

  if ( ! isset( $wp_filter[ $tag ] ) ) {
    return; // 如果没有插件监听这个频道,就直接返回
  }

  $wp_current_filter[] = $tag; // 记录当前正在执行的 action

  $args = func_get_args(); // 获取所有参数
  array_shift( $args ); // 移除第一个参数($tag)

  foreach ( $wp_filter[ $tag ] as $priority => $functions ) { // 遍历所有监听这个频道的函数(按优先级排序)
    foreach ( $functions as $function ) {
      call_user_func_array( $function['function'], $args ); // 执行函数
    }
  }

  array_pop( $wp_current_filter ); // 移除当前正在执行的 action
}

简单来说,do_action() 的工作就是:

  1. 检查是否有插件监听这个频道。
  2. 如果有,就按优先级顺序执行所有监听函数,并将参数传递给这些函数。
  3. 如果没有,就什么也不做。

1.4 Action 的特点总结

特性 描述
目的 触发事件,通知插件执行某些操作。
返回值 无返回值。 Action 主要用于执行操作,而不是修改数据。
作用 允许插件在特定时刻执行自定义代码,例如发送邮件,更新统计数据,缓存清理等等。
监听方式 使用 add_action() 函数来注册监听函数。
常见用途 在文章发布、主题初始化、用户登录等事件发生时,执行自定义操作。
源码核心 遍历监听函数,按优先级执行。
比喻 大型广播站,发布各种事件广播,插件可以选择收听。

第二节:Filter(过滤器)——“美容院”的那些魔法

现在,我们来看看 apply_filters()。 你可以把 apply_filters() 想象成一家美容院。它接收一个值(比如一篇文章的内容),然后让各种“美容师”(插件)对这个值进行修改、加工,最后返回修改后的值。

2.1 apply_filters() 的基本用法

apply_filters() 的基本语法是这样的:

apply_filters( string $tag, mixed $value, mixed ...$arg );
  • $tag: 过滤器的名称,也就是“美容项目”的名称。
  • $value: 要过滤的值,也就是要“美容”的对象。
  • $arg: 可选的额外参数,传递给过滤函数。

举个例子,WordPress在显示文章内容之前会执行 apply_filters('the_content', $content)

$content = apply_filters( 'the_content', $content );

这里,the_content 是过滤器的名称,$content 是文章的内容。

2.2 插件如何“参与美容”

插件使用 add_filter() 函数来注册一个过滤函数。

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

参数和 add_action() 类似,只是作用略有不同。 $accepted_args 表示你的函数希望接收到的参数个数,其中第一个参数必须是要过滤的值。

例如,如果你想在文章内容后面添加一个版权声明,可以这样做:

function my_awesome_filter( $content ) {
  $copyright = '<p>Copyright 2023. All rights reserved.</p>';
  return $content . $copyright;
}

add_filter( 'the_content', 'my_awesome_filter' );

这段代码的意思是:当 the_content 过滤器被调用时,执行 my_awesome_filter 函数,并将文章内容作为参数传递给它。 my_awesome_filter 函数会在文章内容后面添加版权声明,并返回修改后的内容。

2.3 apply_filters() 的源码分析(简化版)

apply_filters() 的核心逻辑如下:

function apply_filters( $tag, $value, ...$args ) {
  global $wp_filter, $wp_current_filter, $wp_merged_filters;

  if ( ! isset( $wp_filter[ $tag ] ) ) {
    return $value; // 如果没有插件注册过滤器,就直接返回原始值
  }

  if ( ! isset( $wp_merged_filters[ $tag ] ) ) {
    ksort( $wp_filter[ $tag ] ); // 按优先级排序
    $wp_merged_filters[ $tag ] = true;
  }

  $wp_current_filter[] = $tag;

  $args = func_get_args(); // 获取所有参数

  // 确保第一个参数是要过滤的值
  $hook_args = array_slice( $args, 1 ); // 从第二个参数开始,提取所有参数

  foreach ( $wp_filter[ $tag ] as $priority => $functions ) {
    foreach ( $functions as $function ) {
      $args[1] = $value; // 始终确保第二个参数是要过滤的值
      $result = call_user_func_array( $function['function'], $hook_args );
      if ( ! is_null( $result ) ) {
        $value = $result; // 用过滤后的值更新 $value
      }
    }
  }

  array_pop( $wp_current_filter );

  return $value; // 返回最终过滤后的值
}

简单来说,apply_filters() 的工作就是:

  1. 检查是否有插件注册了过滤器。
  2. 如果没有,就直接返回原始值。
  3. 如果有,就按优先级顺序执行所有过滤函数,并将原始值和额外参数传递给这些函数。
  4. 每个过滤函数都应该返回修改后的值,apply_filters() 会用这个值更新 $value 变量。
  5. 最后,apply_filters() 返回最终过滤后的值。

2.4 Filter 的特点总结

特性 描述
目的 修改数据,允许插件对数据进行加工、处理。
返回值 必须返回修改后的值。
作用 允许插件修改文章内容、标题、评论、URL 等等。
监听方式 使用 add_filter() 函数来注册过滤函数。
常见用途 在文章显示、URL生成、评论处理等过程中,修改数据。
源码核心 遍历过滤函数,按优先级执行,并用每个函数返回的值更新原始值。
比喻 美容院,接收一个值,让各种“美容师”对这个值进行修改,最后返回修改后的值。

第三节:Action vs Filter – 终极PK!

现在,我们来总结一下 do_action()apply_filters() 的区别:

特性 do_action() apply_filters()
目的 触发事件,通知插件执行操作。 修改数据,允许插件对数据进行加工。
返回值 无返回值。 必须返回修改后的值。
作用 允许插件在特定时刻执行自定义代码。 允许插件修改数据,例如文章内容、标题等。
使用场景 当某个事件发生时,需要通知插件执行某些操作。 当需要对某个数据进行修改时。
比喻 广播站 美容院
重要性 像一个大喇叭,告诉大家发生了什么。 像一个魔术棒,可以改变事物的形态。

3.1 一道练习题

假设你想要开发一个插件,实现以下功能:

  1. 在文章发布后,发送一条推特。
  2. 在文章标题后面添加一个“New!” 标签。

你应该如何使用 do_action()apply_filters()

答案:

  1. 发送推特: 使用 do_action()。 你可以在 publish_post action 中注册一个函数,这个函数负责发送推特。

    function my_tweet_function( $post_id, $post ) {
      // 发送推特的代码
      // 这里需要调用 Twitter API
      $tweet_text = $post->post_title . ' - ' . get_permalink( $post_id );
      // ... 发送推特的代码 ...
    }
    
    add_action( 'publish_post', 'my_tweet_function', 10, 2 );
  2. 添加 "New!" 标签: 使用 apply_filters()。 你可以在 the_title filter 中注册一个函数,这个函数负责在文章标题后面添加 "New!" 标签。

    function my_new_title_filter( $title ) {
      return $title . ' <span class="new-label">New!</span>';
    }
    
    add_filter( 'the_title', 'my_new_title_filter' );

第四节:进阶技巧:优先级和参数

4.1 优先级的重要性

add_action()add_filter() 都有一个 $priority 参数,它决定了插件执行的顺序。 优先级数值越小,执行顺序越靠前。 默认优先级是 10。

例如,如果你有两个插件都监听了 the_content 过滤器:

  • 插件 A 的优先级是 5。
  • 插件 B 的优先级是 15。

那么,插件 A 会在插件 B 之前执行。

合理设置优先级可以避免插件之间的冲突,保证插件的正常运行。

4.2 如何传递参数

do_action()apply_filters() 都可以传递多个参数。 插件可以通过 $accepted_args 参数来指定希望接收到的参数个数。

例如,如果你的 action 或者 filter 传递了三个参数:

do_action( 'my_custom_action', $arg1, $arg2, $arg3 );

apply_filters( 'my_custom_filter', $value, $arg2, $arg3 );

你的插件应该这样注册:

function my_custom_function( $arg1, $arg2, $arg3 ) {
  // ...
}

add_action( 'my_custom_action', 'my_custom_function', 10, 3 );

function my_custom_filter( $value, $arg2, $arg3 ) {
  // ...
  return $value;
}

add_filter( 'my_custom_filter', 'my_custom_filter', 10, 3 );

注意:在 apply_filters() 中,第一个参数始终是要过滤的值。

第五节:总结与实践

今天,我们深入探讨了 do_action()apply_filters() 的区别和用法。 记住,do_action() 用于触发事件,apply_filters() 用于修改数据。 掌握了它们,你就掌握了 WordPress 插件开发的精髓。

为了巩固学习,建议你尝试开发一些简单的 WordPress 插件,例如:

  • 一个在文章发布后发送邮件的插件。
  • 一个在文章内容后面添加广告的插件。
  • 一个修改文章标题样式的插件。

通过实践,你会更深入地理解 do_action()apply_filters() 的强大之处。

祝大家学习愉快! 下课!

发表回复

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