WordPress源码深度解析之:`WordPress`的`post meta`:`add_post_meta()`和`get_post_meta()`的数据库交互过程。

咳咳,各位同学,欢迎来到今天的WordPress源码深度解析小课堂!今天我们要聊的是WordPress里面一个非常重要,但又常常被大家忽略的小可爱 —— post meta。 更具体地说,我们要扒一扒add_post_meta()get_post_meta()这两个函数背后,跟数据库是如何眉来眼去的。准备好了吗? Let’s dive in!

Post Meta 是个啥?

首先,咱们得明白post meta到底是干嘛的。简单来说,它就是给你的文章、页面,甚至是自定义文章类型(custom post type)添加额外信息的一种方式。 想象一下,你写了一篇电影影评,除了标题、正文之外,你还想记录这部电影的评分、导演、主演等等信息。这些信息就可以通过post meta来存储。

与直接修改wp_posts表不同,post meta提供了一种更加灵活、可扩展的方式来存储这些额外信息。它把这些信息存在另一个表里,也就是我们今天要重点关注的wp_postmeta表。

wp_postmeta 表的结构

在我们深入代码之前,先来看看wp_postmeta表长什么样:

列名 数据类型 描述
meta_id bigint(20) unsigned 唯一标识符,自增主键
post_id bigint(20) unsigned 关联的文章/页面的ID,外键,指向wp_posts.ID
meta_key varchar(255) 元数据的键名,比如ratingdirector
meta_value longtext 元数据的值,可以是字符串、数字、甚至是序列化后的数组或对象

add_post_meta():小数据的搬运工

add_post_meta()函数的作用,就是往wp_postmeta表里插入一条新的记录。 它的基本语法是:

add_post_meta( int $post_id, string $meta_key, mixed $meta_value, bool $unique = false ) : int|false

参数解释:

  • $post_id: 文章/页面的ID。
  • $meta_key: 元数据的键名。
  • $meta_value: 元数据的值。
  • $unique: 是否只允许存在一个相同键名的元数据。如果设置为true,并且已经存在相同键名的元数据,则不会插入新的记录。

让我们来看一段简化的add_post_meta()源码(删除了部分错误处理和钩子):

function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) {
    global $wpdb;

    $post_id = (int) $post_id;

    if ( ! is_string( $meta_key ) ) {
        return false;
    }

    $meta_type = 'post'; //Meta type for post

    $meta_key = wp_unslash( $meta_key );
    $meta_value = wp_unslash( $meta_value );

    if ( $unique ) {
        $check = $wpdb->get_var( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s", $post_id, $meta_key ) );

        if ( $check ) {
            return false; // 已经存在相同的键名,不插入
        }
    }

    $meta_value_serialized = maybe_serialize( $meta_value ); // 序列化复杂数据类型

    $result = $wpdb->insert(
        $wpdb->postmeta,
        array(
            'post_id'    => $post_id,
            'meta_key'   => $meta_key,
            'meta_value' => $meta_value_serialized,
        ),
        array(
            '%d',
            '%s',
            '%s',
        )
    );

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

    return false;
}

代码解读:

  1. 参数验证和清理: 函数首先对输入的参数进行类型验证和清理,确保数据的安全性。
  2. unique 检查: 如果设置了$uniquetrue,函数会先查询数据库,看看是否已经存在相同的post_idmeta_key的记录。如果存在,则直接返回false,阻止插入。
  3. 数据序列化: maybe_serialize()函数非常重要。因为meta_value可以存储各种类型的数据,包括数组和对象。为了方便存储到数据库的longtext字段,需要将这些复杂的数据类型序列化成字符串。
  4. 数据库插入: 使用$wpdb->insert()函数向wp_postmeta表插入一条新的记录。 $wpdb是WordPress的数据库操作对象,它提供了一系列方法来执行SQL查询。
    • $wpdb->postmeta: 指定要插入的表名,即wp_postmeta
    • array(...): 指定要插入的字段和对应的值。
    • array(...): 指定字段对应的数据类型,用于防止SQL注入。
  5. 清除缓存: wp_cache_delete()函数用于清除缓存,确保下次获取元数据时,能获取到最新的值。
  6. 返回结果: 如果插入成功,返回新插入的meta_id;否则,返回false

举个例子:

$post_id = 123;
$movie_rating = 8.5;

$result = add_post_meta( $post_id, 'movie_rating', $movie_rating, true );

if ( $result ) {
    echo "成功添加电影评分,meta_id: " . $result;
} else {
    echo "添加电影评分失败";
}

这段代码会在wp_postmeta表中插入一条记录,post_id为123,meta_keymovie_ratingmeta_value为8.5。由于unique设置为true,如果已经存在post_id为123,meta_keymovie_rating的记录,则不会插入新的记录。

get_post_meta():从数据库里捞数据

get_post_meta()函数的作用,就是从wp_postmeta表里根据给定的post_idmeta_key,获取对应的元数据值。 它的基本语法是:

get_post_meta( int $post_id, string $key = '', bool $single = false ) : mixed

参数解释:

  • $post_id: 文章/页面的ID。
  • $key: 元数据的键名。如果为空,则返回所有与post_id相关的元数据。
  • $single: 是否只返回单个值。如果设置为true,则返回第一个匹配的元数据值;否则,返回一个包含所有匹配值的数组。

让我们来看一段简化的get_post_meta()源码(同样删除了部分错误处理和钩子):

function get_post_meta( $post_id, $key = '', $single = false ) {
    global $wpdb, $_wp_post_meta_cache;

    $post_id = (int) $post_id;

    $meta_type = 'post'; //Meta type for post

    if ( isset( $_wp_post_meta_cache[ $post_id ] ) ) {
        $meta_cache = $_wp_post_meta_cache[ $post_id ];
    } else {
        $meta_cache = update_meta_cache( $meta_type, array( $post_id ) );
        $meta_cache = isset( $_wp_post_meta_cache[ $post_id ] ) ? $_wp_post_meta_cache[ $post_id ] : array();
    }

    if ( empty( $key ) ) {
        return $meta_cache;
    }

    if ( isset( $meta_cache[ $key ] ) ) {
        $values = $meta_cache[ $key ];
    } else {
        return $single ? '' : array();
    }

    if ( $single ) {
        return maybe_unserialize( $values[0] ); // 反序列化
    } else {
        foreach ( $values as $index => $value ) {
            $values[ $index ] = maybe_unserialize( $value ); // 反序列化
        }
        return $values;
    }
}

代码解读:

  1. 参数验证: 对输入的参数进行类型验证,确保数据的安全性。
  2. 缓存机制: 这是get_post_meta()性能优化的关键。 WordPress会将已经获取过的post meta数据缓存到全局变量$_wp_post_meta_cache中。 这样,下次获取相同post_id的元数据时,可以直接从缓存中读取,避免重复查询数据库。
    • isset( $_wp_post_meta_cache[ $post_id ] ): 检查缓存中是否已经存在post_id对应的元数据。
    • update_meta_cache(): 如果缓存中没有,则调用update_meta_cache()函数从数据库中获取,并更新缓存。
  3. 获取所有元数据: 如果$key为空,则直接返回缓存中post_id对应的所有元数据。
  4. 获取指定元数据: 如果$key不为空,则从缓存中查找key对应的元数据值。
  5. 数据反序列化: maybe_unserialize()函数与add_post_meta()中的maybe_serialize()相对应。 它会将序列化后的字符串反序列化成原来的数据类型(数组或对象)。
  6. 返回结果: 如果$singletrue,则返回第一个匹配的元数据值;否则,返回一个包含所有匹配值的数组。

update_meta_cache()函数源码简化如下:

function update_meta_cache( $meta_type, $object_ids ) {
    global $wpdb, $_wp_post_meta_cache;

    $object_ids = array_map( 'intval', (array) $object_ids );
    $object_ids = array_filter( $object_ids );

    if ( empty( $object_ids ) ) {
        return;
    }

    switch ( $meta_type ) {
        case 'post':
            $table = $wpdb->postmeta;
            $id_column = 'post_id';
            $cache =& $_wp_post_meta_cache;
            break;
        default:
            return false;
    }

    $id_list = implode( ',', $object_ids );

    $query = "SELECT meta_key, meta_value, $id_column FROM $table WHERE $id_column IN ($id_list) ORDER BY meta_key";

    $meta_list = $wpdb->get_results( $query, ARRAY_A );

    if ( ! empty( $meta_list ) ) {
        foreach ( $meta_list as $metarow ) {
            $object_id = (int) $metarow[ $id_column ];
            $meta_key = $metarow['meta_key'];
            $meta_value = $metarow['meta_value'];

            $cache[ $object_id ][ $meta_key ][] = $meta_value;
        }
    } else {
        return false;
    }

    return $cache;
}

代码解读:

  1. 确定表名和ID列:根据meta_type(这里是’post’),确定要查询的表(wp_postmeta)和ID列(post_id)。
  2. 构建SQL查询:构造一个SQL查询,从wp_postmeta表中获取所有与给定object_ids(文章ID)相关的元数据,并按照meta_key排序。
  3. 执行查询:使用$wpdb->get_results()执行查询,获取结果集。
  4. 填充缓存:遍历结果集,将每个元数据项添加到全局缓存$_wp_post_meta_cache中。

举个例子:

$post_id = 123;

$movie_rating = get_post_meta( $post_id, 'movie_rating', true );

if ( $movie_rating ) {
    echo "电影评分: " . $movie_rating;
} else {
    echo "未找到电影评分";
}

这段代码会从wp_postmeta表中获取post_id为123,meta_keymovie_rating的元数据值。由于$single设置为true,所以只会返回一个值。

update_post_meta():更新数据,旧貌换新颜

除了添加和获取,我们有时候还需要更新post meta。 这时候就要用到update_post_meta()函数。它的基本语法是:

update_post_meta( int $post_id, string $meta_key, mixed $meta_value, mixed $prev_value = '' ) : int|bool

参数解释:

  • $post_id: 文章/页面的ID。
  • $meta_key: 元数据的键名。
  • $meta_value: 元数据的新值。
  • $prev_value: 可选参数,旧值。如果指定了旧值,则只有在数据库中存在与旧值匹配的记录时,才会更新。

update_post_meta()的源码可以简化为:

function update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) {
    global $wpdb;

    $post_id = (int) $post_id;

    if ( ! is_string( $meta_key ) ) {
        return false;
    }

    $meta_type = 'post'; //Meta type for post

    $meta_key = wp_unslash( $meta_key );
    $meta_value  = wp_unslash( $meta_value );

    $meta_value_serialized = maybe_serialize( $meta_value );

    $null = null;

    if ( is_null( $prev_value ) ) {
        $prev_value = $null;
    } else {
        $prev_value = wp_unslash( $prev_value );
        $prev_value = maybe_serialize( $prev_value );
    }

    $data = array( 'meta_value' => $meta_value_serialized );
    $where = array( 'post_id' => $post_id, 'meta_key' => $meta_key );
    $format = array( '%s' );
    $where_format = array( '%d', '%s' );

    if ( '' !== $prev_value ) {
        $where['meta_value'] = $prev_value;
        $where_format[] = '%s';
    }

    $result = $wpdb->update( $wpdb->postmeta, $data, $where, $format, $where_format );

    if ( ! $result ) {
        if ( ! get_post_meta( $post_id, $meta_key, true ) ) {
            return add_post_meta( $post_id, $meta_key, $meta_value );
        } else {
           return false;
        }
    }

    wp_cache_delete( $post_id, $meta_type . '_meta' );

    return $result;
}

代码解读:

  1. 参数验证和清理:类似于add_post_meta(),对输入参数进行类型验证和清理。
  2. 数据序列化:将$meta_value$prev_value序列化。
  3. 构建SQL UPDATE语句:使用$wpdb->update()函数构造SQL UPDATE语句,更新wp_postmeta表中符合条件的记录。
    • $data: 指定要更新的字段和对应的新值(meta_value)。
    • $where: 指定更新的条件(post_idmeta_key)。 如果提供了$prev_value,则还会加上meta_value的条件。
    • $format: 指定$data中字段的数据类型。
    • $where_format: 指定$where中字段的数据类型。
  4. 处理更新失败的情况:如果更新失败,且数据库中不存在该post meta, 则调用add_post_meta()添加一条记录,否则返回false
  5. 清除缓存:更新缓存,确保下次获取元数据时能获取到最新的值。
  6. 返回结果:返回更新的行数。

delete_post_meta():删除数据,干净利落

最后,我们再来看看delete_post_meta()函数,它的作用是从wp_postmeta表中删除指定的元数据。 它的基本语法是:

delete_post_meta( int $post_id, string $meta_key, mixed $meta_value = '' ) : bool

参数解释:

  • $post_id: 文章/页面的ID。
  • $meta_key: 元数据的键名。
  • $meta_value: 可选参数,元数据的值。如果指定了值,则只删除与值匹配的记录。

delete_post_meta()的源码简化如下:

function delete_post_meta( $post_id, $meta_key, $meta_value = '' ) {
    global $wpdb;

    $post_id = (int) $post_id;

    if ( ! is_string( $meta_key ) ) {
        return false;
    }

    $meta_type = 'post'; //Meta type for post

    $meta_key = wp_unslash( $meta_key );
    $meta_value = wp_unslash( $meta_value );

    $null = null;

    if ( is_null( $meta_value ) ) {
        $meta_value = $null;
    } else {
        $meta_value = maybe_serialize( $meta_value );
    }

    $query = "DELETE FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s";
    $args = array( $post_id, $meta_key );

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

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

    wp_cache_delete( $post_id, $meta_type . '_meta' );

    return (bool) $result;
}

代码解读:

  1. 参数验证和清理:对输入参数进行类型验证和清理。
  2. 数据序列化:如果提供了$meta_value,则将其序列化。
  3. 构建SQL DELETE语句:使用$wpdb->query()函数构造SQL DELETE语句,删除wp_postmeta表中符合条件的记录。
    • 如果提供了$meta_value,则SQL语句会包含meta_value的条件。
  4. 清除缓存:更新缓存,确保下次获取元数据时不会获取到已删除的值。
  5. 返回结果:返回true表示删除成功,否则返回false

总结

好了,今天的post meta数据库交互之旅就到这里。 我们一起扒了add_post_meta()get_post_meta()update_post_meta()delete_post_meta()这几个函数的源码, 了解了它们是如何与wp_postmeta表交互,以及WordPress是如何使用缓存来优化性能的。

记住,post meta是一个非常强大的工具,可以让你灵活地扩展WordPress的功能。 掌握了它的原理,你就可以更加自信地使用它,并根据自己的需求进行定制。

下课! 希望大家有所收获,下次再见!

发表回复

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