Красноречивые коллекции: каждый против foreach

Возможно, это не вопрос, относящийся к коллекциям Eloquent, но меня просто осенило во время работы с ними. Давайте просто предположим, что у нас есть объект $collection, который является экземпляром Illuminate\Support\Collection.

Теперь, если мы хотим перебрать его, каковы плюсы и минусы использования each() с замыканием по сравнению с обычным foreach. Есть ли такие?

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

против

$collection->each(function ($item) {
    // Some code
});

person Niklas Modess    schedule 03.09.2013    source источник
comment
Если вы еще не видели их, вы можете прочитать комментарии к принятому ответу. Это неправильно, и я предлагаю вам не принимать это.   -  person Mark Amery    schedule 07.06.2015
comment
поскольку я пробую это прямо сейчас в 5.1, метод each() кажется совершенно бесполезным, поскольку он все равно не меняет $item.   -  person S S    schedule 01.10.2016
comment
У вас есть return $item; в конце?   -  person Niklas Modess    schedule 02.10.2016


Ответы (5)


Оператор foreach следует использовать как своего рода способ циклического перемещения по коллекции и выполнения какой-либо логики над ней. Если то, что в нем, влияет на другие вещи в программе, используйте этот цикл.

Метод .each использует array_map для циклического просмотра каждого объекта в коллекции и выполнения закрытия для каждого из них. Затем он возвращает результирующий массив. Это ключ! .each следует использовать, если вы хотите каким-то образом изменить коллекцию. Может быть, это массив автомобилей, и вы хотите сделать модель прописной или строчной. Вы просто передадите замыкание методу .each, который берет объект и вызывает strtoupper() для модели каждого объекта Car. Затем он возвращает коллекцию с внесенными изменениями.

Мораль этой истории такова: используйте метод .each, чтобы каким-то образом изменить каждый элемент в массиве; используйте цикл foreach, чтобы использовать каждый объект для воздействия на какую-либо другую часть программы (с использованием некоторого логического оператора).

ОБНОВЛЕНИЕ (7 июня 2015 г.)

Как сказано так Красноречиво (видите, что я там сделал?) ниже, приведенный выше ответ немного отличается. Метод .each, использующий array_map, фактически никогда не использовал выходные данные вызова array_map. Таким образом, новый массив, созданный array_map, не будет сохранен на Collection. Чтобы изменить его, вам лучше использовать метод .map, который также существует для объекта Collection.

Использование оператора foreach для перебора каждого из них имеет немного больше смысла, потому что вы не сможете получить доступ к переменным вне замыкания, если не убедитесь, что используете оператор use, что мне кажется неудобным.

Реализация, когда приведенный выше ответ был первоначально написан, может быть найдено здесь.

.each в Laravel 5.1

Новый .each, о котором идет речь ниже, больше не использует array_map. Он просто перебирает каждый элемент в коллекции и вызывает переданный $callback, передавая ему элемент и его ключ в массиве. Функционально вроде работает одинаково. Я считаю, что использование цикла foreach имело бы больше смысла при чтении кода. Тем не менее, я вижу преимущества использования .each, потому что он позволяет вам объединять методы в цепочку, если вам это нравится. Это также позволяет вам вернуть false из обратного вызова, чтобы выйти из цикла раньше, если ваша бизнес-логика требует от вас возможности.

Для получения дополнительной информации о новой реализации посетите страницу исходный код.

person searsaw    schedule 03.09.2013
comment
Все это имело для меня смысл, и затем я использовал .each для набора массивов, чтобы преобразовать каждый массив в объект. Я мог сбросить переменную, которую возвращал в функции обратного вызова, это был объект. но потом.. еще массив. Я беспокоюсь, что я что-то неправильно понимаю - person Damon; 09.08.2014
comment
Похоже, вы не возвращаете объект. Вам нужно убедиться, что вы возвращаете приведенный объект из функции обратного вызова, чтобы он попал в массив, а не в массив, который я привел. - person searsaw; 10.08.2014
comment
Этот ответ неверен и основан на непонимании кода. Тот факт, что .each использует array_map(), не имеет значения, это просто деталь реализации. На самом деле код Laravel для .each() даже не возвращает возвращаемое значение array_map(), он просто игнорируется. И .each(), и foreach одинаково полезны (или бесполезны!) для изменения значений. Подробности смотрите в моем ответе. - person orrd; 06.06.2015
comment
В ответ на комментарий Деймона .map() разрешил вам это сделать. Возвращаемое значение закрытия .each() игнорируется и не может использоваться как способ замены элементов в коллекции (это часть того, почему ответ Searsaw неверен). - person orrd; 06.06.2015
comment
Этот ответ ужасно неверен. Объекты PHP изменяемы, и всякий раз, когда вы передаете их как переменные, вы фактически передаете ссылки на них; следовательно, вы можете изменить существующий объект из тела foreach, из обратного вызова array_map или вообще из любого кода. Это не разница между foreach и array_map. Вы предполагаете, что, возвращая что-то из обратного вызова, переданного в $collection->each(), вы можете изменить коллекцию. Это тоже неправда. Метод each использовался для вызова array_map (до апреля 2015 г.), но не использовал возвращаемое значение. - person Mark Amery; 07.06.2015
comment
Спасибо за обновление @searsaw! Работает как часы! - person Steven; 12.06.2015

В существующих ответах много запутанной дезинформации.

Краткий ответ

Короткий ответ: Нет большой разницы между использованием .each() и foreach для перебора коллекции Laravel. Оба метода дают одинаковый результат.

А если модифицировать предметы?

Изменяете ли вы элементы или нет, не имеет значения, используете ли вы .each() или foreach. Они оба позволяют (и не позволяют!) изменять элементы в коллекции в зависимости от того, о каком типе элементов идет речь.

  • Изменение элементов, если Коллекция содержит объекты. Если Коллекция представляет собой набор объектов PHP (например, Коллекция Eloquent), .each() или foreach позволяют изменять свойства объектов (например, $item->name = 'foo'). Это просто из-за того, что объекты PHP всегда ведут себя как ссылки. Если вы пытаетесь заменить весь объект другим объектом (менее распространенный сценарий), используйте вместо этого .map().
  • Изменение элементов, если коллекция содержит необъекты. Это встречается реже, но если ваша коллекция содержит необъекты, например строки, .each() не дает возможности изменить значения коллекции. Предметы. (Возвращаемое значение замыкания игнорируется.) Вместо этого используйте .map().

Итак... какой из них я должен использовать?

Если вас интересует производительность, я провел несколько тестов с большими коллекциями элементов Eloquent и набором строк. В обоих случаях использование foreach было быстрее, чем .each(). Но мы говорим о микросекундах. В большинстве реальных сценариев разница в скорости не будет существенной по сравнению со временем, которое требуется для доступа к базе данных и т. д.

В основном это сводится к вашим личным предпочтениям. Использование .each() удобно, потому что вы можете объединить несколько операций (например, .where(...).each(...)). Я склонен использовать оба в своем собственном коде, просто в зависимости от того, что кажется самым чистым для каждой ситуации.

person orrd    schedule 06.06.2015
comment
что тебе без разницы? простой тест дал примерно 1 секунду для 10000 итераций по 1000 элементам с -›каждым и ~0,4 для той же коллекции. то есть 2/5. если что-то объективно, так это голые цифры, а не предпочтение цепочки вызовов. - person iRaS; 24.10.2020

Вопреки тому, что говорят два других ответа, Collection::each() не меняет значения элементов в Коллекции, технически говоря. Он использует array_map(), но не сохраняет результат этого вызова.

Если вы хотите изменить каждый элемент в коллекции (например, привести их к объектам как Деймон в комментарии к принятому ответу), вам следует использовать Collection::map(). Это создаст новую коллекцию на основе результата базового вызова array_map().

person Jake S    schedule 27.05.2015
comment
Через несколько дней после того, как вы написали этот ответ, Illuminate\Support\Collection::each() больше даже не использует array_map. - person Mark Amery; 07.06.2015

Выгоднее использовать последний: each().

Вы можете связать условия и написать более понятный и выразительный код, например:

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

Это приближает вас к декларативному программированию, где вы говорите компьютеру, что делать, а не как делать.

Несколько полезных статей:

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

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

person Nik K    schedule 27.08.2017

Конструкция foreach() не позволяет вам изменить значение элемента массива, по которому выполняется итерация, если только вы не передадите массив по ссылке.

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

Красноречивый метод each() оборачивает функцию PHP array_map(), что позволяет это сделать.

Как и в предыдущем ответе, вы должны использовать foreach(), если у вас есть другая мотивация. Это связано с тем, что производительность foreach() намного выше, чем у array_map().

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

person Shannon    schedule 24.03.2015
comment
Как и в другом ответе, в котором упоминается то же самое, часть array_map() является неправильным пониманием кода Laravel и функциональности array_map(). Возвращаемое значение array_map() игнорируется при использовании .each(). Но вы можете использовать foreach в качестве ссылки, как вы упомянули, но вы не должны этого делать, если коллекция содержит объекты, такие как объекты Eloquent, потому что объекты в PHP уже действуют как ссылки. - person orrd; 06.06.2015