V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
CismonX
V2EX  ›  PHP

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

  •  
  •   CismonX · 2020-03-31 01:19:00 +08:00 · 3238 次点击
    这是一个创建于 1699 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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,从而让它活跃得更久。

    16 条回复    2020-03-31 14:16:20 +08:00
    sagaxu
        1
    sagaxu  
       2020-03-31 01:25:22 +08:00 via Android
    lambda 如果不能 inline,开销是极大的
    ericgui
        2
    ericgui  
       2020-03-31 02:46:19 +08:00 via Android
    你不如干脆搞个 libpdk,把 java 的 jdk 都搬过来
    yuzo555
        3
    yuzo555  
       2020-03-31 05:10:35 +08:00
    不过,原生用法里第一步可以用 array_multisort,第三步可以用 array_column,会简洁一些...
    oneisall8955
        4
    oneisall8955  
       2020-03-31 06:40:16 +08:00 via Android
    和 JAVA 的 stream 操作很像
    zachlhb
        5
    zachlhb  
       2020-03-31 08:15:29 +08:00 via Android
    这是借鉴了 laravel 的集合么
    php01
        6
    php01  
       2020-03-31 08:57:26 +08:00
    楼主,你举的这个例子,三楼是最佳实践。
    你的这个例子,路子走歪了。
    PHPJit
        7
    PHPJit  
       2020-03-31 09:50:49 +08:00 via iPhone
    不错,支持一下
    heybuddy
        8
    heybuddy  
       2020-03-31 10:45:29 +08:00
    laravel 和 tp 都 tp 都有操作集合
    askfilm
        9
    askfilm  
       2020-03-31 10:50:59 +08:00
    不错,支持一下 +1
    fancy111
        10
    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
        11
    skymei  
       2020-03-31 11:15:11 +08:00
    还是要感谢楼主的奉献,社区就是靠大家的奉献才能更加完善
    realpg
        12
    realpg  
       2020-03-31 11:22:35 +08:00
    @php01 #6
    学会了用锤子,看啥都是钉子
    Junjunya
        13
    Junjunya  
       2020-03-31 11:26:55 +08:00
    为楼主赞一个,感觉这个可以当做一个很好的 php 怎么写扩展的教程
    gz911122
        14
    gz911122  
       2020-03-31 12:02:01 +08:00
    @fancy111 这也太丑了

    楼主的操作符清晰很多啊
    CismonX
        15
    CismonX  
    OP
       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
        16
    hbolive  
       2020-03-31 14:16:20 +08:00
    @CismonX 不管如何,我很赞同你的理念,可读性大于简洁,除非这个简洁真的能带来很大性能上的提升。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2700 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 04:00 · PVG 12:00 · LAX 20:00 · JFK 23:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.