阐述 `delete_post_meta()` 函数的源码,它是如何从数据库中删除指定元数据的?

各位同学,今天咱们来扒一扒 WordPress 里面一个非常重要的函数—— delete_post_meta()。这货可是负责给文章(post)“瘦身”的,专门用来删除那些我们不再需要的自定义字段(meta data)。想象一下,你的文章本来穿了很多“衣服”(自定义字段),现在觉得太累赘了,想脱掉几件,那 delete_post_meta() 就是你的私人造型师,帮你把那些多余的“衣服”一件件脱下来。

好,废话不多说,直接上代码,然后咱们一点一点拆解它。

<?php
/**
 * Deletes post meta data.
 *
 * @since 2.0.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param int    $post_id  Post ID.
 * @param string $meta_key Optional. Meta key to delete. By default, delete for all meta keys.
 * @param mixed  $meta_value Optional. Meta value to delete. Must be used with $meta_key. By default, delete for all meta values.
 * @param bool   $delete_all Optional. Whether to delete all matching meta entries,
 *                             or just the first. Default is false.
 * @return bool True on successful delete, false on failure.
 */
function delete_post_meta( $post_id, $meta_key = '', $meta_value = '', $delete_all = false ) {
    global $wpdb;

    $post_id = absint( $post_id );
    if ( ! $post_id ) {
        return false;
    }

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

    $table = _get_meta_table( 'post' );

    $id_column = 'meta_id';
    $post_id_column = 'post_id';

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

    $meta_ids = array();

    do_action( 'delete_post_meta', $post_id, $meta_key, $meta_value, $delete_all );

    $key_name = esc_sql( $meta_key );

    if ( $delete_all ) {
        if ( ! empty( $meta_value ) || is_numeric( $meta_value ) ) {
            $value_to_delete = wp_slash( $meta_value );
            $query = $wpdb->prepare(
                "SELECT {$id_column} FROM {$table} WHERE {$post_id_column} = %d AND meta_key = %s AND meta_value = %s",
                $post_id,
                $meta_key,
                $value_to_delete
            );
        } else {
            $query = $wpdb->prepare(
                "SELECT {$id_column} FROM {$table} WHERE {$post_id_column} = %d AND meta_key = %s",
                $post_id,
                $meta_key
            );
        }

        $meta_ids = $wpdb->get_col( $query );

        if ( ! empty( $meta_ids ) ) {
            $meta_ids = array_map( 'intval', $meta_ids );
            $sql = "DELETE FROM {$table} WHERE {$id_column} IN (" . implode( ',', $meta_ids ) . ')';
            $count = $wpdb->query( $sql );

            if ( $count ) {
                wp_cache_delete_multiple( $meta_ids, 'post_meta' );
                do_action( 'deleted_post_meta', $meta_ids, $post_id, $meta_key, $meta_value, $delete_all );
                return true;
            } else {
                return false;
            }
        }
    } else {
        if ( ! empty( $meta_value ) || is_numeric( $meta_value ) ) {
            $value_to_delete = wp_slash( $meta_value );
            $query = $wpdb->prepare(
                "SELECT {$id_column} FROM {$table} WHERE {$post_id_column} = %d AND meta_key = %s AND meta_value = %s LIMIT 1",
                $post_id,
                $meta_key,
                $value_to_delete
            );
        } else {
            $query = $wpdb->prepare(
                "SELECT {$id_column} FROM {$table} WHERE {$post_id_column} = %d AND meta_key = %s LIMIT 1",
                $post_id,
                $meta_key
            );
        }

        $meta_id = $wpdb->get_var( $query );

        if ( $meta_id ) {
            /**
             * Fires immediately before post meta is deleted.
             *
             * @since 2.9.0
             *
             * @param int $meta_ids ID of the meta data being deleted.
             */
            do_action( 'delete_post_meta_by_mid', $meta_id );

            $result = $wpdb->delete( $table, array( $id_column => $meta_id ) );

            if ( $result ) {
                wp_cache_delete( $meta_id, 'post_meta' );
                do_action( 'deleted_post_meta', array( $meta_id ), $post_id, $meta_key, $meta_value, $delete_all );
                return true;
            } else {
                return false;
            }
        }
    }

    return false;
}

是不是感觉有点长?别怕,咱们把它分解成几个小模块,逐个击破。

1. 参数校验与准备阶段

    global $wpdb;

    $post_id = absint( $post_id );
    if ( ! $post_id ) {
        return false;
    }

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

    $table = _get_meta_table( 'post' );

    $id_column = 'meta_id';
    $post_id_column = 'post_id';

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

    $meta_ids = array();

    do_action( 'delete_post_meta', $post_id, $meta_key, $meta_value, $delete_all );
  • global $wpdb;: 这句是老朋友了,把全局的 $wpdb 对象拿过来,这样我们才能操作数据库。$wpdb 就像一个数据库的遥控器,你想对数据库做什么,都得通过它。
  • $post_id = absint( $post_id );: absint() 函数确保 $post_id 是一个正整数。如果 $post_id 不是整数,或者小于等于 0,那就直接返回 false,说明文章 ID 不合法,没法删除。
  • $meta_key = trim( $meta_key );: trim() 函数去除 $meta_key 首尾的空格,防止因为空格导致匹配失败。
  • if ( empty( $meta_key ) ) { return false; }: 如果 $meta_key 是空的,那还删个啥?直接返回 false
  • $table = _get_meta_table( 'post' );: _get_meta_table( 'post' ) 函数获取存储 post meta 的表名。通常情况下,这个表名是 wp_postmeta
  • $id_column = 'meta_id'; $post_id_column = 'post_id';: 定义了两个常量,分别代表 meta 表中的 ID 列(meta_id)和 post ID 列(post_id)。
  • $meta_key = wp_unslash( $meta_key ); $meta_value = wp_unslash( $meta_value );: wp_unslash() 函数移除 $meta_key$meta_value 中的反斜杠。这个操作主要是为了还原数据,因为在保存到数据库时,可能会对某些特殊字符进行转义。
  • $meta_ids = array();: 初始化一个空数组,用来存储待删除的 meta ID。
  • do_action( 'delete_post_meta', $post_id, $meta_key, $meta_value, $delete_all );: 这是一个非常重要的钩子(hook),允许开发者在删除 meta 数据之前执行自定义操作。你可以在这里做一些权限验证、日志记录等等。

这部分代码主要做了两件事:一是验证输入参数的合法性,二是准备一些后面要用到的变量。

2. delete_alltrue 的情况:删除所有匹配的 meta 数据

    if ( $delete_all ) {
        if ( ! empty( $meta_value ) || is_numeric( $meta_value ) ) {
            $value_to_delete = wp_slash( $meta_value );
            $query = $wpdb->prepare(
                "SELECT {$id_column} FROM {$table} WHERE {$post_id_column} = %d AND meta_key = %s AND meta_value = %s",
                $post_id,
                $meta_key,
                $value_to_delete
            );
        } else {
            $query = $wpdb->prepare(
                "SELECT {$id_column} FROM {$table} WHERE {$post_id_column} = %d AND meta_key = %s",
                $post_id,
                $meta_key
            );
        }

        $meta_ids = $wpdb->get_col( $query );

        if ( ! empty( $meta_ids ) ) {
            $meta_ids = array_map( 'intval', $meta_ids );
            $sql = "DELETE FROM {$table} WHERE {$id_column} IN (" . implode( ',', $meta_ids ) . ')';
            $count = $wpdb->query( $sql );

            if ( $count ) {
                wp_cache_delete_multiple( $meta_ids, 'post_meta' );
                do_action( 'deleted_post_meta', $meta_ids, $post_id, $meta_key, $meta_value, $delete_all );
                return true;
            } else {
                return false;
            }
        }
    }

这部分代码是核心逻辑之一,处理 $delete_alltrue 的情况。

  • if ( ! empty( $meta_value ) || is_numeric( $meta_value ) ) { ... } else { ... }: 判断是否指定了 $meta_value。如果指定了 $meta_value,就需要精确匹配 post_idmeta_keymeta_value;如果没有指定 $meta_value,只需要匹配 post_idmeta_key
  • $value_to_delete = wp_slash( $meta_value );: 如果存在 $meta_value,使用 wp_slash() 函数对 $meta_value 进行转义,防止 SQL 注入。
  • $query = $wpdb->prepare( ... );: 使用 $wpdb->prepare() 函数构造 SQL 查询语句。这个函数可以安全地处理用户输入,防止 SQL 注入。注意,这里使用了预处理语句,用 %d%s 等占位符代替实际的值,然后再将值传递给 $wpdb->prepare() 函数。
    • "SELECT {$id_column} FROM {$table} WHERE {$post_id_column} = %d AND meta_key = %s AND meta_value = %s": 如果指定了 $meta_value,这个 SQL 语句会查找所有 post_idmeta_keymeta_value 都匹配的 meta ID。
    • "SELECT {$id_column} FROM {$table} WHERE {$post_id_column} = %d AND meta_key = %s": 如果没有指定 $meta_value,这个 SQL 语句会查找所有 post_idmeta_key 都匹配的 meta ID。
  • $meta_ids = $wpdb->get_col( $query );: 执行 SQL 查询,获取所有匹配的 meta ID,并将它们存储在 $meta_ids 数组中。$wpdb->get_col() 函数只返回查询结果的第一列。
  • if ( ! empty( $meta_ids ) ) { ... }: 判断是否找到了匹配的 meta ID。如果没有找到,就直接跳过删除操作。
  • $meta_ids = array_map( 'intval', $meta_ids );: 将 $meta_ids 数组中的所有元素转换为整数。
  • $sql = "DELETE FROM {$table} WHERE {$id_column} IN (" . implode( ',', $meta_ids ) . ')';: 构造 SQL 删除语句。这里使用了 IN 子句,可以一次性删除多个 meta ID。implode( ',', $meta_ids ) 函数将 $meta_ids 数组转换为一个以逗号分隔的字符串。
  • $count = $wpdb->query( $sql );: 执行 SQL 删除语句。$wpdb->query() 函数返回受影响的行数。
  • if ( $count ) { ... } else { ... }: 判断是否成功删除了 meta 数据。如果 $count 大于 0,说明成功删除了 meta 数据,就执行一些清理操作,并返回 true;否则,返回 false
  • wp_cache_delete_multiple( $meta_ids, 'post_meta' );: 从 WordPress 对象缓存中删除已删除的 meta 数据。wp_cache_delete_multiple() 函数可以一次性删除多个缓存项。
  • do_action( 'deleted_post_meta', $meta_ids, $post_id, $meta_key, $meta_value, $delete_all );: 这是一个钩子,允许开发者在删除 meta 数据之后执行自定义操作。

3. delete_allfalse 的情况:只删除第一个匹配的 meta 数据

    else {
        if ( ! empty( $meta_value ) || is_numeric( $meta_value ) ) {
            $value_to_delete = wp_slash( $meta_value );
            $query = $wpdb->prepare(
                "SELECT {$id_column} FROM {$table} WHERE {$post_id_column} = %d AND meta_key = %s AND meta_value = %s LIMIT 1",
                $post_id,
                $meta_key,
                $value_to_delete
            );
        } else {
            $query = $wpdb->prepare(
                "SELECT {$id_column} FROM {$table} WHERE {$post_id_column} = %d AND meta_key = %s LIMIT 1",
                $post_id,
                $meta_key
            );
        }

        $meta_id = $wpdb->get_var( $query );

        if ( $meta_id ) {
            /**
             * Fires immediately before post meta is deleted.
             *
             * @since 2.9.0
             *
             * @param int $meta_ids ID of the meta data being deleted.
             */
            do_action( 'delete_post_meta_by_mid', $meta_id );

            $result = $wpdb->delete( $table, array( $id_column => $meta_id ) );

            if ( $result ) {
                wp_cache_delete( $meta_id, 'post_meta' );
                do_action( 'deleted_post_meta', array( $meta_id ), $post_id, $meta_key, $meta_value, $delete_all );
                return true;
            } else {
                return false;
            }
        }
    }

这部分代码处理 $delete_allfalse 的情况,也就是只删除第一个匹配的 meta 数据。

  • if ( ! empty( $meta_value ) || is_numeric( $meta_value ) ) { ... } else { ... }: 和前面一样,判断是否指定了 $meta_value
  • $query = $wpdb->prepare( ... );: 构造 SQL 查询语句。注意,这里使用了 LIMIT 1,表示只返回第一个匹配的结果。
    • "SELECT {$id_column} FROM {$table} WHERE {$post_id_column} = %d AND meta_key = %s AND meta_value = %s LIMIT 1": 如果指定了 $meta_value
    • "SELECT {$id_column} FROM {$table} WHERE {$post_id_column} = %d AND meta_key = %s LIMIT 1": 如果没有指定 $meta_value
  • $meta_id = $wpdb->get_var( $query );: 执行 SQL 查询,获取第一个匹配的 meta ID。$wpdb->get_var() 函数只返回查询结果的第一行第一列。
  • if ( $meta_id ) { ... }: 判断是否找到了匹配的 meta ID。
  • do_action( 'delete_post_meta_by_mid', $meta_id );: 这是一个钩子,允许开发者在通过 meta ID 删除 meta 数据之前执行自定义操作。
  • $result = $wpdb->delete( $table, array( $id_column => $meta_id ) );: 执行 SQL 删除语句。$wpdb->delete() 函数删除表中满足指定条件的行。
  • if ( $result ) { ... } else { ... }: 判断是否成功删除了 meta 数据。
  • wp_cache_delete( $meta_id, 'post_meta' );: 从 WordPress 对象缓存中删除已删除的 meta 数据。wp_cache_delete() 函数删除单个缓存项。
  • do_action( 'deleted_post_meta', array( $meta_id ), $post_id, $meta_key, $meta_value, $delete_all );: 这是一个钩子,允许开发者在删除 meta 数据之后执行自定义操作。

4. 兜底返回 false

    return false;
}

如果前面的所有操作都没有成功删除 meta 数据,就返回 false

总结一下,delete_post_meta() 函数的执行流程如下:

  1. 参数校验: 检查 $post_id$meta_key 是否合法。
  2. 准备工作: 获取 meta 表名,定义 ID 列和 post ID 列,移除反斜杠,初始化 meta ID 数组。
  3. 钩子: 触发 delete_post_meta 钩子。
  4. delete_alltrue
    • 构造 SQL 查询语句,查找所有匹配的 meta ID。
    • 执行 SQL 查询,获取 meta ID 列表。
    • 构造 SQL 删除语句,删除所有匹配的 meta ID。
    • 执行 SQL 删除语句。
    • 从对象缓存中删除已删除的 meta 数据。
    • 触发 deleted_post_meta 钩子。
  5. delete_allfalse
    • 构造 SQL 查询语句,查找第一个匹配的 meta ID。
    • 执行 SQL 查询,获取 meta ID。
    • 触发 delete_post_meta_by_mid 钩子。
    • 执行 SQL 删除语句,删除该 meta ID。
    • 从对象缓存中删除已删除的 meta 数据。
    • 触发 deleted_post_meta 钩子。
  6. 返回结果: 如果成功删除了 meta 数据,返回 true;否则,返回 false

表格总结

步骤 描述
参数校验 验证 $post_id$meta_key 的有效性。
准备工作 获取 meta 表名,定义 ID 和 post ID 列,移除反斜杠。
delete_post_meta 钩子 允许在删除 meta 数据前执行自定义操作。
delete_all = true 查找并删除所有匹配的 meta 数据:
1. 构建 SQL 查询,获取所有匹配的 meta ID。
2. 执行 SQL 删除,一次性删除所有 meta ID。
3. 从缓存中删除已删除的 meta 数据。
4. 触发 deleted_post_meta 钩子。
delete_all = false 查找并删除第一个匹配的 meta 数据:
1. 构建 SQL 查询,获取第一个匹配的 meta ID。
2. 触发 delete_post_meta_by_mid 钩子。
3. 执行 SQL 删除,删除该 meta ID。
4. 从缓存中删除已删除的 meta 数据。
5. 触发 deleted_post_meta 钩子。
返回结果 如果成功删除 meta 数据,返回 true,否则返回 false

实际应用场景举例

假设你有一个文章类型是 "book",你想删除所有书的 "author" 这个自定义字段。

$args = array(
    'post_type' => 'book',
    'posts_per_page' => -1, // 获取所有 book 类型的文章
);

$books = get_posts( $args );

foreach ( $books as $book ) {
    delete_post_meta( $book->ID, 'author', '', true ); // 删除所有 'author' 字段
}

这段代码会遍历所有 "book" 类型的文章,然后调用 delete_post_meta() 函数,将每本书的 "author" 字段都删除掉。delete_all 设置为 true,确保所有 "author" 字段都被删除,不管有多少个。

注意事项

  • 谨慎使用 delete_all = true 如果你的 meta key 使用不当,可能会误删一些不该删的数据。
  • SQL 注入: $wpdb->prepare() 函数可以防止 SQL 注入,但是在使用自定义 SQL 语句时,一定要注意对用户输入进行转义。
  • 缓存: 删除 meta 数据后,一定要清理缓存,确保前台显示的数据是最新的。

好了,关于 delete_post_meta() 函数的讲解就到这里。希望通过今天的学习,大家对这个函数有了更深入的了解。记住,熟练掌握这些常用的函数,才能在 WordPress 开发的道路上越走越远!下课!

发表回复

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