探究 WordPress `get_option()` 和 `update_option()` 函数的源码:如何处理单值与多值选项。

各位朋友,早上好!今天咱们来聊聊 WordPress 里面一对老朋友:get_option()update_option()。 这俩哥们儿,一个负责取,一个负责存,专门伺候 WordPress 的选项(options)。听起来简单,但里面的门道可不少,尤其是处理单值和多值选项的时候,那叫一个精彩。

咱们今天就扒开它们的源码,看看这俩家伙到底是怎么玩转单值和多值的。保证让大家听得明白,看得清楚,还能乐呵乐呵。

一、Option 是个什么玩意儿?

在 WordPress 的世界里,Option 就是一个键值对。你可以把它想象成一个简单的字典,里面装着各种各样的配置信息。 比如,网站的标题、描述、主题设置、插件配置等等,都可以用 Option 来存储。

WordPress 提供了一张表,叫做 wp_options,专门用来存放这些 Option。 这张表里最关键的几个字段是:

  • option_id: 唯一 ID,自增长。
  • option_name: Option 的名字,也就是键。
  • option_value: Option 的值,可以是字符串、数字、数组、对象,甚至是序列化后的数据。
  • autoload: 是否自动加载。 如果设置为 yes,WordPress 会在初始化的时候自动加载这个 Option,提高网站性能。

二、get_option():取经之路

咱们先来看看 get_option() 这个函数。 它的作用就是根据 Option 的名字,从数据库里把对应的值取出来。

function get_option( $option, $default = false ) {
    global $wpdb;

    /**
     * Fires before retrieving an option.
     *
     * @since 2.9.0
     *
     * @param string $option Name of option to retrieve.
     */
    do_action( 'pre_option_' . $option, $option );

    // Filter the cached option value.
    $cache_value = wp_cache_get( $option, 'options' );

    if ( false !== $cache_value ) {
        /**
         * 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 2.9.0
         *
         * @param mixed  $value  Value of option.
         * @param string $option Name of the option.
         */
        $value = apply_filters( 'option_' . $option, $cache_value, $option );

        return $value;
    }

    // If the option exists as a site option for the current network, bypass the global option.
    if ( is_multisite() ) {
        $site_value = get_site_option( $option, false );

        if ( false !== $site_value ) {
            return $site_value;
        }
    }

    $suppress = $wpdb->suppress_errors();
    $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) );
    $wpdb->suppress_errors( $suppress );

    // Has to unserialize regardless if it is already an array.
    // Sometimes options are serialized twice, so we have to check for that.
    if ( is_object( $row ) ) {
        $value = maybe_unserialize( $row->option_value );
    } else {
        $value = $default;
    }

    wp_cache_add( $option, $value, 'options' );

    /**
     * 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 option.
     * @param string $option Name of the option.
     */
    return apply_filters( 'option_' . $option, $value, $option );
}

咱们来分解一下:

  1. 缓存优先: get_option() 首先会去缓存里找,如果找到了,直接返回。 缓存是提高性能的好帮手,能避免频繁访问数据库。WordPress 用的是 wp_cache_* 系列函数来操作缓存。

  2. 多站点判断: 如果是多站点环境,get_option() 还会尝试从站点选项里取值。 多站点允许每个站点有自己的选项设置。

  3. 数据库查询: 如果缓存里没有,也不是站点选项,那就只能去数据库里查了。 get_row() 函数会执行 SQL 查询,从 wp_options 表里找到 option_name 对应的 option_value

  4. 反序列化: 最关键的一步来了! maybe_unserialize() 函数。 这个函数会判断 option_value 是否是序列化后的数据,如果是,就把它反序列化成 PHP 的数组或者对象。 这就是 get_option() 能够处理多值选项的关键所在!

  5. 默认值: 如果数据库里没有找到对应的 Option,get_option() 会返回你指定的默认值 ($default)。

  6. 过滤器: apply_filters( 'option_' . $option, $value, $option ) 允许开发者通过过滤器来修改 Option 的值。 这是一个强大的扩展机制,可以让你在不修改 WordPress 核心代码的情况下,定制 Option 的行为。

三、update_option():存储之道

接下来,咱们看看 update_option() 函数,它是负责更新 Option 的。

function update_option( $option, $value, $autoload = null ) {
    global $wpdb;

    if ( is_protected_option( $option ) ) {
        /**
         * Fires when a protected option is attempted to be updated.
         *
         * @since 4.7.0
         *
         * @param string $option Name of the protected option.
         * @param mixed  $value  Attempted new value for the protected option.
         */
        do_action( 'update_option_' . $option, $option, $value );
        return false;
    }

    /**
     * Filters a specific option before its value 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 mixed  $old_value The old option value.  It will be the same as
     *                           $value if the option does not exist.
     */
    $value = apply_filters( 'pre_update_option_' . $option, $value, 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 value 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 );

    $autoload = ( null === $autoload ) ? 'yes' : $autoload;

    $autoload = ( 'yes' === $autoload ) ? 'yes' : 'no';

    if ( false === get_option( $option ) ) {
        $suppress = $wpdb->suppress_errors();
        $wpdb->query( $wpdb->prepare( "INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE `option_name` = VALUES(`option_name`), `option_value` = VALUES(`option_value`), `autoload` = VALUES(`autoload`)", $option, maybe_serialize( $value ), $autoload ) );
        $wpdb->suppress_errors( $suppress );
        wp_cache_add( $option, $value, 'options' );
    } else {
        $suppress = $wpdb->suppress_errors();
        $wpdb->query( $wpdb->prepare( "UPDATE `$wpdb->options` SET `option_value` = %s, `autoload` = %s WHERE `option_name` = %s", maybe_serialize( $value ), $autoload, $option ) );
        $wpdb->suppress_errors( $suppress );
        wp_cache_replace( $option, $value, 'options' );
    }

    /**
     * Fires immediately after an option value 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( 'updated_option', $option, $old_value, $value );
    do_action( 'updated_option_' . $option, $option, $old_value, $value );

    return true;
}

咱们也来分解一下:

  1. 保护选项: is_protected_option() 函数会检查要更新的 Option 是否是受保护的。 有些 Option 是 WordPress 核心使用的,不应该被随意修改。

  2. 过滤器: apply_filters( 'pre_update_option_' . $option, $value, get_option( $option ) ) 允许开发者在 Option 更新之前修改它的值。

  3. 值比较: update_option() 会先比较新值和旧值,如果一样,就直接返回,避免不必要的数据库操作。

  4. 序列化: 关键的一步! maybe_serialize() 函数。 这个函数会判断 $value 是否是数组或者对象,如果是,就把它序列化成字符串。 这就是 update_option() 能够存储多值选项的关键所在!

  5. 数据库操作: update_option() 会根据 Option 是否存在,执行 INSERT 或者 UPDATE 语句,把序列化后的值存入数据库。

  6. 缓存更新: wp_cache_replace() 函数会更新缓存,保持数据的一致性。

  7. Action 钩子: do_action() 函数会触发一系列的 Action 钩子,允许开发者在 Option 更新之后执行一些自定义操作。

四、单值与多值:Option 的两种姿势

现在,咱们来聊聊单值和多值 Option 的区别。

  • 单值 Option: Option 的值是一个简单的字符串、数字或者布尔值。 例如,网站的标题、描述、每页显示的文章数等等。

  • 多值 Option: Option 的值是一个数组或者对象。 例如,主题的设置、插件的配置、用户的角色等等。

get_option()update_option() 都能处理这两种类型的 Option。 关键在于 maybe_unserialize()maybe_serialize() 这两个函数。

  • maybe_unserialize()get_option() 中,它负责把从数据库里取出来的序列化字符串反序列化成 PHP 的数组或者对象,让你能够方便地访问多值 Option。

  • maybe_serialize()update_option() 中,它负责把 PHP 的数组或者对象序列化成字符串,然后存入数据库。

五、代码示例:Option 的实战演练

咱们来写几个例子,演示一下如何使用 get_option()update_option() 来处理单值和多值 Option。

1. 单值 Option:存储网站的每页文章数

// 获取每页文章数,如果不存在,默认值为 10
$posts_per_page = get_option( 'my_theme_posts_per_page', 10 );

// 输出每页文章数
echo '每页显示文章数:' . $posts_per_page;

// 更新每页文章数为 20
update_option( 'my_theme_posts_per_page', 20 );

2. 多值 Option:存储主题的颜色设置

// 获取颜色设置,如果不存在,使用默认值
$color_settings = get_option( 'my_theme_color_settings', array(
    'background_color' => '#ffffff',
    'text_color'       => '#000000',
    'link_color'       => '#0000ff',
) );

// 输出颜色设置
echo '背景颜色:' . $color_settings['background_color'] . '<br>';
echo '文本颜色:' . $color_settings['text_color'] . '<br>';
echo '链接颜色:' . $color_settings['link_color'] . '<br>';

// 更新颜色设置
$color_settings['background_color'] = '#f0f0f0';
update_option( 'my_theme_color_settings', $color_settings );

六、Option 的注意事项

在使用 Option 的时候,有一些需要注意的地方:

  • Option Name 的唯一性: Option Name 必须是唯一的,否则会发生冲突。 建议使用前缀,例如 my_theme_ 或者 my_plugin_,避免与其他主题或者插件的 Option Name 冲突。

  • Autoload 的合理使用: autoload 设置为 yes 可以提高网站性能,但是过多的自动加载会导致 WordPress 初始化变慢。 只有那些经常使用的 Option 才应该设置为自动加载。

  • Option 值的类型: 尽量保持 Option 值的类型一致。 例如,如果一个 Option 存储的是数字,就不要把它改成字符串。

  • 数据安全: 对用户输入的数据进行验证和过滤,防止 SQL 注入攻击。

  • 性能优化: 尽量使用缓存,避免频繁访问数据库。

七、总结:Option 的奥秘

今天咱们一起探索了 WordPress get_option()update_option() 函数的源码,了解了它们是如何处理单值和多值 Option 的。 简单来说,maybe_unserialize()maybe_serialize() 这两个函数是关键,它们负责在数据库存储和 PHP 变量之间进行转换。

希望今天的讲解能够帮助大家更好地理解 WordPress 的 Option 机制,写出更高效、更安全的代码。

八、彩蛋:几个常用的 Option

最后,给大家分享几个常用的 Option:

Option Name 描述
siteurl 网站的 URL。
home 网站的首页 URL。
blogname 网站的标题。
blogdescription 网站的描述。
admin_email 管理员的邮箱。
users_can_register 是否允许用户注册。
default_role 默认的用户角色。
timezone_string 时区。
date_format 日期格式。
time_format 时间格式。

好了,今天的讲座就到这里。 感谢大家的聆听! 如果有什么问题,欢迎随时提问。

祝大家编程愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注