-
Notifications
You must be signed in to change notification settings - Fork 11k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[8.x] Convert EloquentCollection to base if not solely filled with models #32677
[8.x] Convert EloquentCollection to base if not solely filled with models #32677
Conversation
Some collection methods, may introduce non-model items into an EloquentCollection. This was previously only checked for `map()`. This PR extends this check to include other such methods. Another group of methods can be known ahead of time to return a base Collection, in those cases the check is omitted, the collection is converted `toBase()` and the call is passed along. This achieves a few things: - Prevent bugs by having more accurate return type hints - Ensure correct behaviour for code that has special treatment for EloquentCollections - Aid static analysis tools
There is another group of methods that might taint an EloquentCollection by adding non-model elements: While it would certainly be possible to return a new base collection instead, not mutating the original and not returning the same instance would be quite a big breaking change, so i decided against it. If we wan't to go further in this direction, we might have to change those methods to throw if non-models are added in. That would be quite a drastic change, considering the value of strongly typed collections in a language without generics is pretty limited anyways. I think this current approach is a good middleground, shouldn't break existing code, but prevent or fix lingering bugs. |
*/ | ||
protected static function maybeToBase($maybeEloquent) | ||
{ | ||
return $maybeEloquent->contains(function ($item) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think EloquentCollections assume all contained models to be the same class, so we might change this check to the following:
$model = $maybeEloquent->first() ?? Model::class;
foreach($maybeEloquent as $maybeModel) {
if(! $maybeModel instanceof $model) {
return $maybeEloquent->toBase();
}
}
return $maybeEloquent;
What do you think?
Ping @JosephSilber |
I'm not a fan, and don't think having added it to
I believe we should only return a base collection when we are the ones changing the values to non-models. In any other case, let the user call |
@JosephSilber i agree with the point you make. As long as PHP lacks generics, we won't be able to fully guarantee the integrity of the items in an EloquentCollection. The issues you outlined in 2. are especially striking. Safety is great and all, but we certainly have to balance it with performance considerations. My intent with this PR is not necessarily to push in the direction of adding more validation, but rather to have consistency. I don't see a reason why In cases where we can know ahead of time the result definitely won't be a collection that holds models, calling Nit: There might be rare cases where that is not desirable, e.g.: Post::all()
->groupBy('some_attribute')
->filter(function ($group) { return $group->count() > 3; })
->flatten()
->load('someRelation'); |
Will hold off on this for now. |
@taylorotwell I would even go a step further, and propose that having added that to Consider the following piece of code: $inactiveUsernames = User::where('active', false)->get()->map->name;
// Now try using the $inactiveUsernames collection elsewhere What's the type of
This is not only confusing, but can lead to subtle bugs that are extremely tricky to debug. I vote that we revert Let users call |
@JosephSilber Agreed. What is your take on the implicit I am starting to think we are better off to stick with an We might have to fix methods such as |
Some collection methods may introduce non-model items
into an EloquentCollection. This was previously only checked for
map()
.This PR extends this check to include other such methods.
Another group of methods can be known ahead of time to return a base Collection, in
those cases the check is omitted, the collection is converted
toBase()
and the call is passed along.
This achieves a few things: