managur/collection

一个基础集合类,用于构建你的对象集合

1.12.0 2024-01-27 18:07 UTC

README

CI Latest Stable Version License Coverage Status Maintainability

Managur Collections 是一个库,为 PHP 提供了一个功能齐全的集合对象。

什么是集合?

集合在 PHP 中类似于数组,但可以在索引和值级别上限制为特定类型。

集合还提供了一些内置功能,例如能够轻松搜索特定值的内容、执行过滤以及在封闭作用域内迭代。

让我们看看一些例子

安装

在我们可以使用 Managur Collections 之前,我们需要安装它。推荐的方法是使用 PHP 的包管理器,Composer

$ composer require managur/collection

示例用法

我们假设你已经通过 Composer 安装,因此正在使用 Composer 的自动加载器。

你可以使用两种方式来使用 Managur Collections。第一种是使用其类构造函数,如下所示

<?php
use Managur\Collection\Collection;

$collection = new Collection(['your', 'collectible', 'data']);

第二种方式是使用方便的 collect() 函数,它已包含在内

$collection = collect(['your', 'collectible', 'data']);

现在你有一个集合了!

限制键和值类型

集合非常适合类型保证。如果你传递一个数组,无法保证数组索引或值是任何给定的类型,这意味着你将经常需要在应用程序中检查它们。

Managur Collections 可以轻松地约束到类型,无论是通过自己扩展基本 Collection 类,还是使用提供的工厂方法。

整数集合

如果你需要一个只能有整数值的集合,你可以像以下这样使用自己的类扩展 Managur Collections

<?php declare(strict_types=1);

use Managur\Collection\Collection;

final class IntegerCollection extends Collection
{
    protected $valueType = 'integer';
}

现在,当你构建或添加数据到你的 IntegerCollection 时,如果你要添加到集合中的数据类型不是 Integer,将会抛出一个 TypeError 异常,你可以立即捕获它。

快速失败。

具有整数键的杂项集合

类似于对值执行类型限制,你还可以修复索引(或键)类型

<?php declare(strict_types=1);

use Managur\Collection\Collection;

final class IntegerKeyCollection extends Collection
{
    protected $keyType = 'integer';
}

现在无法使用非整数的索引为此特定集合类型。

如果你需要,可以结合键和值的限制来强制一个非常特定类型的集合。

键策略

当你 append()offsetSet() 一个值到集合中时,我们调用受保护的 keyStrategy() 方法,将你想要收集的项目传递给它。默认情况下我们返回 null,这告诉 append()offsetSet() 你希望集合使用 PHP 的默认零索引、自动递增的键 ID。

如果不是这样,无论是你仅仅希望按照自己的键组织,还是因为你的键类型限制为 string 等,你都可以在子集合类中重写 keyStrategy() 方法来自动设置键为你选择的值。

例如,在我们的假设 UserCollection 类中,我们添加了以下 keyStrategy() 的实现

protected function keyStrategy($value)
{
    return $value->id();
}

一旦设置了键策略,在集合中添加或设置项目将适当地设置键。

$user = new User('my-user-id', 'Managur User');
$collection = new UserCollection();
$collection[] = $user;

var_dump($collection['my-user-id'] === $user); // returns true

使用静态工厂方法动态设置类型化集合

我们提供了三种方法,可以生成具有与主集合相同功能但强制键和/或值类型的匿名类

use Managur\Collection\Collection;

$integerValueCollection = Collection::newTypedValueCollection('integer', [1,2,3,4,5]);
$integerKeyCollection = Collection::newTypedKeyCollection('integer', ['a','b',1]);
$integerCollection = Collection::newTypedCollection('integer', 'integer', [1,2,3,4,5]);

如果您愿意,可以使用 newTypedCollection() 方法来实现所有目的,传入 null 作为键(第一个参数)和/或值(第二个参数)来获取您所需的特定组合。

测试

此库使用 PHPUnit 7 来提供单元测试。要自行运行测试,只需在命令行界面输入以下内容

$ composer test

测试应自动运行,前提是您可以直接从命令行调用 PHP(即它在您的路径中)。

我们还使用 Github Actions 自动化测试。您可以在 此处 查看最新的构建。

代码规范

我们强制执行 PSR-2 编码标准。要测试代码,运行以下命令

$ composer cs

修复代码规范失败

要自动修复代码规范失败,运行以下命令

$ composer cs-fix

手册

Managur Collection 提供的完整功能在此处文档化

追加数据

重要:如果您使用的是类型化索引,追加将失败。

数据可以通过两种方式之一追加到集合中

数组语法

$collection[] = 'new data';

对象语法

$collection->append('new data');

偏移量设置

如果您需要将值设置为集合中的特定索引,请按照以下方式操作

注意:如果此索引已有数据,它将被覆盖。

数组语法

$collection[4] = 'new data';

对象语法

$collection->offsetSet(4, 'new data');

映射

map() 提供了一种遍历集合、修改它们并返回带有更改的新集合的方法。它将接受任何 callable 作为参数。

因为此函数在私有作用域内运行,它保护了任何存在于 循环 之外的外部变量,同时防止作用域内的数据泄漏到更广泛的代码中。因此,在大多数情况下,建议使用 map() 而不是简单的 foreach() 循环。

以下是一个简单示例,将每个值加倍

$collection = new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$doubles = $collection->map(function ($value) {
    return $value * 2;
});

映射到

mapInto() 方法允许您通过一次调用,将任何集合转换为给定类型的新的集合,在数据上运行可调用的函数,并将其立即收集到新的集合中。

以下是一个示例,其中我们创建了新的集合,第一个集合仅允许收集整数,第二个集合仅允许收集字符串

$ints = new IntegerCollection([1, 2, 3, 4, 5]);
$strings = $ints->mapInto(function (int $item): string {
    return (string)($item*10);
}, StringCollection::class);

现在我们有一个 StringCollection,它包含来自原始 $ints 集合的每个值的字符串表示,乘以 10。

切片

slice() 提供了一种返回数组中元素序列的方法

这基于数组的 位置,而不是键。

以下是一个简单示例,从数组中切片出前两个元素

$collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3]);
$doubles = $collection->slice(0, 2); // ['a' => 1, 'b' => 2]

PHP 文档 详细介绍了 $offset$limit 的行为。

每个

有时我们需要遍历值,但不想进行任何更改。这就是 each()map() 的不同之处。否则,功能是相同的。

参考到 map() 的示例,以下是 each() 的实现,我们简单地 echo 加倍后的值,而不是进行任何操作

$collection = new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$collection->each(function ($value) {
    echo $value * 2;
});

减少

我们经常遍历数据以连接结果。也许我们想要从用户集合中获取所有电子邮件地址的列表。这就是 reduce() 成为我们的首选方法的地方。您可以将结果减少到任何内容。

reduce() 方法需要一个回调函数,该函数接受两个参数;我们将要追加的 carry 变量,以及我们将要使用的值。

reduce() 函数接受一个最终参数,它是初始化的 carry 值。这是可选的,在这种情况下,第一次迭代时将传递 null 到你的回调函数中,所以请确保你的回调函数可以接受 null 值。然而,我们不推荐这样做,并建议你始终提供一个与完成时返回的类型相同的初始值。

在这个例子中,我们将传递一个空数组作为我们的初始值,如最后一行所示。

$collection = new Collection($arrayOfUserObjects);
$emails = $collection->reduce(function ($carry, $user) {
    $carry[] = $user->email();
    return $carry
}, []);

人们常常忘记将值应用到 $carry 并在最后返回 $carry。不要忘记这一点,否则你会发现所有内容都缩减为 null

过滤器

如果你需要从你的集合中删除值,你可以使用 filter() 方法。如果你不传递任何参数,则任何匹配核心 empty() 函数返回 true 的相同标准的值都将从结果中删除。

你可以可选地提供一个回调函数,如果数据应该保留则返回 true,如果应该过滤则返回 false

注意$filter() 返回一个没有过滤值的集合的新副本。它不会过滤原始集合。

以下是一个例子,我们将过滤掉我们的用户集合中的所有非活动用户。

$collection = new Collection($arrayOfUserObjects);
$filtered = $collection->filter(function ($user) {
    return $user->isActive();
});

简单明了,并且我们又保护了作用域,这样我们就不可能在自己的代码中意外地覆盖任何东西。

第一

first() 方法可以可选地接受一个回调函数作为参数。如果你不提供它,则它将返回集合中的第一个非空值。

如果你 提供了 回调函数,它将返回第一个符合回调函数标准并返回 true 的值。

让我们看一个例子,我们获取第一个具有 Gmail 电子邮件地址的用户。

$collection = new Collection($arrayOfUserObjects);
$firstGmailUser = $collection->first(function ($user) {
    return false !== stripos($user->emailAddress(), '@gmail.');
});

最后

last()first() 完全相同,唯一的区别是它返回最后一个条目(希望这是显而易见的)。

$collection = new Collection($arrayOfUserObjects);
$lastGmailUser = $collection->last(function ($user) {
    return false !== stripos($user->emailAddress(), '@gmail.');
});

包含

如果你需要检查特定值是否存在于你的集合中,这是你要使用的方法。你也可以使用回调函数进行搜索,这为你提供了一种非常灵活的搜索操作,你可以匹配部分或计算数据。

contains() 返回一个布尔值,所以你可以用它来检查条件。

$collection = new Collection($arrayOfUserObjects);
$containsAGmailUser = $collection->contains(function ($user) {
    return false !== stripos($user->emailAddress(), '@gmail.');
});

如果在集合中找到一个具有 Gmail 地址的用户,则 $containsAGmailUser 将为 true

isEmpty()isNotEmpty() 方法可用于检查集合是否包含项。

$collection = new Collection([]);
$collection->isEmpty(); // true
$collection = new Collection([1, 2, 3]);
$collection->isNotEmpty(); // true

推入

push() 方法类似于 append(),但它仅在未定义严格索引类型时才起作用。它与 append() 的唯一区别在于你可以在一个操作中推送多个值。

$collection->push(5, 6, 7, 8, 9);

弹出

pop() 是一个典型的弹出方法,它将返回最新的值,同时将其从集合中删除。

$collection = new Collection([1, 2, 3, 4, 5]);
$popped = $collection->pop();

现在,$popped 将等于 5,而集合本身现在将只包含数字 1 到 4。

合并

如果你有两个兼容的集合(即它们不受不同类型的限制),你可以在一个操作中合并它们。

$collection1 = new Collection([1,2,3,4,5]);
$collection2 = new Collection([6,7,8,9,10]);
$merged = $collection1->merge($collection2);

请注意,返回了一个全新的集合,所以 $collection1$collection2 将保持它们之前的状态。

进入

into() 方法提供了一个可读的将集合复制到另一个 兼容 集合的方法。

$collection1 = new Collection([1,2,3,4,5]);
$collection2 = $collection1->into(IntegerCollection::class);

还有一个提供直接收集到你的首选集合类中的函数。

$collection = collectInto(IntegerCollection::class, [1,2,3,4,5]);

排序

如果你需要对你的集合数据进行排序,这是你要使用的方法。

sort() 可以接受一个可调用的函数,如果你希望使用用户定义的排序算法,或者你可以不带参数调用它。

这类似于使用简单的数组中的sort()usort()函数。

如果您指定了固定索引类型,我们将为每个值维护索引,类似于在常规数组上调用asort()uasort()

$collection = new Collection([5,2,6,4,7,1]);
$sorted = $collection->sort();
$alsoSorted = $collection->sort(function ($a, $b) {
    return $a <=> $b;
});

此方法将返回应用了排序的集合的副本。

ASort

sort()类似,asort()方法可以可选地接受一个回调函数来按自定义算法排序。与sort()asort()之间的唯一区别是,无论集合键类型是否受限制,都会维护索引关联。

$collection = new Collection([5,2,6,4,7,1]);
$sorted = $collection->asort();
$alsoSorted = $collection->asort(function ($a, $b) {
    return $a <=> $b;
});

此方法将返回应用了排序的集合的副本。

Shuffle

shuffle()方法将随机打乱您的值,然后返回一个包含打乱数据的新集合实例。

$collection = new Collection([1,2,3,4,5]);
$shuffled = $collection->shuffle();

shuffle()还接受一个可选的整数seed,该种子将用于确定用于确定元素打乱顺序的伪随机数生成器(mt_rand())的起始状态。

如果您使用seedshuffle()一起,则对于给定的原始集合和种子值,生成的集合将始终以相同的顺序排列其元素。它永远不会为这种组合返回不同的顺序。

Implode

implode()方法将接受您的集合,并返回一个使用$glue连接项目而成的字符串。

$collection = new Collection(['a', 'b']);
$joined = $collection->implode(', '); // 'a, b'

您还可以传递一个可选的可调用对象作为第二个参数,该参数允许您在imploding时从收集的对象中选择特定的属性。

$collection = new Collection($users);
$joined = $collection->implode(', ', function (User $user): string {
    return $user->name();
});