各位观众老爷,大家好!今天咱们来聊聊WordPress插件的灵魂——wp-includes/plugin.php
。说它是灵魂,一点也不夸张,它就像个媒婆,牵线搭桥,把各种插件的功能“嫁接”到WordPress的核心系统上。
废话不多说,咱们直接深入源码,看看这媒婆是怎么运作的。
一、plugin.php
的主要职能
plugin.php
文件主要负责以下几大块:
- 插件激活、停用、卸载: 管理插件的生命周期。
- 插件加载: 扫描并加载插件文件。
Hooks
机制(Action & Filter): 这是最重要的部分,它提供了插件与核心交互的接口。
二、插件的加载流程
WordPress启动时,会经历一个插件加载的过程。简单来说,就是找到所有激活的插件,然后把它们包含进来。
// 位于 wp-settings.php,它会调用 plugin.php 中的函数
if ( is_multisite() ) {
add_action( 'muplugins_loaded', 'wp_load_alloptions', 0 );
add_action( 'muplugins_loaded', 'wp_load_ext_plugins', 1 );
} else {
add_action( 'plugins_loaded', 'wp_load_alloptions', 0 );
add_action( 'plugins_loaded', 'wp_load_ext_plugins', 1 );
}
wp_load_ext_plugins()
函数(定义在plugin.php
中)负责加载插件。让我们看看它的简化版:
function wp_load_ext_plugins( $sitewide = false ) {
global $wp_plugin_paths, $plugins;
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugins = get_plugins(); // 获取所有插件的信息
$active_plugins = get_option( 'active_plugins', array() ); // 获取激活的插件列表
// 如果是多站点,还要考虑站点级别的插件
if ( is_multisite() && $sitewide ) {
$active_sitewide_plugins = get_site_option( 'active_sitewide_plugins', array() );
$active_plugins = array_merge( $active_plugins, array_keys( $active_sitewide_plugins ) );
}
// 加载激活的插件
foreach ( $active_plugins as $plugin ) {
if ( ! validate_file( $plugin ) && isset( $plugins[ $plugin ] ) ) {
include_once WP_PLUGIN_DIR . '/' . $plugin;
} else {
do_action( 'activate_plugin_fallback', $plugin );
}
}
do_action( 'after_plugin_loaded' );
}
这个函数做了几件事:
- 获取插件信息:
get_plugins()
函数会扫描插件目录,读取每个插件的头部信息(比如插件名称、版本号等)。它返回一个数组,键是插件的文件名,值是插件的信息。 - 获取激活的插件:
get_option( 'active_plugins' )
获取已经激活的插件列表,这些插件的信息存储在数据库的wp_options
表中。 - 加载插件: 循环激活的插件列表,使用
include_once
将插件文件包含进来。
三、Hooks
机制:Action 和 Filter
Hooks
机制是WordPress插件系统的核心,它允许插件在不修改核心代码的情况下,扩展和修改WordPress的功能。 Hooks
分为两种:
- Action(动作): 允许插件执行某些操作,比如在文章发布后发送邮件。
- Filter(过滤器): 允许插件修改数据,比如修改文章标题。
1. Action
Action 就像一个事件监听器。WordPress在执行到某个关键点时,会触发一个 Action,所有注册到该 Action 的函数都会被执行。
add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 )
: 注册一个 Action。$tag
: Action 的名称(钩子名称)。$function_to_add
: 要执行的函数。$priority
: 优先级,数值越小,优先级越高,越早执行(默认是 10)。$accepted_args
: 传递给函数的参数数量(默认是 1)。
do_action( $tag, ...$arg )
: 触发一个 Action。
// 插件代码:
function my_awesome_function( $post_id ) {
// 在文章发布后,发送邮件通知管理员
$post = get_post( $post_id );
$admin_email = get_option( 'admin_email' );
wp_mail( $admin_email, '新文章发布:' . $post->post_title, '有一篇新文章发布了,快去看看!' );
}
add_action( 'publish_post', 'my_awesome_function', 10, 1 );
// WordPress 核心代码 (简化版):
function wp_publish_post( $post_id ) {
// ... 一些文章发布的代码 ...
do_action( 'publish_post', $post_id ); // 触发 publish_post Action
}
在这个例子中,当文章发布时,WordPress会调用 do_action( 'publish_post', $post_id )
。my_awesome_function
函数注册到了 publish_post
Action,所以它会被执行,发送邮件给管理员。
2. Filter
Filter 允许插件修改数据。WordPress在处理某个数据时,会先经过 Filter,插件可以修改这个数据,然后把修改后的数据返回给WordPress。
add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 )
: 注册一个 Filter。参数含义同add_action
。apply_filters( $tag, $value, ...$arg )
: 应用一个 Filter。$tag
: Filter 的名称(钩子名称)。$value
: 要被过滤的值。...$arg
: 传递给过滤函数的额外参数。
// 插件代码:
function my_awesome_title_filter( $title ) {
// 在文章标题后面加上 " - Awesome!"
return $title . ' - Awesome!';
}
add_filter( 'the_title', 'my_awesome_title_filter' );
// WordPress 核心代码 (简化版):
function get_the_title( $post_id ) {
$title = get_post_field( 'post_title', $post_id );
$title = apply_filters( 'the_title', $title, $post_id ); // 应用 the_title Filter
return $title;
}
在这个例子中,当WordPress获取文章标题时,会调用 apply_filters( 'the_title', $title, $post_id )
。my_awesome_title_filter
函数注册到了 the_title
Filter,所以它会被执行,在标题后面加上 " – Awesome!",然后返回修改后的标题。
四、$wp_filter
全局变量:Hooks
的核心存储
所有的 Action 和 Filter 都存储在一个全局变量 $wp_filter
中。 $wp_filter
是一个多维数组,它的结构如下:
$wp_filter = array(
'hook_name' => array( // 钩子名称 (Action 或 Filter 的名称)
'priority' => array( // 优先级
'unique_id' => array( // 唯一ID (通常是函数名)
'function' => 'callable', // 要执行的函数
'accepted_args' => int, // 接受的参数数量
),
),
),
);
hook_name
: Action 或 Filter 的名称,例如'publish_post'
或'the_title'
。priority
: 优先级,数值越小,优先级越高。相同优先级的函数按照注册顺序执行。unique_id
: 函数的唯一 ID,通常是函数名,但匿名函数会生成一个唯一的哈希值。function
: 要执行的函数,可以是一个函数名,也可以是一个对象的方法。accepted_args
: 函数接受的参数数量。
add_action
和 add_filter
的本质
add_action
和 add_filter
函数的本质就是往 $wp_filter
数组中添加数据。
function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
global $wp_filter, $merged_filters, $wp_current_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
);
unset( $merged_filters[ $tag ] );
return true;
}
这段代码做了几件事:
- 生成唯一 ID:
_wp_filter_build_unique_id()
函数根据$tag
、$function_to_add
和$priority
生成一个唯一的 ID,用于标识这个 Hook。 - 添加到
$wp_filter
: 将函数信息添加到$wp_filter
数组中,按照hook_name
、priority
和unique_id
进行组织。 - 清除缓存: 清除
$merged_filters
数组中与该$tag
相关的缓存,确保下次执行时重新构建过滤链。
do_action
和 apply_filters
的本质
do_action
和 apply_filters
函数的本质就是从 $wp_filter
数组中取出数据,然后执行相应的函数。
function do_action( $tag, ...$arg ) {
global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
$wp_actions[ $tag ] = isset( $wp_actions[ $tag ] ) ? ++$wp_actions[ $tag ] : 1;
if ( empty( $wp_filter[ $tag ] ) ) {
return;
}
$wp_current_filter[] = $tag;
$args = func_get_args();
array_shift( $args ); // Remove the tag
if ( isset( $merged_filters[ $tag ] ) ) {
$the_ = $merged_filters[ $tag ];
} else {
$the_ = wp_unslash( $wp_filter[ $tag ] );
uksort( $the_, 'strnatcmp' ); // Sort by priority
$merged_filters[ $tag ] = $the_;
}
foreach ( $the_ as $priority => $functions ) {
if ( ! empty( $functions ) ) {
foreach ( $functions as $function ) {
call_user_func_array( $function['function'], array_slice( $args, 0, (int) $function['accepted_args'] ) );
}
}
}
array_pop( $wp_current_filter );
}
这段代码做了几件事:
- 检查是否存在 Hook: 检查
$wp_filter
数组中是否存在与$tag
相关的 Hook。如果不存在,直接返回。 - 获取 Hook 列表: 从
$wp_filter
数组中获取与$tag
相关的 Hook 列表,并按照优先级进行排序。 - 执行 Hook: 循环 Hook 列表,使用
call_user_func_array()
函数执行每个 Hook 对应的函数,并传递相应的参数。 - 维护全局状态: 使用
$wp_current_filter
数组跟踪当前正在执行的 Hook,防止循环调用。
apply_filters
的代码逻辑与 do_action
类似,只不过它需要返回经过过滤的值。
五、 插件激活、停用、卸载
plugin.php
还提供了插件激活、停用和卸载的函数。
register_activation_hook( $file, $function )
: 注册插件激活时要执行的函数。register_deactivation_hook( $file, $function )
: 注册插件停用时要执行的函数。register_uninstall_hook( $file, $function )
: 注册插件卸载时要执行的函数。 注意:卸载钩子只能在插件主文件中定义,并且卸载钩子函数必须是全局函数,不能是类的方法。
这些函数会将 $function
注册到 wp_options
表中,当插件激活、停用或卸载时,WordPress会读取这些信息并执行相应的函数。
// 插件代码:
function my_plugin_activate() {
// 创建数据库表
global $wpdb;
$table_name = $wpdb->prefix . 'my_plugin_data';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
time datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
name varchar(255) NOT NULL,
value text NOT NULL,
PRIMARY KEY (id)
) $charset_collate;";
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
dbDelta( $sql );
add_option( 'my_plugin_version', '1.0' );
}
register_activation_hook( __FILE__, 'my_plugin_activate' );
function my_plugin_deactivate() {
// 清理临时数据
delete_option( 'my_plugin_version' );
}
register_deactivation_hook( __FILE__, 'my_plugin_deactivate' );
function my_plugin_uninstall() {
// 删除数据库表
global $wpdb;
$table_name = $wpdb->prefix . 'my_plugin_data';
$sql = "DROP TABLE IF EXISTS $table_name";
$wpdb->query( $sql );
}
register_uninstall_hook( __FILE__, 'my_plugin_uninstall' );
六、总结
wp-includes/plugin.php
文件是WordPress插件系统的基石。它负责插件的加载、激活、停用和卸载,最重要的是,它提供了 Hooks
机制,允许插件与核心系统进行交互,扩展和修改WordPress的功能。
理解 plugin.php
的源码,对于开发高质量的WordPress插件至关重要。掌握了 Hooks
机制,你就掌握了WordPress插件开发的精髓。
希望今天的讲解能帮助你更深入地理解WordPress插件系统。 记住,多多阅读源码,才能真正理解其背后的原理! 祝各位编程愉快!下次再见!