集合
介绍
Illuminate\Support\Collection
类提供了一个流畅、方便的包装器,用于处理数组数据。例如,查看以下代码。我们将使用 collect
辅助函数从数组创建一个新的集合实例,对每个元素运行 strtoupper
函数,然后移除所有空元素:
$collection = collect(['taylor', 'abigail', null])->map(function (?string $name) {
return strtoupper($name);
})->reject(function (string $name) {
return empty($name);
});
如你所见,Collection
类允许你链式调用其方法,执行对底层数组的流畅映射和缩减。通常,集合是不可变的,这意味着每个 Collection
方法都会返回一个全新的 Collection
实例。
创建集合
如上所述,collect
辅助函数会为给定的数组返回一个新的 Illuminate\Support\Collection
实例。因此,创建集合非常简单:
$collection = collect([1, 2, 3]);
Eloquent 查询的结果总是以 |
扩展集合
集合是 “可扩展的”(macroable),这使得你可以在运行时向 Collection
类添加额外的方法。Illuminate\Support\Collection
类的 macro
方法接受一个闭包,该闭包将在调用宏时执行。宏的闭包可以通过 $this
访问集合的其它方法,就像它是集合类的一个真实方法一样。例如,以下代码将一个 toUpper
方法添加到 Collection
类:
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
Collection::macro('toUpper', function () {
return $this->map(function (string $value) {
return Str::upper($value);
});
});
$collection = collect(['first', 'second']);
$upper = $collection->toUpper();
// ['FIRST', 'SECOND']
通常,你应该在【服务提供者】的 boot
方法中声明集合宏。
宏的参数
如果需要,你可以定义接受额外参数的宏:
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Lang;
Collection::macro('toLocale', function (string $locale) {
return $this->map(function (string $value) use ($locale) {
return Lang::get($value, [], $locale);
});
});
$collection = collect(['first', 'second']);
$translated = $collection->toLocale('es');
可用方法列表
在接下来的大部分集合文档中,我们将讨论 Collection
类中每个可用的方法。请记住,所有这些方法都可以链式调用,以流畅地操作底层数组。此外,几乎每个方法都会返回一个新的 Collection
实例,这样你就可以在必要时保留集合的原始副本。
TODO
高级有序消息
集合还支持“高阶消息”(higher order messages),它们是用于在集合上执行常见操作的快捷方式。提供高阶消息的集合方法包括:average
、avg
、contains
、each
、every
、filter
、first
、flatMap
、groupBy
、keyBy
、map
、max
、min
、partition
、reject
、skipUntil
、skipWhile
、some
、sortBy
、sortByDesc
、sum
、takeUntil
、takeWhile
和 unique
。
每个高阶消息都可以作为集合实例的动态属性进行访问。例如,下面我们使用 each
高阶消息来调用集合中每个对象的方法:
use App\Models\User;
$users = User::where('votes', '>', 500)->get();
$users->each->markAsVip();
同样,我们可以使用 sum
高阶消息来计算一个用户集合中所有 “votes” 的总和:
$users = User::where('group', 'Development')->get();
return $users->sum->votes;
懒集合
介绍
在深入学习 Laravel 的懒集合(lazy collections)之前,先花一些时间熟悉 【PHP 的生成器】(generators)。 |
为了补充已经非常强大的 Collection
类,LazyCollection
类利用了 PHP 的【生成器】,让你在处理非常大的数据集时保持低内存使用。
例如,假设你的应用程序需要处理一个多吉字节的日志文件,并利用 Laravel 的集合方法来解析日志。与一次性将整个文件读取到内存中不同,懒集合可以让你在给定的时间内仅将文件的一小部分保留在内存中:
use App\Models\LogEntry;
use Illuminate\Support\LazyCollection;
LazyCollection::make(function () {
$handle = fopen('log.txt', 'r');
while (($line = fgets($handle)) !== false) {
yield $line;
}
})->chunk(4)->map(function (array $lines) {
return LogEntry::fromLines($lines);
})->each(function (LogEntry $logEntry) {
// 处理日志条目...
});
或者,假设你需要遍历 10,000 个 Eloquent 模型。当使用传统的 Laravel 集合时,所有 10,000 个 Eloquent 模型必须一次性加载到内存中:
use App\Models\User;
$users = User::all()->filter(function (User $user) {
return $user->id > 500;
});
然而,查询构建器的 cursor
方法返回的是一个 LazyCollection
实例。这允许你仍然只对数据库执行一次查询,并且每次只加载一个 Eloquent 模型到内存中。在这个例子中,filter
回调只有在我们真正遍历每个用户时才会执行,从而大大减少了内存使用:
use App\Models\User;
$users = User::cursor()->filter(function (User $user) {
return $user->id > 500;
});
foreach ($users as $user) {
echo $user->id;
}
创建懒集合
要创建一个懒集合实例,你应该将一个 PHP 生成器函数传递给集合的 make
方法:
use Illuminate\Support\LazyCollection;
LazyCollection::make(function () {
$handle = fopen('log.txt', 'r');
while (($line = fgets($handle)) !== false) {
yield $line;
}
});
Enumerable 合约
几乎所有在 Collection
类上可用的方法也都可以在 LazyCollection
类上使用。这两个类都实现了 Illuminate\Support\Enumerable
接口,该接口定义了以下方法:
TODO
懒集合方法
除了 Enumerable
接口中定义的方法外,LazyCollection
类还包含以下方法:
takeUntilTimeout()
takeUntilTimeout
方法返回一个新的懒集合,该集合会在指定的时间之前枚举值。超过该时间后,集合将停止枚举:
$lazyCollection = LazyCollection::times(INF)
->takeUntilTimeout(now()->addMinute());
$lazyCollection->each(function (int $number) {
dump($number);
sleep(1);
});
// 1
// 2
// ...
// 58
// 59
为了说明这个方法的使用,假设一个应用程序需要从数据库中提交发票。你可以定义一个计划任务,每 15 分钟运行一次,并且每次只处理最多 14 分钟的发票:
use App\Models\Invoice;
use Illuminate\Support\Carbon;
Invoice::pending()->cursor()
->takeUntilTimeout(
Carbon::createFromTimestamp(LARAVEL_START)->add(14, 'minutes')
)
->each(fn (Invoice $invoice) => $invoice->submit());
tapEach()
each
方法会立即为集合中的每个项调用给定的回调,而 tapEach
方法则只有在项一个一个地从列表中提取时,才会调用给定的回调。
// 到目前为止没有任何输出...
$lazyCollection = LazyCollection::times(INF)->tapEach(function (int $value) {
dump($value);
});
// 输出三个项...
$array = $lazyCollection->take(3)->all();
// 1
// 2
// 3