Raw Generator in LazyCollection::make() is not lazy #33297
-
Description:When using Lazy Collections, one can use the following to read a large file without loading it all in memory: use Illuminate\Support\LazyCollection;
LazyCollection::make(function () {
$handle = fopen('file.txt', 'r');
while ($line = fgets($handle)) yield $line;
})->map(...); However, if you have a raw Generator (the result of calling a Generator function), then the LazyCollection will not handle it lazy anymore. The following will crash on out of memory when the file is too large: use Illuminate\Support\LazyCollection;
class FileReader
{
public function read($file)
{
$handle = fopen($file, 'r');
while ($line = fgets($handle)) yield $line;
}
}
$reader = new FileReader();
LazyCollection::make($reader->read('file.txt'))->map(...); The solution is to change it to the following: LazyCollection::make(fn() => yield from $reader->read('file.txt'))->map(...); It cost me some time to figure out this bug, and it surprised me: I think Laravel can handle this for me? How it works
framework/src/Illuminate/Support/LazyCollection.php Lines 29 to 39 in a00484a The generator falls into the case that checks for framework/src/Illuminate/Support/Traits/EnumeratesValues.php Lines 870 to 887 in a00484a A solution could be to change the constructor of public function __construct($source = null)
{
if ($source instanceof Closure || $source instanceof self) {
$this->source = $source;
} elseif ($source instanceof Generator) {
$this->source = function () use ($source) {
yield from $source;
};
} elseif (is_null($source)) {
$this->source = static::empty();
} else {
$this->source = $this->getArrayableItems($source);
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
Heya. This sounds like something more for discussions so I'm moving it there. |
Beta Was this translation helpful? Give feedback.
-
Also pinging @JosephSilber since he probably has some insights here. |
Beta Was this translation helpful? Give feedback.
-
The documentation does not say nor suggest that you can pass a But this keeps on coming up every now and then, so maybe we should document it properly somewhere.
I don't think it can, since iterators (AKA With your proposed solution: public function __construct($source = null)
{
if ($source instanceof Generator) {
$this->source = function () use ($source) {
yield from $source;
};
}
// other options omitted for brevity...
} ...this code will break: $collection->first() == $collection->first(); // false :( |
Beta Was this translation helpful? Give feedback.
The documentation does not say nor suggest that you can pass a
Generator
to theLazyCollection
'smake
method.But this keeps on coming up every now and then, so maybe we should document it properly somewhere.
I don't think it can, since iterators (AKA
Generator
s) aren't rewindable.With your proposed solution:
...this code will break: