各位未来的WordPress大师们,晚上好!我是你们今晚的向导,代号“Option侦探”,很高兴能和大家一起破解WordPress选项系统的密码。今天,我们要聚焦两个关键函数:get_option()
和 update_option()
,看看它们是如何巧妙地处理单值和多值选项的。
准备好了吗?让我们开始这场代码探险!
第一幕:get_option()
—— 选项侦查员
get_option()
,顾名思义,负责从WordPress的选项数据库中检索数据。 它的核心任务是将存储的选项值取出来,然后还给我们,整个过程涉及到缓存读取和数据库查询。
首先,我们来看看get_option()
的简化版源码(为了便于理解,我删除了部分不常用的参数和过滤器,只保留核心逻辑):
function get_option( $option, $default = false ) {
global $wpdb, $wp_load_alloptions;
// 1. 检查是否已经加载所有选项到缓存
if ( ! isset( $wp_load_alloptions ) ) {
wp_load_alloptions();
}
// 2. 检查缓存
if ( isset( $wp_load_alloptions[ $option ] ) ) {
return maybe_unserialize( $wp_load_alloptions[ $option ] );
}
// 3. 检查选项是否已加载到运行时缓存
if ( isset( $GLOBALS['wp_options'][ $option ] ) ) {
return $GLOBALS['wp_options'][ $option ];
}
// 4. 从数据库查询
$row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) );
// 5. 处理结果
if ( is_object( $row ) ) {
$value = $row->option_value;
} else {
$value = $default; // 如果没找到,返回默认值
}
// 6. 反序列化
$value = maybe_unserialize( $value );
// 7. 添加到运行时缓存
$GLOBALS['wp_options'][ $option ] = $value;
return $value;
}
让我们逐行解读这段代码,就像侦探分析线索一样:
-
检查是否已加载所有选项:
wp_load_alloptions()
会尝试将wp_options
表中的所有选项加载到内存,以提高性能。 如果wp_load_alloptions
未设置,则调用该函数。 这是一种缓存策略,避免频繁查询数据库。 -
检查缓存: WordPress 使用全局变量
$wp_load_alloptions
和$GLOBALS['wp_options']
作为缓存。 首先,在$wp_load_alloptions
中查找,如果找到,直接返回反序列化后的值。 -
检查运行时缓存: 如果
$wp_load_alloptions
没有找到,检查$GLOBALS['wp_options']
。 这是个运行时缓存,存储着当前请求周期内已经获取过的选项值。 -
从数据库查询: 如果缓存中没有找到,就只能硬着头皮去数据库里捞了。 使用
$wpdb->prepare()
来防止 SQL 注入,查询wp_options
表中option_name
等于$option
的记录。 -
处理结果: 如果查询成功,将
option_value
赋值给$value
;否则,使用传入的$default
值。 -
反序列化: 关键的一步!
maybe_unserialize()
函数会判断$value
是否需要反序列化。 如果$value
是序列化后的字符串,maybe_unserialize()
会将其转换回 PHP 的数组或对象。 这正是 WordPress 能够存储复杂数据结构的关键。 -
添加到运行时缓存: 将查询到的值添加到
$GLOBALS['wp_options']
,以便下次快速访问。
单值与多值的秘密
get_option()
本身并不区分单值和多值。它只是从数据库中取出 option_value
字段的值,然后通过 maybe_unserialize()
尝试将其反序列化。
-
单值选项: 如果
option_value
存储的是一个简单的字符串、数字或布尔值,maybe_unserialize()
不会做任何处理,直接返回原始值。 -
多值选项: 如果
option_value
存储的是一个序列化后的数组或对象,maybe_unserialize()
会将其反序列化为对应的 PHP 数据结构。 这意味着你可以用一个选项来存储整个配置数组,比如某个插件的所有设置。
例子:
// 存储一个简单的字符串(单值)
update_option( 'my_string_option', 'Hello, World!' );
$string_value = get_option( 'my_string_option' ); // $string_value = 'Hello, World!'
// 存储一个数组(多值)
$my_array = array(
'key1' => 'value1',
'key2' => 'value2',
);
update_option( 'my_array_option', $my_array );
$array_value = get_option( 'my_array_option' ); // $array_value = array( 'key1' => 'value1', 'key2' => 'value2' )
第二幕:update_option()
—— 选项数据管理员
update_option()
负责更新或添加 wp_options
表中的选项。 它的核心任务是将数据序列化后,存入数据库。
同样,我们先看一个简化版的源码:
function update_option( $option, $value, $autoload = null ) {
global $wpdb;
// 1. 验证选项名
if ( ! is_string( $option ) ) {
return false;
}
// 2. 过滤新值
$value = apply_filters( 'pre_update_option_' . $option, $value, $option );
$old_value = get_option( $option );
// 3. 过滤新值和旧值
if ( $value === $old_value ) {
return false; // 如果新值和旧值相同,则不更新
}
$value = apply_filters( 'option_' . $option, $value, $option, $old_value );
// 4. 序列化
$value = maybe_serialize( $value );
// 5. 更新或插入数据库
$now = time();
$row = $wpdb->get_row( $wpdb->prepare( "SELECT option_id FROM $wpdb->options WHERE option_name = %s", $option ) );
if ( is_object( $row ) ) {
// 更新
$option_id = $row->option_id;
$result = $wpdb->update(
$wpdb->options,
array( 'option_value' => $value ),
array( 'option_name' => $option )
);
if ( $result ) {
do_action( 'updated_option', $option, $old_value, $value );
}
} else {
// 插入
$result = $wpdb->insert(
$wpdb->options,
array(
'option_name' => $option,
'option_value' => $value,
'autoload' => ( 'no' === $autoload ) ? 'no' : 'yes',
)
);
if ( $result ) {
do_action( 'added_option', $option, $value );
}
}
// 6. 清除缓存
wp_cache_delete( 'alloptions', 'options' ); // 清除所有选项的缓存
wp_cache_delete( $option, 'options' ); // 清除单个选项的缓存
return true;
}
让我们分解一下这个函数:
-
验证选项名: 确保选项名是字符串,否则直接返回
false
。 -
过滤新值: 通过
apply_filters()
应用一系列过滤器,允许插件和主题修改要保存的值。pre_update_option_{$option}
在真正更新数据库之前过滤,option_{$option}
允许你访问新旧值,并做更精细的控制。 -
过滤新值和旧值: 如果新值和旧值相同,为了避免不必要的数据库操作,直接返回
false
。 -
序列化: 关键的一步!
maybe_serialize()
函数会判断$value
是否需要序列化。 如果$value
是一个数组或对象,maybe_serialize()
会将其转换为序列化后的字符串。 这确保了复杂的数据结构可以安全地存储到数据库中。 -
更新或插入数据库: 先查询数据库中是否存在该选项。 如果存在,则更新
option_value
字段;如果不存在,则插入一条新记录。autoload
参数决定了该选项是否在 WordPress 启动时自动加载到内存中。 -
清除缓存: 更新或插入完成后,需要清除缓存,以确保下次调用
get_option()
时能获取到最新的值。wp_cache_delete()
函数用于清除缓存。
单值与多值的存储
update_option()
的关键在于 maybe_serialize()
函数。
-
单值选项: 如果
$value
是一个简单的字符串、数字或布尔值,maybe_serialize()
不会做任何处理,直接将原始值存储到数据库中。 -
多值选项: 如果
$value
是一个数组或对象,maybe_serialize()
会将其序列化为一个字符串,然后再存储到数据库中。
例子:
// 存储一个简单的字符串(单值)
update_option( 'my_string_option', 'Hello, World!' ); // 数据库中 'option_value' 的值为 'Hello, World!'
// 存储一个数组(多值)
$my_array = array(
'key1' => 'value1',
'key2' => 'value2',
);
update_option( 'my_array_option', $my_array ); // 数据库中 'option_value' 的值为 'a:2:{s:4:"key1";s:6:"value1";s:4:"key2";s:6:"value2";}' (序列化后的字符串)
第三幕:maybe_serialize()
和 maybe_unserialize()
的幕后工作
这两个函数是处理单值和多值选项的关键。 它们负责在 PHP 数据结构和序列化后的字符串之间进行转换。
function maybe_serialize( $data ) {
if ( is_array( $data ) || is_object( $data ) ) {
return serialize( $data );
}
// Double serialization is required for backward compatibility.
if ( is_serialized( $data ) ) {
return serialize( $data );
}
return $data;
}
function maybe_unserialize( $original ) {
if ( is_serialized( $original ) ) { // don't attempt to unserialize data that wasn't serialized going in
return @unserialize( $original );
}
return $original;
}
function is_serialized( $data, $strict = true ) {
// If it isn't a string, it isn't serialized.
if ( ! is_string( $data ) ) {
return false;
}
$data = trim( $data );
if ( 'N;' == $data ) {
return true;
}
if ( strlen( $data ) < 4 ) {
return false;
}
if ( ':' !== $data[1] ) {
return false;
}
if ( $strict ) {
$result = preg_match( '/^((s|i|b|d):[0-9]+:|a:[0-9]+:{)/', $data );
} else {
$result = preg_match( '/^(s|i|b|d|a):[0-9]+:/', $data );
}
if ( $result ) {
return true;
}
return false;
}
-
maybe_serialize()
: 如果输入的数据是数组或对象,则使用serialize()
函数将其序列化。 如果已经是序列化的字符串,则再次序列化(为了兼容性)。 否则,直接返回原始数据。 -
maybe_unserialize()
: 使用is_serialized()
函数判断输入的数据是否是序列化后的字符串。 如果是,则使用unserialize()
函数将其反序列化。 否则,直接返回原始数据。 -
is_serialized()
: 用于检测一个字符串是否是序列化后的数据。它通过检查字符串的格式来判断。
表格总结
为了更清晰地理解,我们用一个表格来总结 get_option()
和 update_option()
如何处理单值和多值选项:
函数 | 输入数据类型 | maybe_serialize() 行为 |
存储到数据库中的值 | maybe_unserialize() 行为 |
返回的数据类型 |
---|---|---|---|---|---|
update_option() |
字符串、数字、布尔值 | 不处理 | 原始值 | N/A | N/A |
update_option() |
数组、对象 | 序列化 | 序列化后的字符串 | N/A | N/A |
get_option() |
字符串、数字、布尔值(从数据库读取) | 不处理 | 原始值 | 不处理 | 字符串、数字、布尔值 |
get_option() |
序列化后的字符串(从数据库读取) | N/A | 序列化后的字符串 | 反序列化 | 数组、对象 |
最佳实践与注意事项
-
谨慎使用
autoload
:autoload
参数决定了选项是否在 WordPress 启动时自动加载。 如果一个选项不经常使用,不要将其设置为autoload
,以减少内存消耗。 -
使用选项 Transients API 来缓存复杂数据: 对于计算量大的数据,或者需要频繁更新的数据,使用 Transients API 可以更有效地缓存数据,并设置过期时间。
-
避免存储过大的数据:
wp_options
表不适合存储大量数据。 如果需要存储大量数据,可以考虑创建自定义表。 -
注意数据类型: 在存储数据时,要明确数据的类型,以便在获取数据时进行正确的处理。
-
防止SQL注入: 始终使用
$wpdb->prepare()
来构建查询,这可以帮助你防止 SQL 注入攻击。 -
善用过滤器: WordPress 提供了一系列的过滤器,允许你在选项被保存或检索之前对其进行修改。 这可以帮助你实现各种自定义功能,例如自动转换数据类型或添加安全检查。
总结陈词
get_option()
和 update_option()
是 WordPress 选项系统的核心函数。 它们通过 maybe_serialize()
和 maybe_unserialize()
函数,巧妙地处理了单值和多值选项的存储和检索。 理解这些函数的原理,可以帮助我们更好地使用 WordPress 的选项系统,开发出更高效、更灵活的插件和主题。
希望今天的讲座对大家有所帮助。 如果有任何问题,欢迎提问!
祝大家在 WordPress 的世界里玩得开心!