WordPress插件开发教程手册 — 钩子(Hooks)
钩子是用一段代码添加/修改另外一段代码的方式,是 WordPress插件和主题与 WordPress 内核交互的基础,钩子在 WordPress 内核中也被广泛使用。WordPress 中有两种钩子,Action 和 Filter。使用钩子时,我们需要先编写一个自定义函数作为钩子的回调函数,然后使用 add_action 或 add_filter 函数将我们的回调函数挂载到指定的 Action 或 Filter 上。
Action 可以让我们在函数执行的某个时间点添加一些自定义操作(如输出内容到文章尾部),Filter 和 Action 类似,不同的是我们可以通过 Filter 修改并返回数据,因此,挂载到 Filter 上的函数会接受一些变量,并返回修改后的变量。简单来说,Action 用来添加功能,Filter 用来修改数据。
WordPress 内核提供了很多钩子,来帮助开发者开发WordPress主题或插件。通过创建自定义钩子,我们也可以让第三方开发者添加或修改我们的功能。
WordPress Action 和 Filter 钩子参考链接
Action 钩子
Action 是 WordPress 的两种钩子之一,提供了一种在 WordPress 核心、主题或插件执行的特性时间点运行附加函数的功能。Action 和 Filter 是不一样的。
添加 Action 的操作
我们可以通过两个步骤添加一个函数到某个 Action。首先,我们需要创建一个回调函数,这个函数在 Action 运行时会被调用。其次,我们需要把这个函数挂载到对应的 Action 钩子上面。使用 add_action() 函数,至少需要传递两个参数 $tag (钩子名称) 和 $function_to_add (回调函数名)。
下面的例子在 init 钩子执行时运行:
function wporg_custom() {
// 执行某些操作
}
add_action('init', 'wporg_custom');
我们可以参考上文获取可用的钩子列表。随着开发经验的日益增长,我们可以通过查看 WordPress 核心、主题或插件的源代码找到更多合适的钩子。
其他参数
add_action() 也可以接受两个额外的参数,$priority (整数) 规定了回调函数执行的优先级,和 $accepted_args (整数) 规定了传递给回调函数的参数数量。
优先级
如果一个钩子上面挂载了多个回调函数,钩子就需要一个优先级,来确定这些回调函数的执行顺序。优先级为整数,默认值为 10,数字越小,优先级就越高。比如,优先级为 11 的函数将在优先级为 10 的函数之后执行,优先级为 9 的函数将在优先级为 10 的函数之前执行。
例如,下面的回调函数全部挂载到了 init 钩子上面,但他们有不同的优先级。
add_action('init', 'run_me_early', 9);
add_action('init', 'run_me_normal'); // 如果没有指定优先级,默认为 10
add_action('init', 'run_me_late', 11);
在上面的钩子运行时,第一个运行的函数是 run_me_early(),run_me_normal(), 最后一个运行的函数 run_me_late()。
参数个数
有时候,回调函数需要接收一些额外的数据作为函数的参数。例如,当 WordPress 保存一篇文章时,将会运行 save_post
钩子,这个钩子会传递两个参数给回调函数:保存的文章 ID 和 文章对象:
do_action('save_post', $post->ID, $post);
所以,当我们挂载函数到 save_post
钩子时,我们可以指定它需要接收这两个参数:
add_action('save_post', 'wporg_custom', 10, 2);
然后我们就可以在回调函数中使用钩子提供的参数了。
function wporg_custom($post_id, $post){
// 执行某些操作
}
示例
假设我们需要在 WordPress 的前端文章查询中修改获取搜索结果的查询,我们可以使用 pre_get_posts
钩子。
function wporg_search($query) {
if (!is_admin() && $query->is_main_query() && $query->is_search) {
$query->set('post_type', ['post', 'movie']);
}
}
add_action('pre_get_posts', 'wporg_search');
Filter 钩子
Filter 是 WordPress 钩子两种类型中的另外一个,可以让我们通过注册到某个 Filter 钩子上的回调函数来修改某些函数产生的数据。与 Action 不同,Filter 应该以独立的方式运行,不应该有影响全局变量和输出的副作用。
添加 Filter
我们可以通过两个步骤挂载一个回调函数到某个 Filter 上。首先,我们需要创建一个回调函数,这个函数在 Action 运行时被调用。其次,我们需要把这个函数挂载到对应的 Action 钩子上面。
我们可以使用 add_filter() 函数挂载一个回调函数到 Filter 钩子上面,add_filter 函数至少需要两个参数:$tag (字符) 和 $function_to_add (回调函数名)。下面的例子将在 the_title Filter 执行时运行。
function wporg_filter_title($title) {
return '文章:' . $title . '已被修改。';
}
add_filter('the_title', 'wporg_filter_title');
假设我们有一篇标题为“学习 WordPress插件开发” 的文章,上面的例子将会在显示标题时把标题修改为 “文章:学习 WordPress插件开发已被修改”,我们可以上文获取可用的钩子列表。随着开发经验的日益增长,我们可以通过查看 WordPress 核心、主题或插件的源代码找到更多有用的钩子。
其他参数
add_filter() 也可以接受两个额外的参数,$priority (整数) 规定了回调函数执行的优先级,和 $accepted_args (整数) 规定了传递给回调函数的参数数量。有关这些参数的详细说明,请阅读关于 Action 的章节。
示例
在 <body>
满足特定条件时,向标签添加 CSS 类:
function wporg_css_body_class($classes) {
if (!is_admin()) {
$classes[] = 'wporg-is-awesome';
}
return $classes;
}
add_filter('body_class', 'wporg_css_body_class');
自定义钩子
一个非常重要但经常被忽略的做法是在我们可以在插件中使用自定义钩子,以便其他开发者可以扩展或修改我们的插件。自定义钩子的创建和使用方式和 WordPress 核心钩子相同。
创建一个自定义钩子
我们使用 do_action() 为创建 Action 钩子,使用 apply_filters() 创建 Filter 钩子。
挂载回调函数到自定义钩子
我们使用add_action() 添加回调函数到自定义 Action 钩子上,使用 add_filter() 添加回调函数到自定义 Filter 钩子上。
命名冲突
由于任何主题或插件都可以创建自定义钩子,为了避免与钩子名称冲突,我们应该在钩子名称前添加一个自定义前缀,这一点非常重要。例如,名为 email_body 的 Filter 就非常容易产品冲突,因为其他插件开发人员可能发会选择相同 Filter 名称,如果用户同时安装了这两个插件,就可能会导致难以追踪的错误。
给 Filter 名称加一个前缀,如 wporg_email_body(其中 wporg_ 是我们插件的唯一前缀),会避免和其他插件产生 Filter 名称冲突。
示例
可扩展 Action:设置表单
如果我们的插件添加了一个设置表单到 “仪表盘” 中,我们可以使用自定义 Action 来允许其他插件开发者添加他们自己的设置到我们的插件中。
function wporg_settings_page_html()
{
?>
Foo: <input id=foo name=foo type=text>
Bar: <input id=bar name=bar type=text>
<?php
do_action('wporg_after_settings_page_html');
}
如下,另外一个插件开发者挂载了一个回调函数到 wporg_after_settings_page_html Action 上面,来添加新设置。
function myprefix_add_settings()
{
?>
New 1: <input id=new_setting name=new_settings type=text>
<?php
}
add_action('wporg_after_settings_page_html', 'myprefix_add_settings');
可扩展 Filter:自定义文章类型
在下面的例子中,当我们注册自定义文章类型时,把文章类型的参数传递给了一个自定义 Filter,另外一个插件开发者可以在创建自定义文章类型之前修改这个参数。
function wporg_create_post_type()
{
$post_type_params = [/* ... */];
register_post_type(
'post_type_slug',
apply_filters('wporg_post_type_params', $post_type_params)
);
}
现在,另外一个插件开发者可以挂载一个回调函数到 wporg_post_type_params Filter 上面,来修改自定义文章类型的参数。
function myprefix_change_post_type_params($post_type_params)
{
$post_type_params['hierarchical'] = true;
return $post_type_params;
}
add_filter('wporg_post_type_params', 'myprefix_change_post_type_params');
外部资源
- Michael Fields 的 Extendable Extensions
- Josh Harrison 的 WordPress Plugins as Frameworks
- Brandon Dove 的 The Pluggable Plugin
- Will Norris 的 WordPress Plugin Pet Peeves #3: Not Being Extensible
高级主题
删除挂载到 Action 和 Filter 上的回调函数
有时候,我们需要删除一个注册到一个插件、主题甚至是 WordPress 核心的钩子上的回调函数。这时,我们可以使用 remove_action() 删除挂载到 Action 上的回调函数,使用 remove_filter() 删除挂载到 Filter 上的回调函数。传递给 remove_action 和 remove_filter 的参数应该和使用 add_action 和 add_filter 函数注册他们的参数相同。
示例
举个例子,我们需要删除不必要的功能来提高大型主题的性能。让我们来看一下主题代码的 function.php
function my_theme_setup_slider()
{
// ...
}
add_action('template_redirect', 'my_theme_setup_slider', 9);
这个主题的 my_theme_setup_slider 函数添加了一个我们不需要的幻灯模块,这个模块会加载一个非常大的 CSS 文件,然后初始化一个 JavaScript 文件,这个文件使用了 1MB 的自定义库,我们可以通过卸载这个功能来移除这个幻灯模块。
由于我们需要在 my_theme_setup_slider 回调函数注册后 (在 functions.php 中执行)卸载这个函数,执行这个卸载的最好的时机就是after_setup_theme
钩子。
function wporg_disable_slider(){
// make sure all parameters match the add_action() call exactly
remove_action('template_redirect', 'my_theme_setup_slider', 9);
}
// make sure we call remove_action() after add_action() has been called
add_action('after_setup_theme', 'wporg_disable_slider');
删除所有回调
我们可以使用 remove_all_actions() 和 remove_all_filters() 来删除挂载到某个钩子上面的所有回调。
确定当前钩子
有时候,我们需要在多个钩子上挂载同一个回调函数,在回调函数中,我们需要根据它所挂载到的钩子来确定对应的操作。我们可以使用 current_action() / current_filter() 来确定当前的 Action 或 Filter。
function wporg_modify_content($content){
switch (current_filter()) {
case 'the_content':
// do something
break;
case 'the_excerpt':
// do something
break;
}
return $content;
}
add_filter('the_content', 'wporg_modify_content');
add_filter('the_excerpt', 'wporg_modify_content');
检查钩子是否已经运行了
一些钩子在执行过程中可能会被多次调用,但是我们只希望它被调用一次,在这种情况下,我们可以使用 did_action() 来检查钩子运行了多少次。
function wporg_custom(){
if (did_action('save_post') !== 1) {
return;
}
// ...
}
add_action('save_post', 'wporg_custom');
使用 “全部” 钩子进行调试
如果我们想要一个回调函数在每一个钩子上面都被触发,我们可以挂载回调函数到 ‘all’ 钩子上面。在进行调试时,这个技巧非常有用,可以帮助我们确定某个事件在什么时候发生,页面在什么时候崩溃等等。