钩子是用一段代码添加或修改另外一段代码的方式,是 WordPress 插件和主题与 WordPress 内核交互的基础,钩子在 WordPress 内核中也被广泛使用。WordPress 中有两种钩子,Action 和 Filter。使用钩子时,我们需要先编写一个自定义函数作为钩子的回调函数,然后使用 add_action 或 add_filter 函数将我们的回调函数挂载到指定的 Action 或 Filter 上。

Action 可以让我们在函数执行的某个时间点添加一些自定义操作(如输出内容到文章尾部),Filter 和 Action 类似,不同的是我们可以通过 Filter 修改并返回数据,因此,挂载到 Filter 上的函数会接受一些变量,并返回修改后的变量。简单来说:

Action 用来添加功能,Filter 用来修改数据。

WordPress 内核提供了很多钩子,来帮助开发者开发 WordPress 主题或插件。

WordPress Action 和 Filter 钩子参考链接

# 4.1 Action 钩子

Action 是 WordPress 的两种钩子之一,提供了一种在 WordPress 核心、主题或插件执行的特定时间点运行附加函数的功能。Action 和 Filter 是不一样的。

# 添加 Action 的操作

我们可以通过两个步骤添加一个函数到某个 Action。首先,我们需要创建一个回调函数,这个函数在 Action 运行时会被调用。其次,我们需要把这个函数挂载到对应的 Action 钩子上面。使用 add_action() 函数,至少需要传递两个参数 $tag (钩子名称) 和 $function_to_add (回调函数名)。

下面的例子在 init 钩子执行时运行:


<?php
function mla_custom() {
    // 执行某些操作
}
add_action('init', 'mla_custom');

1
2
3
4
5
6
7

# 其他参数

add_action() 也可以接受两个额外的参数,$priority (整数) 规定了回调函数执行的优先级,和 $accepted_args (整数) 规定了传递给回调函数的参数数量。

# 优先级

如果一个钩子上面挂载了多个回调函数,钩子就需要一个优先级,来确定这些回调函数的执行顺序。优先级为整数,默认值为 10,数字越小,优先级就越高。比如,优先级为 11 的函数将在优先级为 10 的函数之后执行,优先级为 9 的函数将在优先级为 10 的函数之前执行。

.e.g. 下面的回调函数全部挂载到了 init 钩子上面,但他们有不同的优先级。


<?php
add_action('init', 'run_me_early', 9);
add_action('init', 'run_me_normal'); // 如果没有指定优先级,默认为 10
add_action('init', 'run_me_late', 11);

1
2
3
4
5
6

在上面的钩子运行时,第一个运行的函数是 run_me_early(),run_me_normal(), 最后一个运行的函数 run_me_late()。

# 参数个数

有时候,回调函数需要接收一些额外的数据作为函数的参数。例如,当 WordPress 保存一篇文章时,将会运行 save_post 钩子,这个钩子会传递两个参数给回调函数:保存的文章 ID 和 文章对象:


<?php
do_action('save_post', $post->ID, $post);

1
2
3
4

所以,当我们挂载函数到 save_post 钩子时,我们可以指定它需要接收这两个参数:


<?php
add_action('save_post', 'mla_custom', 10, 2);

1
2
3
4

然后我们就可以在回调函数中使用钩子提供的参数了。


<?php
function mla_custom($post_id, $post){
    // 执行某些操作
}

1
2
3
4
5
6

.e.g.

假设我们需要在 WordPress 的前端文章查询中修改获取搜索结果的查询,我们可以使用 pre_get_posts 钩子。


<?php
function mla_search($query) {
    if (!is_admin() && $query->is_main_query() && $query->is_search) {
        $query->set('post_type', ['post', 'movie']);
    }
}
add_action('pre_get_posts', 'mla_search');

1
2
3
4
5
6
7
8
9

# 4.2 Filter 钩子

Filter 是 WordPress 钩子两种类型中的另外一个,可以让我们通过注册到某个 Filter 钩子上的回调函数来修改某些函数产生的数据。与 Action 不同,Filter 应该以独立的方式运行,不应该有影响全局变量和输出的副作用。

# 添加 Filter

我们可以通过两个步骤挂载一个回调函数到某个 Filter 上。首先,我们需要创建一个回调函数,这个函数在 Action 运行时被调用。其次,我们需要把这个函数挂载到对应的 Action 钩子上面。

我们可以使用 add_filter() 函数挂载一个回调函数到 Filter 钩子上面,add_filter 函数至少需要两个参数:$tag (字符) 和 $function_to_add (回调函数名)。下面的例子将在 the_title Filter 执行时运行。

假设我们有一篇标题为“学习 WordPress 插件开发” 的文章,下面的例子将会在显示标题时把标题修改为 “文章:学习 WordPress 插件开发已被修改”,我们可以上文获取可用的钩子列表。


<?php
function mla_filter_title($title) {
    return '文章:' . $title . '已被修改。';
}
add_filter('the_title', 'mla_filter_title');

1
2
3
4
5
6
7

# 其他参数

add_filter() 也可以接受两个额外的参数,$priority (整数) 规定了回调函数执行的优先级,和 $accepted_args (整数) 规定了传递给回调函数的参数数量。

.e.g.

在 <body> 满足特定条件时,向标签添加 CSS 类:


<?php
function mla_css_body_class($classes) {
    if (!is_admin()) {
        $classes[] = 'mla-is-awesome';
    }
    return $classes;
}
add_filter('body_class', 'mla_css_body_class');

1
2
3
4
5
6
7
8
9
10

# 4.3 自定义钩子

一个非常重要但经常被忽略的做法是在我们可以在插件中使用自定义钩子,以便其他开发者可以扩展或修改我们的插件。自定义钩子的创建和使用方式和 WordPress 核心钩子相同。

# 创建一个自定义钩子

我们使用 do_action() 为创建 Action 钩子,使用 apply_filters() 创建 Filter 钩子。

建议在输出到浏览器的文本上使用 apply_filters(),特别是在前端,这可以方便用户根据需求修改插件输出。

我们使用add_action() 添加回调函数到自定义 Action 钩子上,使用 add_filter() 添加回调函数到自定义 Filter 钩子上。

# 挂载回调函数到自定义钩子

我们使用 add_action() 添加回调函数到自定义 Action 钩子上,使用 add_filter() 添加回调函数到自定义 Filter 钩子上。

# 命名冲突

由于任何主题或插件都可以创建自定义钩子,为了避免与钩子名称冲突,我们应该在钩子名称前添加一个自定义前缀,这一点非常重要。例如,名为 email_body 的 Filter 就非常容易产品冲突,因为其他插件开发人员可能发会选择相同 Filter 名称,如果用户同时安装了这两个插件,就可能会导致难以追踪的错误。

给 Filter 名称加一个前缀,如 wporg_email_body(其中 wporg_ 是我们插件的唯一前缀),会避免和其他插件产生 Filter 名称冲突。

.e.g.

# 可扩展 Action:设置表单

如果我们的插件添加了一个设置表单到 “仪表盘” 中,我们可以使用自定义 Action 来允许其他插件开发者添加他们自己的设置到我们的插件中。

<?php
function mla_settings_page_html()
{
   ?>
    Foo: <input id=foo name=foo type=text>
    Bar: <input id=bar name=bar type=text>
   <?php
   do_action('mla_after_settings_page_html');
}
1
2
3
4
5
6
7
8
9

如下,另外一个插件开发者挂载了一个回调函数到 wporg_after_settings_page_html Action 上面,来添加新设置。

<?php
function mla_add_settings()
{
   ?>
    New 1: <input id=new_setting name=new_settings type=text>
   <?php
}
add_action('mla_after_settings_page_html', 'mla_add_settings');

1
2
3
4
5
6
7
8
9

# 可扩展 Filter:自定义文章类型

在下面的例子中,当我们注册自定义文章类型时,把文章类型的参数传递给了一个自定义 Filter,另外一个插件开发者可以在创建自定义文章类型之前修改这个参数。

<?php
function mla_create_post_type()
{
   $post_type_params = [/* ... */];

   register_post_type(
      'post_type_slug',
      apply_filters('mla_post_type_params', $post_type_params)
   );
}
1
2
3
4
5
6
7
8
9
10

现在,另外一个插件开发者可以挂载一个回调函数到 mla_post_type_params Filter 上面,来修改自定义文章类型的参数。

<?php
function mla_change_post_type_params($post_type_params)
{
   $post_type_params['hierarchical'] = true;
   return $post_type_params;
}
add_filter('mla_post_type_params', 'mla_change_post_type_params');
1
2
3
4
5
6
7

# 4.4 高级主题

# 删除挂载到 Action 和 Filter 上的回调函数

有时候,我们需要删除一个注册到一个插件、主题甚至是 WordPress 核心的钩子上的回调函数。这时,我们可以使用 remove_action() 删除挂载到 Action 上的回调函数,使用 remove_filter() 删除挂载到 Filter 上的回调函数。传递给 remove_action 和 remove_filter 的参数应该和使用 add_action 和 add_filter 函数注册他们的参数相同。

要成功删除回调函数,必须在注册回调函数后执行删除操作,执行顺序很重要。

.e.g.

举个例子,我们需要删除不必要的功能来提高大型主题的性能。让我们来看一下主题代码的 functions.php

<?php
function mla_theme_setup_slider()
{
   // ...
}
add_action('template_redirect', 'mla_theme_setup_slider', 9);
1
2
3
4
5
6

这个主题的 my_theme_setup_slider 函数添加了一个我们不需要的幻灯模块,这个模块会加载一个非常大的 CSS 文件,然后初始化一个 JavaScript 文件,这个文件使用了 1.024MB 的自定义库,我们可以通过卸载这个功能来移除这个幻灯模块。

由于我们需要在 my_theme_setup_slider 回调函数注册后 (在 functions.php 中执行)卸载这个函数,执行这个卸载的最好的时机就是after_setup_theme钩子。

<?php
function mla_disable_slider()
{
   // 确保所有参数与 add_action() 调用完全匹配
   remove_action('template_redirect', 'mla_theme_setup_slider', 9);
}
// 确保在调用 add_action() 之后调用 remove_action()
add_action('after_setup_theme', 'mla_disable_slider');
1
2
3
4
5
6
7
8

# 删除所有回调

我们可以使用 remove_all_actions()remove_all_filters() 来删除挂载到某个钩子上面的所有回调。

# 确定当前钩子

有时候,我们需要在多个钩子上挂载同一个回调函数,在回调函数中,我们需要根据它所挂载到的钩子来确定对应的操作。我们可以使用 current_action() / current_filter() 来确定当前的 Action 或 Filter。

<?php
function mla_modify_content($content)
{
   switch (current_filter()) {
      case 'the_content':
         // 执行某些操作
         break;
      case 'the_excerpt':
         // 执行某些操作
         break;
   }
   return $content;
}
add_filter('the_content', 'mla_modify_content');
add_filter('the_excerpt', 'mla_modify_content');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 检查钩子是否已经运行了

一些钩子在执行过程中可能会被多次调用,但是我们只希望它被调用一次,在这种情况下,我们可以使用 did_action() 来检查钩子运行了多少次。

<?php
function mla_custom()
{
   if (did_action('save_post') !== 1) {
      return;
   }
   // ...
}
add_action('save_post', 'mla_custom');
1
2
3
4
5
6
7
8
9

# 使用 “全部” 钩子进行调试

如果我们想要一个回调函数在每一个钩子上面都被触发,我们可以挂载回调函数到 ‘all’ 钩子上面。在进行调试时,这个技巧非常有用,可以帮助我们确定某个事件在什么时候发生,页面在什么时候崩溃等等。