ext-collections: 优雅地操作 PHP 数组

2020-03-31 01:19:00 +08:00
 CismonX

1. 背景

例如,我们有一个数组,包含了一系列员工的姓名、性别、年龄等信息:

$employees = [
    ['name' => 'Alice',    'gender' => 'female', 'age' => 35],
    ['name' => 'Bob',      'gender' => 'male',   'age' => 29],
    ['name' => 'David',    'gender' => 'male',   'age' => 40],
    ['name' => 'Benjamin', 'gender' => 'male',   'age' => 32]
];

我们需要从中获取所有男性员工的姓名,按照他们的年龄倒序排列。用原生 PHP 函数我们可以这样实现:

$arr = array_filter($employees, function ($value) {
    return $value['gender'] == 'male';
});
usort($arr, function ($v1, $v2) {
    return $v2['age'] - $v1['age'];
});
$names = array_map(function ($value) {
    return $value['name'];
}, $arr);
// $names == ['David', 'Benjamin', 'Bob']

如果能像下面这样实现,代码的可读性会得到明显提升:

$names = Collection::init($employees)
    ->filter(function ($value) {
        return $value['gender'] == 'male';
    })
    ->sortedByDescending(function ($value) {
        return $value['age'];
    })
    ->map(function ($value) {
        return $value['name'];
    })
    ->toArray();

2. 项目介绍

ext-collections 扩展库提供了这样的能力。它包含大量的用于操作 PHP 数组的方法,当我们需要操作的数据非常复杂时,使用它来代替 PHP 原生的数组函数,有助于提高开发效率,写出更优雅、更易维护的代码。

同时,ext-collections 使用 C 语言实现,性能上不弱于 PHP 提供的原生函数,一定程度上优于 Laravel Collections 等 PHP 实现的同类的库(性能优势有限,因为绝大多数的开销在回调上)。相比一些 C 实现的同类的库(如 viest/php-ext-collection)也提供了更丰富的功能。

3. 作者的话

这个项目是我在 2018 年初学 Kotlin 的时候,第一次见到 kotlin.collections 中的写法(那会儿还没学 Java,不知道 Stream API ),觉得很 cool 。正巧也在学 PHP,发现 PHP 没有原生支持这样的写法,就头脑一热造了个轮子,零零散散大概用了半年多时间完成。试着投稿 PECL 被拒,原因是 PHP 已经有了 DS 扩展,建议在它的基础上扩充,而不是重新搞一个类出来。

后来忙着实习和毕设,就没怎么打理这个项目。去年下旬 PHP 7.4 发布后,适配了一下 PHP 7.4 。最近几天又接入了一下 Codecov 。之后可能不会主动为这个项目添加更多的 feature 了,因为我还在搞好几个其他的开源项目。发到这里主要是希望有需要的朋友能够看到这个项目,尝试使用它并反馈 bug 。有兴趣的朋友还可以为该项目增添更多的 feature,从而让它活跃得更久。

3120 次点击
所在节点    PHP
16 条回复
sagaxu
2020-03-31 01:25:22 +08:00
lambda 如果不能 inline,开销是极大的
ericgui
2020-03-31 02:46:19 +08:00
你不如干脆搞个 libpdk,把 java 的 jdk 都搬过来
yuzo555
2020-03-31 05:10:35 +08:00
不过,原生用法里第一步可以用 array_multisort,第三步可以用 array_column,会简洁一些...
oneisall8955
2020-03-31 06:40:16 +08:00
和 JAVA 的 stream 操作很像
zachlhb
2020-03-31 08:15:29 +08:00
这是借鉴了 laravel 的集合么
php01
2020-03-31 08:57:26 +08:00
楼主,你举的这个例子,三楼是最佳实践。
你的这个例子,路子走歪了。
PHPJit
2020-03-31 09:50:49 +08:00
不错,支持一下
heybuddy
2020-03-31 10:45:29 +08:00
laravel 和 tp 都 tp 都有操作集合
askfilm
2020-03-31 10:50:59 +08:00
不错,支持一下 +1
fancy111
2020-03-31 11:10:09 +08:00
foreach ($employees as $key => $value) {
if ($value['gender']=='male') {
$arr[$value['name']]=$value['age'];
}
}
arsort($arr);
论可读性,原生直写多好。论性能,也比你省了一次 O(n)。
skymei
2020-03-31 11:15:11 +08:00
还是要感谢楼主的奉献,社区就是靠大家的奉献才能更加完善
realpg
2020-03-31 11:22:35 +08:00
@php01 #6
学会了用锤子,看啥都是钉子
Junjunya
2020-03-31 11:26:55 +08:00
为楼主赞一个,感觉这个可以当做一个很好的 php 怎么写扩展的教程
gz911122
2020-03-31 12:02:01 +08:00
@fancy111 这也太丑了

楼主的操作符清晰很多啊
CismonX
2020-03-31 12:48:03 +08:00
统一回复一下

@yuzo555 @php01

是的,借助这两个函数,代码确实可以更简洁一些。但个人认为代码的可读性主要在于“表达力”,而不是“简洁”。曾经我也热衷于写各种 one-liner,鼓捣一些“黑魔法”,为了让自己的代码短一些。但事实上它们往往是不易读懂、难以维护的。一段表达力强的代码,应当尽可能顺应人类的思维,而不是迫使人用计算机中的概念去理解。

对于我在上面举的这个简单例子,当我们阅读“获取所有男性员工的姓名,按照他们的年龄倒序排列”这段话时,我们的大脑会提炼出这三个关键词,“男性”、“年龄倒序”、“获取姓名”,对应的就是 filter 、sort 、map 三个函数,非常直观,不需要 second thought 就知道这段代码在做什么。基于 array_multisort 之类的实现却欠缺这一直观性。

在实际开发中也能体会到,对“表达力”的强调也深深融入在 Stream API 这些库的设计理念中。

@fancy111

首先这种写法得到的结果不符合预期,还需要一个 array_keys($arr)

可读性这块,可以参考我前面的回答。确实简洁,但我觉得表达力是有所欠缺的。阅读这段代码的人需要先记住第三行“维护了一个姓名和年龄的映射”,然后看到第六行“按值逆序,保留键”,从而推理出来这段代码在做什么。这是对问题的进一步抽象,而非直观认识。这个例子或许很简单,但在更复杂的场景中,这样的代码被别人阅读时,或是被未来的自己阅读时,都会带来额外的理解成本。

性能这块,其实是这些库为了实现其理念所作出的一些牺牲。首先回调带来了额外开销,对于同样的算法,它的性能往往是要弱于简单的 for loop 的,但这些性能损耗在实践中,相比提升可读性、可维护性的优势,完全可以忽略不计。
hbolive
2020-03-31 14:16:20 +08:00
@CismonX 不管如何,我很赞同你的理念,可读性大于简洁,除非这个简洁真的能带来很大性能上的提升。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/657787

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX