阐述 `get_post_meta()` 和 `update_post_meta()` 函数的源码,它们如何与 `wp_postmeta` 表进行交互?

各位同学,晚上好! 今天给大家带来一场关于 WordPress 元数据操作的专题讲座,咱们直接进入主题,聊聊 get_post_meta()update_post_meta() 这两位“元数据大佬”的源码实现,以及它们和 wp_postmeta 表之间不得不说的故事。 准备好了吗?Let’s dive in!

一、wp_postmeta 表:元数据的“大本营”

首先,要理解 get_post_meta()update_post_meta(),必须先认识一下 wp_postmeta 这张表。 它是 WordPress 存储文章(或其他任何支持元数据的对象,比如用户、分类等等)附加信息的“大本营”。

wp_postmeta 表的结构(简化版)大致如下:

字段名 数据类型 描述
meta_id BIGINT(20) UNSIGNED 元数据ID,主键,自增长
post_id BIGINT(20) UNSIGNED 关联的文章ID(或其他对象ID)
meta_key VARCHAR(255) 元数据的键名,用于标识不同的元数据项,例如 ‘_my_custom_field’
meta_value LONGTEXT 元数据的值,可以是任何类型的数据,会被序列化存储

举个例子,假设我们有一篇文章的 ID 是 123,我们想给它添加一个自定义字段,键名为 _my_custom_field,值为 Hello World!,那么 wp_postmeta 表里可能会增加一条这样的记录:

meta_id post_id meta_key meta_value
456 123 _my_custom_field Hello World!

二、get_post_meta():元数据“取经人”

get_post_meta() 的作用很简单:根据文章 ID 和元数据键名,从 wp_postmeta 表里取出对应的值。 接下来,我们深入到源码里看看它是怎么实现的。

function get_post_meta( $post_id, $key = '', $single = false ) {
    return get_metadata( 'post', $post_id, $key, $single );
}

看到了吗? get_post_meta() 实际上是调用了更通用的函数 get_metadata()get_metadata() 才是真正的“取经人”。 我们再来看看get_metadata()的源码(简化版,去掉了部分钩子和缓存逻辑):

function get_metadata( $meta_type, $object_id, $meta_key = '', $single = false ) {
    global $wpdb;

    $meta_type = sanitize_key( $meta_type ); // 安全过滤

    if ( ! in_array( $meta_type, array( 'post', 'user', 'comment', 'term' ), true ) ) {
        return false; // 只支持 post, user, comment, term 这几种类型
    }

    $table = _get_meta_table( $meta_type ); // 获取对应的元数据表名,这里是 wp_postmeta

    if ( empty( $table ) ) {
        return false;
    }

    $id_column = sanitize_key( $meta_type . '_id' ); // 获取ID字段名,这里是 post_id

    $sql = "SELECT meta_value FROM {$table} WHERE {$id_column} = %d"; // 基本的 SQL 查询

    if ( ! empty( $meta_key ) ) {
        $meta_key = sanitize_key( $meta_key );
        $sql .= " AND meta_key = %s";
    }

    if ( $single ) {
        $sql .= " LIMIT 1"; // 如果只需要一个值,加上 LIMIT 1
    }

    $args = array( $object_id ); // 准备 SQL 参数

    if ( ! empty( $meta_key ) ) {
        $args[] = $meta_key;
    }

    $q = $wpdb->prepare( $sql, $args ); // 准备 SQL 语句

    if ( $single ) {
        $result = $wpdb->get_var( $q ); // 如果只需要一个值,使用 get_var
    } else {
        $result = $wpdb->get_col( $q ); // 否则,使用 get_col 获取所有值
    }

    if ( ! empty( $result ) ) {
        if ( $single ) {
            $result = maybe_unserialize( $result ); // 反序列化
        } else {
            foreach ( $result as $key => $value ) {
                $result[ $key ] = maybe_unserialize( $value ); // 批量反序列化
            }
        }
    }

    return $result;
}

get_metadata() 流程分析:

  1. 参数校验和安全过滤: get_metadata() 首先会检查 meta_type 是否是允许的类型(post, user, comment, term),并对 meta_key 进行安全过滤,防止 SQL 注入。
  2. 确定表名和 ID 字段: 根据 meta_type 确定对应的元数据表名(这里是 wp_postmeta)和 ID 字段名(这里是 post_id)。
  3. 构建 SQL 查询: 根据传入的参数,构建 SQL 查询语句。 如果指定了 meta_key,会加上 AND meta_key = %s 的条件。 如果 single 为 true,会加上 LIMIT 1,只取一条记录。
  4. 准备 SQL 参数: 将文章 ID 和元数据键名作为 SQL 参数,防止 SQL 注入。
  5. 执行 SQL 查询: 使用 $wpdb->prepare() 准备 SQL 语句,然后使用 $wpdb->get_var()$wpdb->get_col() 执行查询。
  6. 反序列化: 由于 meta_value 字段存储的是序列化后的数据,因此需要使用 maybe_unserialize() 函数进行反序列化,将其还原为原始的数据类型。
  7. 返回结果: 如果 single 为 true,返回单个反序列化后的值。 否则,返回一个包含所有反序列化后的值的数组。

总结一下 get_post_meta() 的工作原理:

  • 它通过 get_metadata() 函数,构建 SQL 查询语句,从 wp_postmeta 表中根据文章 ID 和元数据键名查找对应的值。
  • 它使用了 $wpdb->prepare() 函数来防止 SQL 注入。
  • 它使用 maybe_unserialize() 函数来反序列化 meta_value 字段的值。
  • 它可以根据 single 参数,返回单个值或一个数组。

三、update_post_meta():元数据“更新小能手”

update_post_meta() 的作用是更新或添加文章的元数据。 如果指定的元数据键名已经存在,就更新它的值。 如果不存在,就添加一条新的记录。 我们来看看它的源码:

function update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) {
    return update_metadata( 'post', $post_id, $meta_key, $meta_value, $prev_value );
}

同样, update_post_meta() 也是调用了更通用的函数 update_metadata()update_metadata() 才是真正的“更新小能手”。 我们再来看看update_metadata()的源码(简化版,去掉了部分钩子和缓存逻辑):

function update_metadata( $meta_type, $object_id, $meta_key, $meta_value, $prev_value = '' ) {
    global $wpdb;

    $meta_type = sanitize_key( $meta_type ); // 安全过滤

    if ( ! in_array( $meta_type, array( 'post', 'user', 'comment', 'term' ), true ) ) {
        return false; // 只支持 post, user, comment, term 这几种类型
    }

    $table = _get_meta_table( $meta_type ); // 获取对应的元数据表名,这里是 wp_postmeta

    if ( empty( $table ) ) {
        return false;
    }

    $id_column = sanitize_key( $meta_type . '_id' ); // 获取ID字段名,这里是 post_id

    $meta_key = sanitize_key( $meta_key ); // 安全过滤

    if ( is_protected_meta( $meta_key, $meta_type ) ) {
        return false; // 防止更新受保护的元数据
    }

    $meta_value = maybe_serialize( $meta_value ); // 序列化

    $old_value = get_metadata( $meta_type, $object_id, $meta_key, true ); // 获取旧值

    if ( ! empty( $prev_value ) ) {
        $prev_value = maybe_serialize( $prev_value ); // 序列化 prev_value

        if ( $old_value !== $prev_value ) {
            return false; // 如果旧值和 prev_value 不一致,更新失败
        }
    }

    if ( is_null( $old_value ) ) {
        // 如果不存在,插入新的记录
        $sql = "INSERT INTO {$table} ({$id_column}, meta_key, meta_value) VALUES (%d, %s, %s)";
        $q = $wpdb->prepare( $sql, $object_id, $meta_key, $meta_value );
        $result = $wpdb->query( $q );

        if ( $result ) {
            wp_cache_delete( $object_id, $meta_type . '_meta' ); // 清除缓存
            return $wpdb->insert_id; // 返回新插入的 meta_id
        }

        return false;

    } else {
        // 如果存在,更新记录
        $sql = "UPDATE {$table} SET meta_value = %s WHERE {$id_column} = %d AND meta_key = %s";

        $args = array( $meta_value, $object_id, $meta_key );

        if ( '' !== $prev_value ) {
            $sql .= " AND meta_value = %s";
            $args[] = $prev_value;
        }

        $q = $wpdb->prepare( $sql, $args );
        $result = $wpdb->query( $q );

        if ( $result ) {
            wp_cache_delete( $object_id, $meta_type . '_meta' ); // 清除缓存
            return true; // 更新成功
        }

        return false;
    }
}

update_metadata() 流程分析:

  1. 参数校验和安全过滤:get_metadata() 类似,update_metadata() 也会进行参数校验和安全过滤,防止 SQL 注入和更新受保护的元数据。
  2. 序列化: meta_value 字段存储的是序列化后的数据,因此需要使用 maybe_serialize() 函数进行序列化。
  3. 获取旧值: 使用 get_metadata() 函数获取旧的元数据值。
  4. prev_value 校验: 如果提供了 $prev_value 参数,会先比较当前数据库中的值是否与 $prev_value 一致。 如果不一致,则更新失败,防止并发更新导致数据错误。
  5. 判断是否存在: 判断指定的元数据键名是否已经存在。 如果不存在,则执行 INSERT 操作,插入一条新的记录。 如果存在,则执行 UPDATE 操作,更新已有的记录。
  6. 构建 SQL 查询: 根据是插入还是更新操作,构建不同的 SQL 查询语句。
  7. 准备 SQL 参数: 将文章 ID、元数据键名和序列化后的值作为 SQL 参数,防止 SQL 注入。
  8. 执行 SQL 查询: 使用 $wpdb->prepare() 准备 SQL 语句,然后使用 $wpdb->query() 执行查询。
  9. 清除缓存: 更新成功后,清除缓存,确保下次读取的是最新的数据。
  10. 返回结果: 如果插入成功,返回新插入的 meta_id。 如果更新成功,返回 true。 否则,返回 false。

总结一下 update_post_meta() 的工作原理:

  • 它通过 update_metadata() 函数,判断指定的元数据键名是否存在,然后执行 INSERT 或 UPDATE 操作。
  • 它使用了 $wpdb->prepare() 函数来防止 SQL 注入。
  • 它使用 maybe_serialize() 函数来序列化 meta_value 字段的值。
  • 它使用了 get_metadata() 函数来获取旧的元数据值,并进行校验。
  • 它在更新成功后,会清除缓存。

四、add_post_meta()delete_post_meta()

除了 get_post_meta()update_post_meta() 之外,WordPress 还提供了 add_post_meta()delete_post_meta() 函数,用于添加和删除元数据。 它们实际上也是调用了 add_metadata()delete_metadata() 函数。

  • add_post_meta(): 只负责添加元数据,如果指定的元数据键名已经存在,则会添加多条记录,而不是覆盖旧值。
  • delete_post_meta(): 用于删除元数据。

五、元数据使用注意事项

  • 性能问题: 大量使用元数据可能会导致性能问题,因为每次读取或更新元数据都需要查询数据库。 尽量避免在循环中频繁读取或更新元数据。
  • 安全问题: 要对元数据键名和值进行安全过滤,防止 SQL 注入和 XSS 攻击。 可以使用 WordPress 提供的 sanitize_key()esc_attr() 函数进行过滤。
  • 数据类型: meta_value 字段存储的是序列化后的数据,因此可以存储任何类型的数据。 但是,要确保在读取时进行正确的反序列化。
  • 受保护的元数据:_ 开头的元数据键名通常被认为是受保护的,不应该被直接修改。

六、代码示例

<?php
// 获取文章的自定义字段
$post_id = get_the_ID();
$my_custom_field = get_post_meta( $post_id, '_my_custom_field', true );

// 输出自定义字段的值
echo esc_html( $my_custom_field );

// 更新文章的自定义字段
update_post_meta( $post_id, '_my_custom_field', 'New Value' );

// 添加文章的自定义字段
add_post_meta( $post_id, '_another_custom_field', 'Another Value' );

// 删除文章的自定义字段
delete_post_meta( $post_id, '_my_custom_field' );
?>

七、总结

get_post_meta()update_post_meta() 是 WordPress 中非常重要的函数,用于操作文章(或其他对象)的元数据。 它们通过 get_metadata()update_metadata() 函数,与 wp_postmeta 表进行交互,实现元数据的读取、更新、添加和删除。 理解它们的源码实现,可以帮助我们更好地使用元数据,并避免一些潜在的问题。

好了,今天的讲座就到这里。 大家有什么问题吗?

发表回复

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