WordPress Options API 解剖:get_option()
和 update_option()
的故事
嘿,各位代码探险家们,欢迎来到今天的 WordPress Options API 解剖讲座!我是你们的导游,一位经验丰富的 WordPress 老油条。今天,我们要深入 WordPress 的核心,扒开 get_option()
和 update_option()
这两个函数的底裤,看看它们是如何与 wp_options
表眉来眼去的。
准备好了吗?系好安全带,咱们出发!
1. 为什么我们需要 wp_options
表?
想象一下,你的 WordPress 网站就像一个巨大的乐高城堡。每个乐高积木(插件、主题、核心设置)都需要记住自己的位置、颜色、大小等等。如果每次乐高积木要记住这些信息都得重新计算,那城堡早就塌了!
wp_options
表就像是城堡的蓝图,它存储了所有乐高积木(也就是 WordPress 网站的各种设置)的信息。这样,每个积木都可以快速找到自己的位置,城堡才能稳固运行。
具体来说,wp_options
表存储了 WordPress 站点级别的全局配置信息,例如站点标题、描述、主题设置、插件设置等等。它避免了将这些配置信息硬编码到代码中,使得代码更灵活、可维护。
2. get_option()
:从蓝图里找宝贝
get_option()
函数就像一个经验丰富的考古学家,专门从 wp_options
这张蓝图中挖掘宝藏。它根据你提供的 "option_name",在表中查找对应的 "option_value",然后把宝藏(配置信息)带回来给你。
2.1 get_option()
的基本用法
<?php
// 获取站点标题
$site_title = get_option( 'blogname' );
// 获取主题的某个设置(假设主题已经将设置存储到 wp_options 表中)
$theme_setting = get_option( 'my_theme_setting' );
// 如果 option 不存在,返回默认值
$default_setting = get_option( 'non_existent_option', 'default_value' );
echo "站点标题: " . $site_title . "<br>";
echo "主题设置: " . $theme_setting . "<br>";
echo "默认设置: " . $default_setting . "<br>";
?>
2.2 get_option()
源码解析
让我们深入 wp-includes/option.php
文件,看看 get_option()
的真实面目:
function get_option( string $option, mixed $default = false ) {
global $wpdb;
/*
* Load all options, if available.
* Will trigger autoloading for all options in a single database query.
*/
if ( ! isset( $GLOBALS['wp_load_alloptions'] ) ) {
$alloptions = wp_load_alloptions();
} else {
$alloptions = $GLOBALS['wp_load_alloptions'];
}
// If the option exists in the cache, return it.
if ( isset( $alloptions[ $option ] ) ) {
return maybe_unserialize( $alloptions[ $option ] );
}
$notoptions = wp_cache_get( 'notoptions', 'options' );
if ( is_array( $notoptions ) && isset( $notoptions[ $option ] ) ) {
return $default;
}
$option = trim( $option );
if ( empty( $option ) ) {
return false;
}
/**
* Filters the value of an existing option before it is retrieved.
*
* The dynamic portion of the hook name, `$option`, refers to the option name.
*
* @since 4.4.0
*
* @param mixed $value The option value. Default null to bypass `get_option()`
* caching.
*/
$pre = apply_filters( "pre_option_{$option}", null );
if ( null !== $pre ) {
return $pre;
}
// Prevent non-existent options from triggering multiple queries.
wp_cache_add( $option, $default, 'options', DAY_IN_SECONDS );
/**
* Filters the value of an option.
*
* The dynamic portion of the hook name, `$option`, refers to the option name.
*
* @since 1.5.0
*
* @param mixed $value Value of the option. If stored serialized, it will be
* unserialized prior to being returned.
*/
$value = apply_filters( 'option_' . $option, wp_cache_get( $option, 'options' ) );
if ( false !== $value ) {
return maybe_unserialize( $value );
}
$row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) );
// Has to be get_row instead of get_var so null is returned instead of ''
if ( is_object( $row ) ) {
$value = $row->option_value;
wp_cache_add( $option, $value, 'options' );
return maybe_unserialize( $value );
} else { // option does not exist.
if ( ! is_array( $notoptions ) ) {
$notoptions = array();
}
$notoptions[ $option ] = true;
wp_cache_set( 'notoptions', $notoptions, 'options' );
return $default;
}
}
让我们分解一下这个函数:
- 缓存检查: 首先,它会检查全局变量
$GLOBALS['wp_load_alloptions']
是否已加载所有 options。 如果没有,它会调用wp_load_alloptions()
来加载所有自动加载的 options 并将其存储在全局变量中。 这样可以避免后续对数据库的多次查询。 - 全局缓存: 检查全局变量
$alloptions
中是否存在该 option。 如果存在,则直接返回缓存的值。 这是性能优化的关键一步,避免了频繁的数据库查询。 - “不存在”缓存: 检查
notoptions
缓存,如果之前查询过这个 option 并且不存在,那么直接返回默认值,避免重复查询。 - 过滤器: 使用
pre_option_{$option}
过滤器,允许其他插件或主题在 option 从数据库加载之前修改其值或直接返回自定义值。这提供了极大的灵活性。 - 再次缓存检查: 使用
wp_cache_get()
从对象缓存中获取 option 值。 如果存在,则返回缓存的值,并使用option_{$option}
过滤器允许其他插件或主题修改其值。 - 数据库查询: 如果缓存中没有找到,它会使用
$wpdb->get_row()
执行一个 SQL 查询,从wp_options
表中获取option_value
。 - 序列化处理: 如果从数据库中获取了值,它会使用
maybe_unserialize()
函数来反序列化数据,如果数据是序列化的。 - 缓存存储: 将从数据库中获取的值存储到对象缓存中,以便下次可以更快地获取。
- 默认值: 如果数据库中没有找到 option,它会将该 option 添加到
notoptions
缓存中,并返回提供的默认值。
2.3 wp_load_alloptions()
: 一次性加载所有 Options
你可能好奇 wp_load_alloptions()
做了什么? 让我们看看它的源码:
function wp_load_alloptions() {
global $wpdb;
if ( ! wp_installing() || ! is_multisite() ) {
$alloptions = wp_cache_get( 'alloptions', 'options' );
if ( ! $alloptions ) {
$alloptions = array();
$options = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options WHERE autoload = 'yes'" );
foreach ( (array) $options as $option ) {
$alloptions[ $option->option_name ] = $option->option_value;
}
wp_cache_add( 'alloptions', $alloptions, 'options' );
}
} else {
$alloptions = array();
}
$GLOBALS['wp_load_alloptions'] = $alloptions;
return $alloptions;
}
简单来说,这个函数从 wp_options
表中加载所有 autoload
字段设置为 yes
的 options,并将它们存储在缓存和全局变量中。这样,在后续调用 get_option()
时,如果 option 已经被加载到缓存中,就可以直接从缓存中获取,避免了数据库查询。
autoload
字段非常重要! 它决定了 WordPress 是否在每次加载时自动加载该 option。 如果你的 option 需要频繁访问,最好将其 autoload
设置为 yes
。 但也要注意,加载过多的 options 会增加内存消耗,影响性能。
3. update_option()
:在蓝图上修改信息
update_option()
函数就像一个负责任的建筑师,它负责更新 wp_options
这张蓝图,确保城堡的每个积木都能按照最新的指示工作。
3.1 update_option()
的基本用法
<?php
// 更新站点标题
update_option( 'blogname', '我的新站点标题' );
// 更新主题设置
update_option( 'my_theme_setting', array( 'color' => 'red', 'font_size' => '16px' ) );
// 添加一个新的 option
update_option( 'new_option', '这是一个新的 option' );
?>
3.2 update_option()
源码解析
让我们再次深入 wp-includes/option.php
文件,看看 update_option()
的内部运作:
function update_option( string $option, mixed $value, string|bool $autoload = null ): bool {
global $wpdb;
$option = trim( $option );
if ( empty( $option ) ) {
return false;
}
/**
* Filters the value of an option before it is updated.
*
* The dynamic portion of the hook name, `$option`, refers to the option name.
*
* @since 2.6.0
*
* @param mixed $value The new, unserialized option value.
* @param string $option Name of the option.
* @param mixed $old_value The old option value.
*/
$value = apply_filters( "pre_update_option_{$option}", $value, $option, get_option( $option ) );
// If the new and old values are the same, no need to update.
$old_value = get_option( $option );
if ( $value === $old_value ) {
return false;
}
/**
* Fires immediately before an option is updated.
*
* The dynamic portion of the hook name, `$option`, refers to the option name.
*
* @since 2.9.0
*
* @param string $option Name of the option to update.
* @param mixed $old_value The old option value.
* @param mixed $value The new option value.
*/
do_action( "update_option_{$option}", $option, $old_value, $value );
$serialized_value = maybe_serialize( $value );
$exists = $wpdb->get_var( $wpdb->prepare( "SELECT option_name FROM $wpdb->options WHERE option_name = %s", $option ) );
if ( ! $exists ) {
if ( is_null( $autoload ) ) {
$autoload = 'yes';
}
/**
* Fires immediately before an option is added.
*
* @since 2.9.0
*
* @param string $option Name of the option to add.
* @param mixed $value The option value.
*/
do_action( "add_option_{$option}", $option, $value );
$result = $wpdb->insert(
$wpdb->options,
array(
'option_name' => $option,
'option_value' => $serialized_value,
'autoload' => $autoload,
),
array(
'%s',
'%s',
'%s',
)
);
if ( ! $result ) {
return false;
}
wp_cache_add( $option, $value, 'options' );
/**
* Fires immediately after an option is added.
*
* @since 2.9.0
*
* @param string $option Name of the option to add.
* @param mixed $value The option value.
*/
do_action( "added_option_{$option}", $option, $value );
} else {
$result = $wpdb->update(
$wpdb->options,
array( 'option_value' => $serialized_value ),
array( 'option_name' => $option ),
array( '%s' ),
array( '%s' )
);
if ( ! $result ) {
return false;
}
if ( $autoload !== null ) {
$autoload = ( 'no' === $autoload ) ? 'no' : 'yes';
$wpdb->update(
$wpdb->options,
array( 'autoload' => $autoload ),
array( 'option_name' => $option ),
array( '%s' ),
array( '%s' )
);
}
wp_cache_replace( $option, $value, 'options' );
}
wp_cache_delete( 'alloptions', 'options' );
/**
* Fires immediately after an option is updated.
*
* The dynamic portion of the hook name, `$option`, refers to the option name.
*
* @since 2.0.0
*
* @param string $option Name of the option to update.
* @param mixed $old_value The old option value.
* @param mixed $value The new option value.
*/
do_action( "updated_option_{$option}", $option, $old_value, $value );
return true;
}
这段代码看起来有点长,但我们可以把它分解成几个关键步骤:
- 参数校验: 首先,它会检查
$option
是否为空,如果为空则直接返回false
。 - 过滤器: 使用
pre_update_option_{$option}
过滤器,允许其他插件或主题在 option 更新之前修改其值。 - 新旧值比较: 使用
get_option()
获取 option 的旧值,并与新值进行比较。 如果新旧值相同,则直接返回false
,避免不必要的数据库操作。 - Action (更新前): 触发
update_option_{$option}
action,允许其他插件或主题在 option 更新之前执行一些操作。 - 序列化处理: 使用
maybe_serialize()
函数来序列化$value
,以便存储到数据库中。 - 检查 Option 是否存在: 使用
$wpdb->get_var()
查询wp_options
表,检查$option
是否已经存在。 - 如果 Option 不存在:
- 如果
$autoload
参数为null
,则默认为yes
。 - 触发
add_option_{$option}
action。 - 使用
$wpdb->insert()
将新的 option 插入到wp_options
表中。 - 将新的 option 添加到对象缓存中。
- 触发
added_option_{$option}
action。
- 如果
- 如果 Option 存在:
- 使用
$wpdb->update()
更新wp_options
表中$option
对应的option_value
。 - 如果
$autoload
参数不为null
,则更新wp_options
表中$option
对应的autoload
字段。 - 使用
wp_cache_replace()
替换对象缓存中$option
对应的值。
- 使用
- 清除
alloptions
缓存: 使用wp_cache_delete()
清除alloptions
缓存,确保下次加载时可以获取到最新的 option 值。 - Action (更新后): 触发
updated_option_{$option}
action,允许其他插件或主题在 option 更新之后执行一些操作。 - 返回
true
: 表示更新成功。
3.3 autoload
参数:控制性能的关键
update_option()
函数还有一个重要的参数:$autoload
。 这个参数决定了 WordPress 是否在每次加载时自动加载该 option。
'yes'
:表示自动加载。 适用于经常需要访问的 option。'no'
:表示不自动加载。 适用于不经常访问的 option。null
:如果 option 不存在,则默认为'yes'
。 如果 option 已经存在,则不修改autoload
字段。
明智地使用 autoload
参数可以显著提高 WordPress 网站的性能。
4. wp_options
表的结构
为了更好地理解 get_option()
和 update_option()
,我们需要了解 wp_options
表的结构:
字段名 | 数据类型 | 说明 |
---|---|---|
option_id |
bigint(20) unsigned |
主键,自增。 |
option_name |
varchar(191) |
Option 的名称。 这是 get_option() 和 update_option() 函数使用的关键标识符。 |
option_value |
longtext |
Option 的值。 可以存储任何类型的数据,包括字符串、数字、数组、对象等。 WordPress 会使用 maybe_serialize() 和 maybe_unserialize() 函数来序列化和反序列化数据。 |
autoload |
varchar(20) |
指定 WordPress 是否自动加载该 option。 可以是 'yes' 或 'no' 。 autoload 设置为 'yes' 的 options 会在 WordPress 启动时自动加载到内存中,以便快速访问。 但加载过多的 options 会影响性能,因此需要谨慎使用。 |
5. 最佳实践和注意事项
- 谨慎使用
autoload
: 不要将不经常访问的 option 设置为autoload = 'yes'
。 这会浪费内存并降低性能。 - 使用缓存: WordPress 已经内置了对象缓存,
get_option()
和update_option()
函数会自动使用缓存。 如果你的网站使用了持久化对象缓存(例如 Memcached 或 Redis),性能会更好。 - 避免频繁更新: 频繁更新 option 会导致频繁的数据库写入操作,影响性能。 尽量减少不必要的更新。
- 使用过滤器和 Action:
get_option()
和update_option()
函数提供了丰富的过滤器和 Action,可以让你在 option 获取和更新前后执行自定义操作。 这提供了极大的灵活性。 - 注意数据类型:
option_value
字段可以存储任何类型的数据,但你需要确保在读取和写入数据时使用正确的数据类型。 - 插件和主题冲突: 不同的插件和主题可能会使用相同的 option name,导致冲突。 为了避免冲突,建议在 option name 中使用插件或主题的唯一标识符作为前缀。
- 数据迁移: 如果你的插件或主题需要迁移数据,可以使用
update_option()
函数来更新 option 值。
6. 总结
get_option()
和 update_option()
函数是 WordPress Options API 的核心。 它们提供了一种简单而强大的方式来存储和管理 WordPress 站点的全局配置信息。
通过深入理解这两个函数的源码,我们可以更好地理解 WordPress 的内部运作机制,并编写更高效、更健壮的插件和主题。
记住,wp_options
表就像一个精密的蓝图,需要我们小心维护。 明智地使用 get_option()
和 update_option()
函数,可以确保我们的 WordPress 城堡稳固运行,并为用户提供最佳的体验。
好了,今天的讲座就到这里。 希望大家有所收获! 感谢各位代码探险家的参与!