Featured image of post Laravel LazyCollection 搭 Generator 為何延遲載入失效

Laravel LazyCollection 搭 Generator 為何延遲載入失效

直接把 Generator 傳給 LazyCollection 會被 iterator_to_array 一次全部展開,失去延遲載入效果,必須用 Closure 包一層才能正確運作。

以為會動的寫法

看文件知道 LazyCollection 搭 Generator 可以做到延遲載入,我就直接把 generator 丟進去,以為找到 $i > 5 之後迴圈就會停下來:

1
2
3
$this->assertEquals(6, LazyCollection::make($this->generator())->collapse()->first(function ($i) {
    return $i > 5;
}));

結果不是。迴圈跑完了全部 9 輪才停。

為什麼

翻了 LazyCollection 的原始碼才搞懂。建構子收到的如果不是 Closure,就會走到 getArrayableItems(),而 Generator 實作了 Traversable,所以會被 iterator_to_array() 一口氣全部展開:

1
2
3
4
5
6
7
8
protected function getArrayableItems($items)
{
    // ...
    } elseif ($items instanceof Traversable) {
        return iterator_to_array($items);
    }
    // ...
}

延遲載入的效果完全沒了。

Eager vs Lazy Generator 執行差異

正確寫法

用 Closure 包一層,讓 LazyCollection 拿到的是一個「能產生 Generator 的函式」,而不是已經開始跑的 Generator:

1
2
3
4
5
$this->assertEquals(6, LazyCollection::make(function () {
    return $this->generator();
})->collapse()->first(function ($i) {
    return $i > 5;
}));

這樣只會執行第一輪迴圈就停了,才是真正的延遲載入。

差別只在於傳進去的是 Generator 本身,還是產生 Generator 的 Closure。

參考資源