Eloquent collections: each vs foreach
Asked Answered
N

7

66

Might not be a question specific to Eloquent collections, but it just hit me while working with them. Let's just assume we have a $collection object which is an instance of Illuminate\Support\Collection.

Now if we want to iterate over it, what are the pros and cons of using each() with a closure versus a regular foreach. Are there any?

foreach ($collection as $item) {
    // Some code
}

versus

$collection->each(function ($item) {
    // Some code
});
Nessy answered 3/9, 2013 at 12:52 Comment(3)
If you haven't already seen them, you may want to read the comments on the accepted answer. It is wrong, and I suggest you unaccept it.Ss
as I'm trying that out just now in 5.1 each() method seems totally useless since it doesn't change $item anyhow.Could
Do you have a return $item; at the end?Nessy
M
63

A foreach statement should be used as a sort of a way to cycle through a collection and perform some sort of logic on it. If what is in it effects other things in the program, then use this loop.

The .each method uses array_map to cycle through each of the objects in the collection and perform a closure on each one. It then returns the resulting array. That is the key! .each should be used if you want to change the collection in some way. Maybe it's an array of cars and you want to make the model upper case or lower case. You would just pass a closure to the .each method that takes the object and calls strtoupper() on the model of each Car object. It then returns the collection with the changes that have been made.

Morale of the story is this: use the .each method to change each item in the array in some way; use the foreach loop to use each object to affect some other part of the program (using some logic statement).

UPDATE (June 7, 2015)

As stated so Eloquently (see what I did there?) below, the above answer is slightly off. The .each method using array_map never actually used the output from the array_map call. So, the new array created by array_map would not be saved on the Collection. To change it, you're better off using the .map method, which also exists on a Collection object.

Using a foreach statement to iterate over each of them makes a bit more sense because you won't be able to access variables outside the Closure unless you make sure to use a use statement, which seems awkward to me.

The implementation when the above answer was originally written can be found here.

.each in Laravel 5.1

The new .each that they are talking about below no longer uses array_map. It simply iterates through each item in the collection and calls the passed in $callback, passing it the item and its key in the array. Functionally, it seems to work the same. I believe using a foreach loop would make more sense when reading the code. However, I see the benefits of using .each because it allows you to chain methods together if that tickles your fancy. It also allows you to return false from the callback to leave the loop early if your business logic demands you to be able to.

For more info on the new implementation, check out the source code.

Merril answered 3/9, 2013 at 16:26 Comment(5)
This all made sense to me and then I used .each on a collection of arrays to cast each array as an object. I could dump the variable I was returning in the callback function, it was an object. but afterward.. still an array. I am worried I'm misunderstanding somethingGoglet
It sounds like you aren't returning the object. You need to make sure you are returning the casted object from the callback function so that it will be out into the array and not the I casted array.Merril
This answer is incorrect and is based on a misunderstanding of the code. The fact that .each uses array_map() is irrelevant, that's just an implementation detail. In fact the Laravel code for .each() doesn't even return the return value of array_map(), it's just ignored. Both .each() and foreach are equally useful (or not useful!) for modifying the values. See my answer for details.Nork
In response to Damon's comment, .map() would allow you to do that. The return value of the .each() closure is ignored and can not be used as a way to replace items in a collection (that's part of why searsaw's answer is incorrect).Nork
This answer is horribly wrong. PHP objects are mutable, and whenever you pass them as variables you actually pass around references to them; hence you can mutate an existing object from within the body of a foreach, from within an array_map callback, or indeed from any code at all. That isn't a difference between foreach and array_map. You suggest that by returning something from the callback passed to $collection->each() you can modify the collection. That isn't true either. The each method used to call array_map (prior to April 2015), but it didn't use the return value.Ss
N
21

There is a lot of confusing misinformation in the existing answers.

The Short Answer

The short answer is: There is no major difference between using .each() vs. foreach to iterate over a Laravel collection. Both techniques achieve the same result.

What about modifying items?

Whether or not you're modifying items is irrelevant to whether you use .each() vs. foreach. They both do (and don't!) allow you to modify items in the collection depending on what type of items we're talking about.

  • Modifying items if the Collection contains objects: If the Collection is a set of PHP objects (such as an Eloquent Collection), either .each() or foreach allow you to modify properties of the objects (such as $item->name = 'foo'). That's simply because of how PHP objects always act like references. If you're trying to replace the entire object with a different object (a less common scenario), use .map() instead.
  • Modifying items if the Collection contains non-objects: This is less common, but if your Collection contains non-objects, such as strings, .each() doesn't give you a way to modify the values of the collection items. (The return value of the closure is ignored.) Use .map() instead.

So... which one should I use?

In case you're wondering about performance, I did several tests with large collections of both Eloquent items and a collection of strings. In both cases, using foreach was faster than .each(). But we're talking about microseconds. In most real-life scenarios the speed difference wouldn't be significant compared to the time it takes to access the database, etc.

It mostly comes down to your personal preference. Using .each() is nice because you can chain several operations together (for example .where(...).each(...)). I tend to use both in my own code just depending on what seems the cleanest for each situation.

Nork answered 6/6, 2015 at 19:15 Comment(1)
what is no difference for you? a simple test resulted in ~1 second for 10000 iterations over a 1000 item with ->each and ~0.4 for the same collection. that is 2/5. if anything is objective it is these bare numbers - not the preference of chaining calls.Tailored
P
4

Contrary to what the two other answers say, Collection::each() does not change the values of the items in the Collection, technically speaking. It does use array_map(), but it doesn't store the result of that call.

If you want to modify each item in a collection (such as to cast them to objects as Damon in a comment to the crrently accepted answer), then you should use Collection::map(). This will create a new Collection based on the result of the underlying call to array_map().

Plectognath answered 27/5, 2015 at 13:43 Comment(1)
As of a few days after you wrote this answer, Illuminate\Support\Collection::each() doesn't even use array_map any more.Ss
H
2

It is more beneficial to use the latter: each().

You can chain conditions and write clearer more expressive code, eg:

$example->each()->map()->filter();

This takes you closer to Declarative Programming where you tell the computer what to accomplish instead of how to accomplish.

Some useful articles:

https://martinfowler.com/articles/collection-pipeline/

https://adamwathan.me/refactoring-to-collections/

Hussey answered 27/8, 2017 at 6:39 Comment(1)
you said "chain conditions", but these are not conditions, these functions.Mirella
M
1

->each() is the same as foreach(...) but worse.

The main difference here is Variable scope. In classical foreach you can easily operate variables declared before the foreach. If you are dealing with closure, you would need to inheriting variables from the parent scope with use. This feature is confusing, because when you use it, your function becomes scope dependant...

You can chain ->each() with other functions, but since it does not change the values in the collection, it has very limited use case. And because it is "rare to use", it is not always easily recognizable by developers when they read your code.

Not many people will read your code, cherish those who will.

Mirella answered 1/10, 2021 at 5:38 Comment(0)
U
0

My personal concern is about speed (performance) especially when dealing with very large data.

So, I did a test, the code is available here:

Note: I used Laravel 11 and php 8.3.

The result:

with 10k integers

Time taken by each(): 0.058869123458862 seconds
Time taken by foreach loop: 0.016932010650635 seconds
with 2M integers

Time taken by each(): 11.714524030685 seconds
Time taken by foreach loop: 3.5777778625488 seconds

I will say that's a VERY large difference.

Uncommon answered 10/5 at 10:10 Comment(0)
A
-3

The foreach() construct does not allow you to change the value of the array item being iterated over, unless you pass the array by reference.

foreach ($array as &$value) $value *= $value;

The each() eloquent method wraps the PHP array_map() function, which allows this.

Like the previous answer states, you should use foreach() if you have any other motivation. This is because the performance of foreach() is much better than array_map().

http://willem.stuursma.name/2010/11/22/a-detailed-look-into-array_map-and-foreach/

Astrosphere answered 24/3, 2015 at 15:51 Comment(1)
As with the other answer that mentions the same thing, the array_map() part is a misunderstanding of the Laravel code and the functionality of array_map(). The array_map() return value is ignored when you use .each(). But you can use foreach as a reference as you mention, but you shouldn't do that if the Collection contains objects, such as Eloquent objects, because objects in PHP already act like references.Nork

© 2022 - 2024 — McMap. All rights reserved.