剖析 `wp_insert_post()` 函数的源码,它是如何处理 `post_meta` 数据并触发相应的 `action` 的?

大家好,各位码农朋友们,欢迎来到今天的“WordPress源码解密”讲座。今天我们要聊的是WordPress的核心函数之一,wp_insert_post(),重点剖析它是如何处理post_meta数据并触发相关action的。准备好了吗?咱们开车啦!

一、wp_insert_post():总指挥的角色

wp_insert_post()函数,顾名思义,负责在WordPress中插入或者更新一篇post(文章、页面、自定义文章类型等)。别看它名字简单,实际上它是个功能强大的总指挥,协调着各种小弟(函数、钩子)完成任务。

它的基本流程是这样的:

  1. 数据校验和预处理: 检查传入的数据是否合法,并进行一些必要的转换。
  2. 数据库操作: 根据传入的数据,插入或更新wp_posts表中的记录。
  3. 处理post_meta 这是我们今天的主角,负责添加、更新或删除文章的自定义字段。
  4. 触发action 在不同的阶段触发各种action钩子,允许其他插件或主题介入整个过程。

二、post_meta:文章的“私房钱”

post_meta,也就是文章的自定义字段,可以理解为文章的“私房钱”,用来存储一些文章本身不包含,但又非常重要的信息。比如:

  • 电影的评分
  • 书籍的ISBN
  • 产品的价格
  • 等等…

post_meta存储在wp_postmeta表中,主要有四个字段:

字段名 类型 描述
meta_id BIGINT 自增主键
post_id BIGINT 关联的文章ID
meta_key VARCHAR 自定义字段的键名(key)
meta_value LONGTEXT 自定义字段的值(value),可以序列化

三、wp_insert_post()中的post_meta处理:源码深度游

好了,现在让我们深入wp_insert_post()的源码,看看它是如何处理post_meta的。 为了方便阅读,我们只截取关键部分的代码,并添加注释。

function wp_insert_post( $postarr = array(), $wp_error = false ) {
    // ... 前面的数据校验和处理 ...

    $post_ID = (int) $postarr['ID'];

    // ... 数据库操作,插入或更新wp_posts表 ...

    // 处理post_meta
    if ( isset( $postarr['meta_input'] ) && is_array( $postarr['meta_input'] ) ) {
        $meta_input = $postarr['meta_input'];
        foreach ( $meta_input as $key => $value ) {
            update_post_meta( $post_ID, $key, $value );
        }
    }

    // ... 后面的操作,触发action等 ...

    return $post_ID;
}

这段代码告诉我们,wp_insert_post()首先检查$postarr数组中是否存在meta_input键,并且它的值是一个数组。如果存在,它会遍历这个数组,然后调用update_post_meta()函数来更新每一个post_meta

四、update_post_meta()post_meta的“管家”

update_post_meta()函数是处理post_meta的核心函数,它的职责是:

  1. 检查meta_key是否合法。
  2. 检查post_meta是否存在,如果存在则更新,如果不存在则添加。
  3. 在更新或添加前后触发相应的action

让我们看看update_post_meta()的源码:

function update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) {
    // 1. 数据校验
    $post_id = absint( $post_id );
    if ( ! is_string( $meta_key ) ) {
        return false;
    }

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

    $meta_key = apply_filters( 'sanitize_key', $meta_key, $meta_key );

    if ( ! $post_id || ! $meta_key ) {
        return false;
    }

    // 2. 权限检查 (省略,实际代码中有权限检查)

    // 3. 获取现有的meta值
    $cur_value = get_post_meta( $post_id, $meta_key, true );

    // 4. 如果新的meta_value和旧的meta_value相同,直接返回,不做任何操作
    if ( $meta_value === $cur_value ) {
        return false;
    }

    // 5. 如果没有传递$prev_value,则更新所有匹配$meta_key的meta值
    if ( '' === $prev_value ) {
        return update_metadata( 'post', $post_id, $meta_key, $meta_value );
    }

    // 6. 如果传递了$prev_value,则只更新匹配$prev_value的meta值
    $prev_value = wp_unslash( $prev_value );
    return update_metadata( 'post', $post_id, $meta_key, $meta_value, $prev_value );
}

update_post_meta()函数首先进行了一些数据校验,然后通过get_post_meta()函数获取现有的meta_value,如果新的meta_value和旧的meta_value相同,则直接返回,不做任何操作。

核心逻辑是通过update_metadata()来实现的。

五、update_metadata():真正的执行者

update_metadata()是一个通用的元数据更新函数,可以用于更新post_metauser_metaterm_meta等。让我们看看它的源码(同样只截取关键部分):

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

    // 1. 数据校验
    if ( ! $meta_type || ! $object_id || ! is_string( $meta_key ) ) {
        return false;
    }

    $table = _get_meta_table( $meta_type );

    if ( ! $table ) {
        return false;
    }

    $column = sanitize_key( $meta_type . '_id' );

    $object_id = absint( $object_id );

    $meta_key = trim( $meta_key );
    if ( empty( $meta_key ) ) {
        return false;
    }

    $meta_value = maybe_serialize( $meta_value );

    $data  = compact( 'meta_key', 'meta_value' );
    $where = array( $column => $object_id, 'meta_key' => $meta_key );

    // 2. 如果传递了$prev_value,则只更新匹配$prev_value的meta值
    if ( '' !== $prev_value ) {
        $prev_value = maybe_serialize( $prev_value );
        $where['meta_value'] = $prev_value;
        $check = $wpdb->get_row( $wpdb->prepare( "SELECT meta_id FROM {$table} WHERE {$column} = %d AND meta_key = %s AND meta_value = %s", $object_id, $meta_key, $prev_value ) );
        if ( ! $check ) {
            return false;
        }

        $result = $wpdb->update( $table, $data, $where );
    } else { // 3. 如果没有传递$prev_value,则更新所有匹配$meta_key的meta值
        if ( get_metadata( $meta_type, $object_id, $meta_key, true ) ) {
            $result = $wpdb->update( $table, $data, $where );
        } else { //如果不存在,新增
            $result = add_metadata( $meta_type, $object_id, $meta_key, $meta_value );
        }

    }

    if ( $result ) {
        wp_cache_delete( $object_id, $meta_type . '_meta' );

        do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $meta_value );
        do_action( 'update_metadata', $meta_id, $object_id, $meta_key, $meta_value );

        return true;
    }

    return false;
}

update_metadata()函数首先进行数据校验,然后根据是否传递了$prev_value来决定更新策略。

  • 如果传递了$prev_value 只更新meta_value等于$prev_valuepost_meta
  • 如果没有传递$prev_value 更新所有meta_key相同的post_meta。如果不存在,则新增。

最后,update_metadata()会触发updated_{$meta_type}_metaupdate_metadata这两个action钩子,让其他插件或主题有机会介入post_meta的更新过程。

六、add_post_meta()post_meta的“新家”

如果update_metadata判断post_meta不存在,则会调用add_metadata()来添加新的post_meta。让我们看看add_metadata()的源码:

function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = false ) {
    global $wpdb;

    // 1. 数据校验
    if ( ! $meta_type || ! $object_id || ! is_string( $meta_key ) ) {
        return false;
    }

    $table = _get_meta_table( $meta_type );

    if ( ! $table ) {
        return false;
    }

    $column = sanitize_key( $meta_type . '_id' );

    $object_id = absint( $object_id );

    $meta_key = trim( $meta_key );
    if ( empty( $meta_key ) ) {
        return false;
    }

    // 2. 检查是否unique
    if ( $unique && get_metadata( $meta_type, $object_id, $meta_key, true ) ) {
        return false;
    }

    $meta_value = maybe_serialize( $meta_value );

    $data = array(
        $column     => $object_id,
        'meta_key'   => $meta_key,
        'meta_value' => $meta_value,
    );

    $format = array(
        '%d',
        '%s',
        '%s',
    );

    // 3. 插入数据
    $wpdb->insert( $table, $data, $format );

    $id = $wpdb->insert_id;

    if ( ! $id ) {
        return false;
    }

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

    do_action( "added_{$meta_type}_meta", $id, $object_id, $meta_key, $meta_value );
    do_action( 'add_metadata', $id, $object_id, $meta_key, $meta_value );

    return $id;
}

add_metadata()函数首先进行数据校验,然后检查是否设置了unique参数,如果设置了,并且已经存在相同的meta_key,则直接返回。

最后,add_metadata()会将数据插入到数据库中,并触发added_{$meta_type}_metaadd_metadata这两个action钩子,给其他插件或主题机会介入post_meta的添加过程。

七、delete_post_meta()post_meta的“搬家公司”

除了添加和更新,wp_insert_post还可能需要删除post_meta。虽然wp_insert_post本身不直接调用delete_post_meta(), 但是理解它对于整体的post_meta管理是很重要的。delete_post_meta()函数的作用就是删除指定的post_meta。 让我们看看它的源码:

function delete_post_meta( $post_id, $meta_key, $meta_value = '' ) {
    return delete_metadata( 'post', $post_id, $meta_key, $meta_value );
}

它仅仅是调用了delete_metadata函数。我们再看一下delete_metadata的源码:

function delete_metadata( $meta_type, $object_id, $meta_key, $meta_value = '', $delete_all = false ) {
    global $wpdb;

    // 1. 数据校验
    if ( ! $meta_type || ! $object_id || ! is_string( $meta_key ) ) {
        return false;
    }

    $table = _get_meta_table( $meta_type );

    if ( ! $table ) {
        return false;
    }

    $column = sanitize_key( $meta_type . '_id' );

    $object_id = absint( $object_id );

    $meta_key = trim( $meta_key );
    if ( empty( $meta_key ) ) {
        return false;
    }

    // 2. 构建SQL语句
    $sql = "DELETE FROM {$table} WHERE {$column} = %d AND meta_key = %s";
    $args = array( $object_id, $meta_key );

    // 3. 是否全部删除
    if ( '' !== $meta_value ) {
        $meta_value = maybe_serialize( $meta_value );
        $sql .= ' AND meta_value = %s';
        $args[] = $meta_value;
    }

    // 4. 是否删除所有
    if ( $delete_all ) {
        $sql = "DELETE FROM {$table} WHERE {$column} = %d";
        $args = array( $object_id );
    }

    // 5. 查询符合条件的所有meta_id
    $ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM {$table} WHERE {$column} = %d AND meta_key = %s", $object_id, $meta_key ) );

    if ( ! $ids ) {
        return false;
    }

    // 6. 执行删除操作
    $result = $wpdb->query( $wpdb->prepare( $sql, $args ) );

    if ( ! $result ) {
        return false;
    }

    // 7. 清除缓存
    wp_cache_delete( $object_id, $meta_type . '_meta' );

    foreach ( $ids as $meta_id ) {
        do_action( "deleted_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $meta_value );
        do_action( 'delete_metadata', $meta_id, $object_id, $meta_key, $meta_value );
    }

    return true;
}

delete_metadata()函数首先进行数据校验,然后根据是否传递了$meta_value来决定删除策略。 如果传递了,则只删除meta_value匹配的记录。

和addmetadata类似,最后也会触发`deleted{$meta_type}_metadelete_metadata`这两个action。

八、Action钩子:给世界一个拥抱的机会

wp_insert_post()以及相关的update_metadata()add_metadata()delete_metadata()函数中,都穿插着各种action钩子。这些钩子就像一个个预留的接口,允许其他插件或主题在特定的时机介入,修改数据,或者执行一些自定义的逻辑。

以下是一些常见的action钩子:

钩子名称 触发时机 作用
add_metadata 在添加meta数据之后触发 允许插件或主题在meta数据添加后执行一些操作
added_{$meta_type}_meta 在添加特定类型的meta数据之后触发 允许插件或主题在特定类型的meta数据添加后执行一些操作,例如added_post_meta
update_metadata 在更新meta数据之后触发 允许插件或主题在meta数据更新后执行一些操作
updated_{$meta_type}_meta 在更新特定类型的meta数据之后触发 允许插件或主题在特定类型的meta数据更新后执行一些操作,例如updated_post_meta
delete_metadata 在删除meta数据之后触发 允许插件或主题在meta数据删除后执行一些操作
deleted_{$meta_type}_meta 在删除特定类型的meta数据之后触发 允许插件或主题在特定类型的meta数据删除后执行一些操作,例如deleted_post_meta

通过这些action钩子,我们可以实现非常灵活的功能,比如:

  • 在保存文章时,自动更新相关的post_meta
  • 在删除文章时,自动删除相关的post_meta
  • 在更新post_meta时,自动发送通知邮件。
  • 等等…

九、一个简单的例子:拯救强迫症患者

假设我们有一个需求:当文章的标题被更新时,自动将一个名为_last_updated_titlepost_meta设置为新的标题。我们可以这样实现:

function my_update_last_updated_title( $post_id ) {
    $post = get_post( $post_id );
    if ( $post && isset( $_POST['post_title'] ) ) {
        update_post_meta( $post_id, '_last_updated_title', sanitize_text_field( $_POST['post_title'] ) );
    }
}
add_action( 'save_post', 'my_update_last_updated_title' );

这段代码首先定义了一个名为my_update_last_updated_title的函数,它接受一个$post_id参数,表示文章的ID。

然后,它使用get_post()函数获取文章对象,并检查$_POST['post_title']是否存在,确保是文章标题被更新了。

最后,它使用update_post_meta()函数将_last_updated_title设置为新的标题。

通过add_action()函数,我们将my_update_last_updated_title函数挂载到save_post这个action钩子上。这意味着,每次文章被保存时,my_update_last_updated_title函数都会被执行,从而实现我们的需求。

十、总结:掌控wp_insert_post(),掌控全局

通过今天的讲座,我们深入剖析了wp_insert_post()函数是如何处理post_meta数据,并触发相关action的。

  • wp_insert_post()是总指挥,负责协调整个文章的插入和更新过程。
  • post_meta是文章的“私房钱”,用来存储一些文章本身不包含,但又非常重要的信息。
  • update_post_meta()add_post_meta()delete_post_meta()post_meta的“管家”,负责post_meta的添加、更新和删除。
  • action钩子是预留的接口,允许其他插件或主题在特定的时机介入,修改数据,或者执行一些自定义的逻辑。

理解了这些,你就可以更加灵活地使用wp_insert_post()函数,更好地管理你的WordPress网站。

希望今天的讲座对你有所帮助! 咱们下期再见!

发表回复

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