LazyCollection 用于处理大数据集防内存溢出,非懒加载关联;cursor() 返回原始数据、性能高,lazy() 创建完整模型实例、支持关系操作。

Lazy Collection 不是用来“懒加载关联”的,它是用来处理大数据集、避免内存爆掉的——别把它和 Eloquent 的 lazy loading 混为一谈。
cursor() 和 lazy() 到底该选哪个
两者都返回 LazyCollection,但底层行为完全不同:
-
cursor()绕过 Eloquent 模型实例化,直接从 PDO 返回stdClass或关联数组,无访问器、无事件、无类型转换,内存最低、速度最快;适合导出、统计、清洗字段等场景 -
lazy()仍会为每一行创建完整 Eloquent 模型实例,支持$user->posts、$user->save()、$user->getAttribute()等全部模型能力;适合需要复用模型逻辑的批量操作 - 如果你只写
User::lazy()->each(...)却没调用任何模型方法,纯属浪费——直接换cursor()
哪些链式操作会让 LazyCollection 失去“懒”特性
不是所有 Collection 方法都惰性执行。以下操作会提前耗尽生成器、一次性加载全部数据(哪怕只是临时):
-
count()、sum()、avg()、max()、min():必须遍历全量,但只存中间值,内存压力可控 -
toArray()、all()、values():彻底放弃惰性,把所有项转成数组,内存暴涨,慎用 -
sortBy()、groupBy()、chunk()(注意:这个chunk()是 Collection 方法,不是查询构建器的chunkById()):需随机访问或分组,会先收集全部再处理,等价于toArray()+ 普通 Collection - 安全的操作包括:
filter()、map()、skip()、take()、each()—— 它们保持流式,按需触发 fetch
load() 和 loadMissing() 在 LazyCollection 里根本不能用
这是最常被踩的坑:load() 只接受 Eloquent 模型实例或 Collection,不接受 LazyCollection。
-
User::lazy()->load('posts')会报错:Call to undefined method Illuminate\Support\LazyCollection::load() -
User::cursor()->loadMissing('posts')同样无效,因为cursor()返回的是原始数组,没有模型关系能力 - 真要预加载关联,得回到查询层:
User::with('posts')->cursor()是合法的,但注意它仍只返回原始数组($user['posts']不可用),想用模型关系就得用lazy()+with() - 如果已用
cursor()拿到用户 ID 列表,再补查关联,应手动构造Post::whereIn('user_id', $ids)->get(),而不是幻想load()能跨过惰性集合工作
从文件或缓存创建 LazyCollection 的正确姿势
不要试图把大数组 json_decode() 后直接传给 collect()——那会全量进内存。要用生成器 + LazyCollection::make():
$lines = LazyCollection::make(function () {
$handle = fopen(storage_path('app/logs/big.log'), 'r');
while (($line = fgets($handle)) !== false) {
yield $line;
}
fclose($handle);
});
$lines->skip(1)
->take(1000)
->filter(fn($line) => str_contains($line, 'ERROR'))
->each(fn($line) => Log::error($line));
- 关键点是
yield,不是return;生成器函数本身不执行,只有迭代时才逐行读取 - 若缓存里存的是 JSON 字符串,别
json_decode($json, true)后 collect,而应:LazyCollection::make(fn() => yield from json_decode($json, true)) - 注意
yield from仅在 PHP 7.4+ 支持;低版本需手动循环 +yield
真正难的不是语法,而是判断哪一步该用模型、哪一步该绕过模型、哪一步根本不能“懒”——比如你打算对十万用户调用 $user->sendWelcomeEmail(),那就别碰 cursor(),老实用 lazy();但要是只统计邮箱域名分布,cursor() + 原生数组操作才是正解。


















