深入理解 `get_option()` 和 `update_option()` 函数的源码,它们如何与 `wp_options` 表进行交互?

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;
    }
}

让我们分解一下这个函数:

  1. 缓存检查: 首先,它会检查全局变量 $GLOBALS['wp_load_alloptions'] 是否已加载所有 options。 如果没有,它会调用 wp_load_alloptions() 来加载所有自动加载的 options 并将其存储在全局变量中。 这样可以避免后续对数据库的多次查询。
  2. 全局缓存: 检查全局变量 $alloptions 中是否存在该 option。 如果存在,则直接返回缓存的值。 这是性能优化的关键一步,避免了频繁的数据库查询。
  3. “不存在”缓存: 检查 notoptions 缓存,如果之前查询过这个 option 并且不存在,那么直接返回默认值,避免重复查询。
  4. 过滤器: 使用 pre_option_{$option} 过滤器,允许其他插件或主题在 option 从数据库加载之前修改其值或直接返回自定义值。这提供了极大的灵活性。
  5. 再次缓存检查: 使用 wp_cache_get() 从对象缓存中获取 option 值。 如果存在,则返回缓存的值,并使用 option_{$option} 过滤器允许其他插件或主题修改其值。
  6. 数据库查询: 如果缓存中没有找到,它会使用 $wpdb->get_row() 执行一个 SQL 查询,从 wp_options 表中获取 option_value
  7. 序列化处理: 如果从数据库中获取了值,它会使用 maybe_unserialize() 函数来反序列化数据,如果数据是序列化的。
  8. 缓存存储: 将从数据库中获取的值存储到对象缓存中,以便下次可以更快地获取。
  9. 默认值: 如果数据库中没有找到 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;
}

这段代码看起来有点长,但我们可以把它分解成几个关键步骤:

  1. 参数校验: 首先,它会检查 $option 是否为空,如果为空则直接返回 false
  2. 过滤器: 使用 pre_update_option_{$option} 过滤器,允许其他插件或主题在 option 更新之前修改其值。
  3. 新旧值比较: 使用 get_option() 获取 option 的旧值,并与新值进行比较。 如果新旧值相同,则直接返回 false,避免不必要的数据库操作。
  4. Action (更新前): 触发 update_option_{$option} action,允许其他插件或主题在 option 更新之前执行一些操作。
  5. 序列化处理: 使用 maybe_serialize() 函数来序列化 $value,以便存储到数据库中。
  6. 检查 Option 是否存在: 使用 $wpdb->get_var() 查询 wp_options 表,检查 $option 是否已经存在。
  7. 如果 Option 不存在:
    • 如果 $autoload 参数为 null,则默认为 yes
    • 触发 add_option_{$option} action。
    • 使用 $wpdb->insert() 将新的 option 插入到 wp_options 表中。
    • 将新的 option 添加到对象缓存中。
    • 触发 added_option_{$option} action。
  8. 如果 Option 存在:
    • 使用 $wpdb->update() 更新 wp_options 表中 $option 对应的 option_value
    • 如果 $autoload 参数不为 null,则更新 wp_options 表中 $option 对应的 autoload 字段。
    • 使用 wp_cache_replace() 替换对象缓存中 $option 对应的值。
  9. 清除 alloptions 缓存: 使用 wp_cache_delete() 清除 alloptions 缓存,确保下次加载时可以获取到最新的 option 值。
  10. Action (更新后): 触发 updated_option_{$option} action,允许其他插件或主题在 option 更新之后执行一些操作。
  11. 返回 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 城堡稳固运行,并为用户提供最佳的体验。

好了,今天的讲座就到这里。 希望大家有所收获! 感谢各位代码探险家的参与!

发表回复

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