分析 `wp_list_pluck()` 函数的源码,它是如何从一个对象数组中提取特定属性的值并返回新数组的?

咳咳,各位观众老爷晚上好!我是今天的主讲人,人称“代码界的段子手”。今天咱们要聊的,是WordPress里面一个看似简单,实则暗藏玄机的函数:wp_list_pluck()

说它简单,是因为它的功能一目了然:从一个对象数组(或者关联数组数组)中,提取特定属性的值,然后把这些值组成一个新的数组返回。说它暗藏玄机,是因为它能处理各种奇奇怪怪的数据结构,还能根据你的需要进行排序和去重。

咱们今天就来扒一扒它的源码,看看它到底是怎么做到的。

一、wp_list_pluck() 函数的定义和基本用法

首先,我们先来看一下 wp_list_pluck() 函数的定义(在 WordPress 的 wp-includes/functions.php 文件里可以找到):

/**
 * Pluck a certain field from each object in a list.
 *
 * This has the same effect as array_column() but works for objects.
 *
 * @since 4.7.0
 *
 * @param array       $list     An array of objects or arrays.
 * @param string      $field    Field from the object to place instead of the entire object.
 * @param string|null $index_key Optional. Field from the object to use as keys for the new array.
 * @return array A list of found field values. Keys correspond to the values
 *               of the $index_key property, if set. Otherwise, keys are numeric.
 *               If $index_key is set, but a key is not found in the object,
 *               that object's value will not be included in the returned array.
 */
function wp_list_pluck( $list, $field, $index_key = null ) {
    if ( ! is_array( $list ) ) {
        return array();
    }

    $newlist = array();

    if ( null === $index_key ) {
        foreach ( $list as $key => $value ) {
            if ( is_object( $value ) ) {
                if ( isset( $value->$field ) ) {
                    $newlist[ $key ] = $value->$field;
                }
            } else {
                if ( isset( $value[ $field ] ) ) {
                    $newlist[ $key ] = $value[ $field ];
                }
            }
        }
    } else {
        foreach ( $list as $value ) {
            if ( is_object( $value ) ) {
                if ( isset( $value->$index_key ) ) {
                    $newlist[ $value->$index_key ] = $value->$field;
                }
            } else {
                if ( isset( $value[ $index_key ] ) ) {
                    $newlist[ $value[ $index_key ] ] = $value[ $field ];
                }
            }
        }
    }

    return $newlist;
}

简单解释一下:

  • $list: 这是你要处理的数据,必须是一个数组,数组的每个元素可以是对象,也可以是关联数组。
  • $field: 这是你要提取的属性名(字段名),比如你要提取所有文章的标题,那这里就是 'post_title'
  • $index_key: (可选)这个参数允许你指定用哪个属性的值作为新数组的键名。如果你不指定,新数组的键名就是默认的数字索引。

举个例子,假设我们有这样一个文章对象数组:

$posts = array(
    (object) array( 'ID' => 1, 'post_title' => 'Hello World', 'post_author' => 'admin' ),
    (object) array( 'ID' => 2, 'post_title' => 'WordPress Rocks', 'post_author' => 'editor' ),
    (object) array( 'ID' => 3, 'post_title' => 'Coding is Fun', 'post_author' => 'developer' ),
);

如果我们想提取所有文章的标题,可以这样:

$titles = wp_list_pluck( $posts, 'post_title' );

print_r( $titles );
// 输出:
// Array
// (
//     [0] => Hello World
//     [1] => WordPress Rocks
//     [2] => Coding is Fun
// )

如果我们想以文章的 ID 作为新数组的键名,可以这样:

$titles_by_id = wp_list_pluck( $posts, 'post_title', 'ID' );

print_r( $titles_by_id );
// 输出:
// Array
// (
//     [1] => Hello World
//     [2] => WordPress Rocks
//     [3] => Coding is Fun
// )

二、源码分析:一步一步揭开它的神秘面纱

接下来,我们就深入源码,看看 wp_list_pluck() 到底是怎么工作的。

  1. 参数校验和初始化
    if ( ! is_array( $list ) ) {
        return array();
    }

    $newlist = array();

这段代码首先检查传入的 $list 是否是一个数组。如果不是,直接返回一个空数组,避免程序出错。然后,初始化一个空数组 $newlist,用来存放提取出来的结果。

  1. 处理没有 $index_key 的情况
    if ( null === $index_key ) {
        foreach ( $list as $key => $value ) {
            if ( is_object( $value ) ) {
                if ( isset( $value->$field ) ) {
                    $newlist[ $key ] = $value->$field;
                }
            } else {
                if ( isset( $value[ $field ] ) ) {
                    $newlist[ $key ] = $value[ $field ];
                }
            }
        }
    }

如果没有指定 $index_key,代码会遍历 $list 数组。对于每个元素 $value,它会判断 $value 是对象还是关联数组。

  • 如果是对象: 它会使用 isset( $value->$field ) 来检查对象 $value 是否存在名为 $field 的属性。如果存在,就将该属性的值赋给 $newlist[ $key ],其中 $key 是原始数组 $list 的键名。
  • 如果是关联数组: 它会使用 isset( $value[ $field ] ) 来检查数组 $value 是否存在名为 $field 的键。如果存在,就将该键的值赋给 $newlist[ $key ]

简单来说,就是遍历数组,判断数组里面的每一个元素是对象还是数组,并根据不同的类型使用对应的语法取值,然后将取到的值放到新的数组中,键名保持不变。

  1. 处理有 $index_key 的情况
    else {
        foreach ( $list as $value ) {
            if ( is_object( $value ) ) {
                if ( isset( $value->$index_key ) ) {
                    $newlist[ $value->$index_key ] = $value->$field;
                }
            } else {
                if ( isset( $value[ $index_key ] ) ) {
                    $newlist[ $value[ $index_key ] ] = $value[ $field ];
                }
            }
        }
    }

如果指定了 $index_key,代码的逻辑类似,只不过在赋值给 $newlist 的时候,键名不再是原始数组的 $key,而是 $value->$index_key (如果是对象)或者 $value[ $index_key ](如果是数组)。

需要注意的是,这里用 isset() 来判断 $index_key 是否存在。如果 $index_key 不存在,那么这个元素就不会被添加到 $newlist 中。这可以避免出现 $newlist[null] 或者 $newlist[''] 这样的键名。

  1. 返回结果
    return $newlist;

最后,函数返回构建好的 $newlist 数组。

三、深入理解:一些细节和技巧

  • isset() 的重要性: wp_list_pluck() 使用 isset() 来判断属性或键是否存在,而不是直接访问属性或键的值。这是因为如果属性或键不存在,直接访问可能会导致 PHP 报错(例如 E_NOTICE 级别的错误)。isset() 可以安全地检查变量是否已经设置,避免这些错误。

  • 对象和数组的兼容性: wp_list_pluck() 可以同时处理对象数组和关联数组数组,这使得它非常灵活。它会根据元素的类型,使用不同的语法来访问属性或键的值。

  • 键名冲突: 如果 $index_key 对应的属性值在数组中存在重复,那么后面的值会覆盖前面的值。例如:

    $posts = array(
        (object) array( 'ID' => 1, 'post_title' => 'Hello World', 'post_author' => 'admin' ),
        (object) array( 'ID' => 2, 'post_title' => 'WordPress Rocks', 'post_author' => 'editor' ),
        (object) array( 'ID' => 2, 'post_title' => 'Coding is Fun', 'post_author' => 'developer' ), // ID 重复
    );
    
    $titles_by_id = wp_list_pluck( $posts, 'post_title', 'ID' );
    
    print_r( $titles_by_id );
    // 输出:
    // Array
    // (
    //     [1] => Hello World
    //     [2] => Coding is Fun  // 后面的值覆盖了前面的值
    // )
  • 性能考虑: wp_list_pluck() 使用 foreach 循环来遍历数组,对于大型数组来说,可能会有一些性能损耗。如果性能是关键,可以考虑使用 PHP 的 array_column() 函数,它通常比 wp_list_pluck() 更快,但是 array_column() 只能处理数组,不能处理对象。

四、wp_list_pluck() 的应用场景

wp_list_pluck() 在 WordPress 开发中有很多应用场景,下面列举一些常见的:

  • 提取文章标题: 从文章对象数组中提取文章标题,用于生成文章列表。

    $posts = get_posts( array( 'numberposts' => 10 ) ); // 获取 10 篇文章
    $titles = wp_list_pluck( $posts, 'post_title' );  // 提取文章标题
  • 获取分类 ID: 从分类对象数组中获取分类 ID,用于构建分类下拉菜单。

    $categories = get_categories(); // 获取所有分类
    $category_ids = wp_list_pluck( $categories, 'term_id' ); // 提取分类 ID
  • 生成用户 ID 数组: 从用户对象数组中提取用户 ID,用于权限判断。

    $users = get_users(); // 获取所有用户
    $user_ids = wp_list_pluck( $users, 'ID' ); // 提取用户 ID
  • 自定义字段处理: 从自定义字段数组中提取特定字段的值。

    $meta_values = get_post_meta( get_the_ID(), 'my_custom_field' );
    $values = wp_list_pluck( $meta_values, 0 ); // 提取自定义字段的值(假设字段是数组)

五、wp_list_pluck()array_column() 的比较

PHP 5.5 引入了 array_column() 函数,它的功能与 wp_list_pluck() 类似,都是从数组中提取指定列的值。那么,我们应该选择哪个函数呢?

特性 wp_list_pluck() array_column()
数据类型 支持对象数组和关联数组数组 只支持关联数组数组
兼容性 WordPress 内置函数,所有 WordPress 版本都可用 PHP 5.5+
性能 通常比 array_column() 通常比 wp_list_pluck()
灵活性 更灵活,可以处理更复杂的数据结构 相对简单,只适用于标准的关联数组数组

简单总结一下:

  • 如果你的数据是对象数组,或者需要兼容较旧的 PHP 版本,那么 wp_list_pluck() 是一个不错的选择。
  • 如果你的数据是关联数组数组,并且运行环境是 PHP 5.5+,那么 array_column() 通常是更好的选择,因为它性能更高。

六、 实例演示:结合 WordPress API 使用 wp_list_pluck()

为了更好地理解 wp_list_pluck() 的用法,我们来看一个更完整的例子:从 WordPress 数据库中获取所有文章的标题和 ID,并以 ID 作为键名,创建一个包含标题的数组。

<?php
/**
 * 获取所有文章标题,以 ID 为键名
 *
 * @return array
 */
function get_all_post_titles_by_id() {
    global $wpdb;

    // 查询所有文章的 ID 和标题
    $query = "SELECT ID, post_title FROM {$wpdb->posts} WHERE post_type = 'post' AND post_status = 'publish'";
    $results = $wpdb->get_results( $query );

    // 使用 wp_list_pluck 提取标题,以 ID 为键名
    $titles_by_id = wp_list_pluck( $results, 'post_title', 'ID' );

    return $titles_by_id;
}

// 调用函数并输出结果
$post_titles = get_all_post_titles_by_id();
print_r( $post_titles );

?>

这个例子首先使用 $wpdb 对象执行 SQL 查询,获取所有文章的 ID 和标题。然后,使用 wp_list_pluck() 函数从查询结果中提取标题,并将文章 ID 作为键名。最后,返回包含所有文章标题的数组。

七、总结:wp_list_pluck() 的价值

wp_list_pluck() 是一个非常实用的函数,它可以帮助我们从复杂的数据结构中快速提取所需的信息。它不仅可以简化代码,提高开发效率,还可以避免手动遍历数组带来的错误。

虽然 array_column() 在某些情况下性能更好,但是 wp_list_pluck() 的灵活性和兼容性使得它在 WordPress 开发中仍然扮演着重要的角色。

好了,今天的讲座就到这里。希望通过今天的分析,大家对 wp_list_pluck() 函数有了更深入的了解。如果有什么疑问,欢迎在评论区留言,我会尽力解答。下次再见!

发表回复

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