[转发]在Laravel6需要操作大量内存的地方使用LazyCollection

Laravel 团队在新版本 Laravel6 中添加了许多新特性,本文主要讨论 LazyCollection(惰性集合包装器)

在 Laravel 中,Illuminate\Support\Collection 类提供了一个非常方便的包装器来处理数据对象的数组。实际上,所有的 Eloquent 的数据库查询总是作为 Collection 实例来返回,当然不包括查询具体某一条数据的情况,相信使用 Laravel 的朋友对 Collection 类的操作都已经很熟悉了。LazyCollection 本质上扩展了 Collection 类的功能。

什么是 LazyCollection?#

LazyCollection 类和 Illuminate\Support\Collection 类相似只是加了点糖,该类基本上使用了 php 的生成器特性使你可以处理非常大的数据集,同时保持较低的内存占用,如果你不熟悉生成器,请参考鸟哥的一篇文章,非常简单易懂。

生成器类似于 PHP 中的普通函数,使用 yield 关键字代替 return。它的行为与 return 相似,因为它向函数的调用者返回一个值,但不是将函数从堆栈中删除,这使函数可以从再次调用时的位置继续。​​因此,无论哪个只要包含了 “yield” 的函数都是生成器,就像 “实时” 从函数返回值一样,您无需在函数本身中维护值的状态。一旦没有更多的值要产生,则生成器可以简单地退出,并且调用代码将继续进行,就像数组的值用完了一样。

LazyCollection 利用生成器的这种行为,以便在处理较大数据集时保持较低的内存占用。在这里,我们可以使用 LazyCollection 实例与传统 Collection 类方法结合使用,例如解析一个很大的日志文件。在这里,可以使用惰性集合而不是一次性将整个文件读取到内存中,而是仅将文件的一小部分保留在内存中。

use App\LogEntry;
use Illuminate\Support\LazyCollection;

LazyCollection::make(function () {
    $handle = fopen('log.txt', 'r');

    while (($line = fgets($handle)) !== false) {
        yield $line;
    }
})->chunk(4)->map(function ($lines) {
    return LogEntry::fromLines($lines);
})->each(function (LogEntry $logEntry) {
    // 具体操作。。。
});

请注意,我们传递给该匿名函数 make 的是一个生成器函数,因为使用了 yield 的关键字(并将返回 Generator 一个可以使用 foreach 循环进行迭代的实例对象)。在经过 chunk 方法进行分割,并最终在 each 方法中处理已经切割好的数据行。

在模型中使用 LazyCollection#

我们可以在自定义模型实例上使用查询对象的 “cursor” 方法来来返回一个 LazyCollection 实例,让我们检查一下 cursor 方法的实现。

public function cursor()
{
    if (is_null($this->columns)) {
        $this->columns = ['*'];
    }

    return new LazyCollection(function () {
        yield from $this->connection->cursor(
            $this->toSql(), $this->getBindings(), ! $this->useWritePdo
        );
    });
}

如您所见,返回了一个 LazyCollection 实例,我们依然能够像操作 Collection 实例一样来操作它。我们仍然只对数据库运行一个查询,不同的是一次只在内存中加载了一个用户模型实例。以下面为例。

$users = App\User::cursor()->filter(function ($user) {
    return $user->id > 500;
});

foreach ($users as $user) {
    echo $user->id;
}

如您所见,在此示例中,filter 方法只有在我们实际逐个遍历每个用户之前,才执行回调,从而大大减少了内存使用。

来源 zhuanlan.zhihu.com/p/90607027

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。