Lazy collections no longer dropped by collapse(), collapseWithKeys() and flatten()
Contributed by ahmadreza-fatemikia
Part of the framework v13.35.0 release
Pull request #61811 is a bug fix for the collections layer that closes a silent data-loss hole. Up to and including v13.34.0, Arr::collapse(), Arr::flatten() and Collection::collapseWithKeys() only unwrapped nested items that were instances of Illuminate\Support\Collection. Any other Enumerable — most commonly a LazyCollection — was silently dropped by collapse() and collapseWithKeys(), and left un-flattened by flatten(). This release widens those checks to the Enumerable interface so eager and lazy collections finally behave identically, and it adds tests to lock that in.
What changed
Three internal type checks changed from instanceof Collection to instanceof Enumerable:
Arr::collapse()— nested enumerables are now expanded with->all()before being merged into the result.Arr::flatten()— nested enumerables are now unwrapped before the depth-based flattening runs.Collection::collapseWithKeys()— nested enumerables are now merged in instead of being skipped.
Because both Illuminate\Support\Collection and Illuminate\Support\LazyCollection implement the Enumerable interface, the fix covers the two core classes plus any custom Enumerable implementations you may have. The Collection::collapse() and Collection::flatten() methods flow through these same code paths, and the new tests are run against both Collection and LazyCollection as the outer collection via the shared data provider.
The bug, in code
Here is the PR's own example, which is the clearest way to see the difference:
use Illuminate\Support\LazyCollection;
$items = collect([
collect([1, 2]),
LazyCollection::make([3, 4]),
[5],
]);
$items->collapse()->all();
// Before: [1, 2, 5]
// After: [1, 2, 3, 4, 5]
$items->flatten()->all();
// Before: [1, 2, LazyCollection, 5]
// After: [1, 2, 3, 4, 5]
Before the fix, items 3 and 4 simply vanished from the collapsed result, and flatten() left the LazyCollection object itself sitting in the output instead of its values. The same applies to keyed merging:
$data = collect([
collect(['a' => 1, 'b' => 2]),
LazyCollection::make(['b' => 3, 'c' => 4]),
]);
$data->collapseWithKeys()->all();
// Before: ['a' => 1, 'b' => 2] // lazy entries silently dropped
// After: ['a' => 1, 'b' => 3, 'c' => 4] // later keys win
Why it matters
Silent data loss is the worst kind of bug: no exception, no warning, just fewer items than you put in. The inconsistency made it worse — LazyCollection::collapse(), collapseWithKeys() and flatten() already handled any Enumerable, so identical data produced different results depending on whether the outer collection happened to be eager or lazy. In real applications, lazy collections appear easily, whether from Model::lazy(), cursor(), large file or CSV generators, or API streams, so mixing them with eager collections is a very plausible scenario.
Upgrade impact
If you are upgrading from v13.34.0:
- Most apps need to do nothing. The method signatures are unchanged, there is no configuration, and results now simply include the items you expected all along.
- If you deliberately relied on lazy collections being skipped, make that explicit rather than accidental:
$onlyEager = $items
->filter(fn ($value) => ! $value instanceof LazyCollection)
->collapse();
- Mind the enumeration cost.
collapse()andflatten()now call->all()on nested enumerables, which consumes a lazy source at call time. If you nested a huge or infinite generator, it will now be enumerated where it previously was skipped or left as an object. If you need laziness end to end, keep the outer collection lazy —LazyCollection's own methods already behaved this way.
Tests included
The existing tests only nested collections of the same class as the outer collection, which is exactly how this gap survived. New tests in tests/Support/SupportArrTest.php and tests/Support/SupportCollectionTest.php cover collapse(), flatten() and collapseWithKeys() with mixed Collection/LazyCollection nesting, plus direct coverage of Arr::collapse() and Arr::flatten(). The data provider runs each case against both collection classes, guaranteeing identical behavior going forward.
Takeaways
Arr::collapse(),Arr::flatten()andCollection::collapseWithKeys()now accept anyEnumerable, not justCollectioninstances.- Lazy collections nested inside eager collections are no longer dropped from
collapse()/collapseWithKeys()or left un-flattened byflatten(). - Eager and lazy outer collections now produce the same results for the same data.
- Nested lazy sources are enumerated at call time — relevant if you work with large or infinite generators.
- Pure bug fix: no API, signature or configuration changes; upgrading from v13.34.0 is a drop-in improvement for anyone mixing collection types.