managur / collection
一个基础集合类,用于构建你的对象集合
Requires
- php: 8.0 - 8.3
Requires (Dev)
- phpunit/phpunit: ^9.5
- squizlabs/php_codesniffer: ^3.3
Replaces
README
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()
)的起始状态。
如果您使用seed与shuffle()
一起,则对于给定的原始集合和种子值,生成的集合将始终以相同的顺序排列其元素。它永远不会为这种组合返回不同的顺序。
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(); });