foreach php что такое
foreach
Конструкция foreach предоставляет простой способ перебора массивов. Foreach работает только с массивами и объектами, и будет генерировать ошибку при попытке использования с переменными других типов или неинициализированными переменными. Существует два вида синтаксиса:
Первый цикл перебирает массив, задаваемый с помощью array_expression. На каждой итерации значение текущего элемента присваивается переменной $value и внутренний указатель массива увеличивается на единицу (таким образом, на следующей итерации цикла работа будет происходить со следующим элементом).
Второй цикл будет дополнительно соотносить ключ текущего элемента с переменной $key на каждой итерации.
Когда оператор foreach начинает исполнение, внутренний указатель массива автоматически устанавливается на первый его элемент Это означает, что нет необходимости вызывать функцию reset() перед использованием цикла foreach.
Так как оператор foreach опирается на внутренний указатель массива, его изменение внутри цикла может привести к непредсказуемому поведению.
Для того, чтобы напрямую изменять элементы массива внутри цикла, переменной $value должен предшествовать знак &. В этом случае значение будет присвоено по ссылке.
Указатель на $value возможен, только если на перебираемый массив можно ссылаться (т.е. если он является переменной). Следующий код не будет работать:
Оператор foreach не поддерживает возможность подавления сообщений об ошибках с помощью префикса ‘@’.
Вы могли заметить, что следующие конструкции функционально идентичны:
Следующие конструкции также функционально идентичны:
Вот еще несколько примеров, демонстрирующие использование оператора:
/* Пример 1: только значение */
/* Пример 2: значение (для иллюстрации массив выводится в виде значения с ключом) */
$i = 0 ; /* только для пояснения */
/* Пример 3: ключ и значение */
/* Пример 4: многомерные массивы */
$a = array();
$a [ 0 ][ 0 ] = «a» ;
$a [ 0 ][ 1 ] = «b» ;
$a [ 1 ][ 0 ] = «y» ;
$a [ 1 ][ 1 ] = «z» ;
/* Пример 5: динамические массивы */
Распаковка вложенных массивов с помощью list()
В PHP 5.5 была добавлена возможность обхода массива массивов с распаковкой вложенного массива в переменные цикла, передав list() в качестве значения.
Результат выполнения данного примера:
Результат выполнения данного примера:
Изучаем цикл foreach PHP: два способа его использования
Цикл foreach PHP можно использовать следующим образом:
Как цикл foreach PHP работает с числовым массивом
Посмотреть демо-версию и код
Цикл foreach PHP — примеры с ключами и значениями массива
Посмотреть демо-версию и код
Цикл foreach PHP — примеры изменения значения элемента массива
Значение будет изменено. Чтобы вам было понятнее, рассмотрим следующий пример.
В этом примере мы создали числовой массив из пяти элементов. После этого использовали цикл foreach для отображения значений элементов.
Посмотреть демо-версию и код
Для чего используется цикл foreach PHP?
Цикл foreach PHP используется для работы с массивом. Он перебирает каждый его элемент.
Обратите внимание, что можно использовать цикл foreach с массивом или только с объектом.
Как применять цикл foreach PHP
В PHP существует два способа использовать цикл foreach PHP. Оба описаны ниже.
Это подходит для ассоциативных массивов, в которых используются пары ключ / значение.
Если вы работаете с числовыми массивами, то можно использовать первый метод, в котором не нужны ключи элементов.
Пожалуйста, оставляйте свои мнения по текущей теме статьи. Мы крайне благодарны вам за ваши комментарии, подписки, отклики, дизлайки, лайки!
Работа с циклами foreach, for, while в PHP
Учебник PHP
Практика
Важное
Регулярки
Работа с htaccess
Файлы, папки
Сессии и куки
Работа с БД
Практика по работе с БД в PHP
Перед чтением см. новые уроки раздела «Важное», которые появились выше.
Практика
Движок PHP
Продвинутые БД
Аутентификация
Практика
ООП и MVC
Абстрактные классы и интерфейсы
Трейты
ООП Магия
Практика
Практика: классы как набор методов
Циклы используются для того, чтобы некоторый участок кода выполнился несколько раз подряд.
Делается это с помощью циклов.
Есть три вида циклов: foreach, while и for. Давайте разберемся, как с ними работать и чем они отличаются друг от друга.
Цикл foreach
Цикл foreach используется для прохождения по всем элементам массива.
После команды foreach() должны идти фигурные скобки <>. Код, который лежит в этих скобках, называется телом цикла.
Этот код будет выполняться столько раз, сколько проходов сделает цикл. А он сделает столько проходов, сколько элементов у нашего массива.
Итак, синтаксис цикла foreach выглядит так:
Давайте решим следующую задачу: пусть дан массив $arr с пятью элементами, выведем столбец этих элементов с помощью цикла foreach.
Будем при каждом проходе цикла выводить на экран (с помощью echo) текущий элемент массива (тот, что лежит в переменной $elem), и ставить после него тег br, чтобы получался столбец элементов, а не строка:
Если вам нужны только значения ассоциативного массива и не нужны ключи, то $ключ=> можно не писать:
Цикл foreach имеет альтернативный синтаксис:
Как и в случае с конструкцией if-else, мы можем разорвать скобки PHP внутри цикла, далее написать что-то на HTML и опять открыть скобки PHP – в этом случае HTML код внутри цикла повторится столько раз, сколько проходов сделает цикл (в случае foreach – это количество элементов массива):
Цикл while
Цикл while будет выполняться до тех пор, пока верно (истинно) выражение, переданное ему параметром. Смотрите синтаксис:
Давайте выведем с помощью цикла while столбец цифр от одного до пяти.
Для этого введем переменную $i, которую будем использовать для того, чтобы остановить наш цикл.
Как мы это сделаем: перед циклом поставим ей значение 1, а внутри цикла будем при каждом проходе цикла увеличивать ее на единицу. Сначала она будет 1, потом 2, потом 3 и так далее.
Цикл for
Цикл for является альтернативой while. Он более сложен для понимания, но чаще всего его любят больше, чем while, за то, что он занимает меньше строчек.
Его синтаксис выглядит так:
Циклы в PHP
Цикл — это конструкция языка, которая позволяет выполнить блок кода больше одного раза.
Мы привыкли, что наши сценарии выполняются линейно: сверху вниз, строчка за строчкой, инструкция за инструкцией. Но что делать, если надо повторить какую-нибудь инструкцию несколько раз?
Например, как вывести на экран натуральные числа от 1 до 9? Есть очевидный способ:
Но, во первых, такой способ заставляет писать много кода. Во-вторых, что если требуется вывести последовательность из миллиона чисел? И, наконец, бывают ситуации, когда заранее неизвестно сколько раз нужно выполнить определённую инструкцию.
Использование циклов значительно упрощает и укорачивает код. Также циклы незаменимы в ситуациях, когда заранее неизвестно сколько раз должен выполниться блок кода. Такое число может зависеть от множества условий и вычисляться в момент выполнения сценария.
Так выглядит цикл в PHP:
Если выражение из условия цикла возвращает истину, то выполнение сразу перейдёт к блоку «тело цикла», если же оно вернёт ложь — то тело цикла исполнено не будет и сценарий продолжит выполняться как обычно, со следующей строки после цикла.
Выходит, что циклы имеют такое название, потому что как бы «зацикливают» обычное, линейное исполнение на своем блоке кода и не дают сценарию выполняться дальше, пока условие цикла будет истинным.
Важно понимать последовательность, в которой исполняется код при использовании циклов.
Каждая последовательность из шагов 2-4, то есть очередное выполнение блока кода в теле цикла — называется итерацией.
Количество итераций должно быть конечным, то есть, как правило, бесконечное выполнение одного блока кода не входит в наши планы.
А значит необходимо заранее предусмотреть ситуацию, когда истинное условие станет ложным.
Теперь вернёмся к задаче по выводу на экран всех натуральных чисел:
Данный цикл в своём теле содержит две инструкции. Первая выводит на экран цифру из переменной. Вторая инструкция увеличивает значение переменной на единицу. Теперь вопрос: сколько раз будет исполнен такой цикл?
Циклы и массивы
Чаще всего циклы используются для работы с массивами. А конкретнее — для перечисления всех элементов массива и выполнения какого-нибудь действия с каждым из этих элементов.
Умение использовать циклы и массивы совместно, сразу позволит тебе выполнять множество интересных и разнообразных задач!
Чуть раньше мы уже научились работать с массивами. Например, мы можем показать все значения массива, обратившись к каждому из элементов по его индексу. Но это чрезвычайно утомительно: обращаться к каждому из элементов массива по очереди, когда мы просто хотим показать всего его значения. Циклы избавят от этой рутины!
С помощью циклов можно показать содержимое любого массива и это потребует всего несколько строк кода!
Перепишем пример с выводом списка любимых сериалов, но теперь задействовав цикл:
Потренируйтесь использовать циклы с массивами, пройдя этот тренажёр.
foreach — специальный цикл для массивов
Дело в том, что цикл while слишком универсален. А платой за эту универсальность всегда будет более сложный и обьемный код. Приходится придумывать условие, следить, чтобы оно не было бесконечным. А в теле цикла обязательно надо не забыть увеличивать переменную-счётчик. И всё это нужно для простого перебора элементов массива. Неужели нельзя сделать проще?
К счастью, foreach решает все эти проблемы. Вот его возможности:
Цикл foreach становится совершенно незаменим, когда дело доходит до итерации по ассоциативным массивам. Возьмём такой пример: у нас есть данные пользователя, которые хранятся в ассоциативном массиве. На сайте понадобилось сделать страницу с информацией об этом юзере. Задача состоит в том, чтобы показать на странице все данные, которые известны об этом человеке. Выглядеть это должно так:
Оригинальный массив, который надо показать в таком виде:
Код сценария, который обойдёт массив и покажет всё его содержимое займёт всего 4 строчки:
Препарируем PHP. Как устроены while, foreach, array_walk и некоторые другие страшные слова
Дело было вечером, делать было нечего. Самое время устроить небольшой разбор того, чем изнутри отличаются некоторые способы перебора массивов в PHP.
Исходники от master ветки (это сейчас 7.4 с вкраплениями 8)
Генератор опкодов от php 7.3.0.
Замеры производились на 7.3.6.
Дисклеймер для зануд: упоминание пары наносекунд и тактов процессора – это такой полемический приём под названием «гипербола».
Может быть, на самом деле, там десятки или сотни наносекунд и тысячи тактов, но это всё равно настолько малые величины, что необходимость экономить на них говорит о том, что что-то в вашем коде не так.
Этап компиляции
for, foreach, do и while являются ключевыми словами языка, тогда как функции array_* – это функции стандартной библиотеки. Следовательно, при прочих равных, по первым парсер отработает на пару наносекунд быстрее.
Парсер
До токена statement путь будет одинаков для всех
Циклы определены на уровне statement:
Отличие for_exprs от просто expr только в том, что для первого допустима запись нескольких expr через запятую.
foreach_variable – это конструкция, которая помимо просто variable, также отслеживает распаковку с помощью list или [].
for_statement, foreach_statement, while_statement отличаются от стандартного statement тем, что в них добавлена возможность разбора альтернативного синтаксиса:
Вызов функций закопан гораздо глубже:
callable_variable, хм… Забавно, не правда ли? 🙂
Перейдём к опкодам
Для примера давайте возьмём простой перебор индексированного массива с печатью каждого ключа и значения. Понятно, что использование for, while и do для такой задачи не оправдано, но у нас цель просто показать внутреннее устройство.
foreach
Что тут происходит:
5, а значение в !1. Либо, если достигнут конец массива, переходит к инструкции 7.
Инструкции 3-6 не особо интересны. Тут происходит вывод и возврат к FE_FETCH_R.
FE_FREE: уничтожает итератор.
На самом деле это частный случай while
На самом деле это частный случай if+goto
Опкоды для всех трёх случаев будут практически идентичны. Разве что в случае с if, JMPNZ поменяется на пару JMPZ+JMP из-за входа в тело if‘а.
Для цикла do опкоды будут незначительно отличаться из-за его постпроверочной природы.
А можно ещё и так поитерировать
Этот вариант хорош тем, что подходит для итерации по массиву с любыми ключами, а не только с монотонно возрастающими целыми числами.
Функции reset, next и key довольно легковесные, но накладные расходы на их вызов всё же есть. И, как мы увидим дальше, расходы эти велики.
Хотя такой подход очень сильно напоминает принцип работы foreach, между ними есть два принципиальных отличия.
1) Тогда как reset, next и key (и current тоже) работают напрямую с внутренним указателем массива, foreach использует собственный итератор и не меняет состояние внутреннего указателя.
2) При использовании foreach для итерации по значению, что бы вы не делали с массивом внутри цикла, проитерирован будет именно первоначальный набор данных
Что будет при итерации по ссылке, можно почитать в этом RFC. Там всё не очень просто.
array_walk с анонимной функцией
Так как используется пользовательская функция, то будет дополнительный набор опкодов.
Функция
Основной код
Поскольку array_walk, как и остальные функции стандартной библиотеки, является интринсиком, то в скомпилированных опкодах механизм итерации отсутствует.
INIT_FCALL: инициализируем вызов array_walk
SEND_REF: кладём ссылку на массив на стек вызова
DECLARE_LAMBDA_FUNCTION: объявляем анонимную функцию
SEND_VAL: кладём анонимную функцию на стек вызова
DO_ICALL: запускаем array_walk на выполнение
Далее там происходит магия с вызовом нашей лямбды для каждого элемента массива.
array_walk с использованием предопределённой функции
Не сильно отличается от вызова с анонимной, разве только чуть меньше накладных расходов на создание лямбды во время исполнения.
Выводы банальны. foreach заточен под итерирование массивов, тогда как остальные циклы – просто обёртка над if+goto.
Функции же стандартной библиотеки работают по принципу чёрного ящика.
Погружаемся чуть глубже
Для начала рассмотрим случай с for и его опкодом FETCH_DIM_R, использующимся для извлечения значения по ключу. Извлечение элемента идёт через поиск в хеш-таблице (ZEND_HASH_INDEX_FIND). В нашем случае извлечение идёт из упакованного массива (ключи – непрерывная числовая последовательность) – это довольно лёгкая и быстрая операция. Для неупакованных массивов она будет чуть подороже.
Теперь foreach (FE_FETCH_R). Тут все банально:
Если совсем упрощённо, то (псевдокод):
На самом деле внутри всё сложнее, но суть одна – идёт довольно быстрый перебор хеш-таблицы без участия виртуальной машины PHP (не учитывая вызова пользовательской функции).
Ну и немного замеров
А то ведь какая же статья без замеров (по памяти получилось настолько одинаково, что убрал её измерение).
В качестве массива, по традиции, возьмём zend_vm_execute.h на 70.108 строк.
Каждое измерение запускал раз по 10, выбирая наиболее часто встречающееся по первым 4-м цифрам.
Подведём итоги
Анализируя результаты, не забываем учитывать, что они получены на 10 проходах по массиву из 70 тысяч элементов.
Абсолютным антигероем оказалась «эмуляция» foreach с помощью next/key. Не делайте так без крайней на то необходимости.
array_walk с лямбдой дышит ему в спину, но тут есть нюанс. Грядущий JIT может кардинально изменить ситуацию. А может и не изменить. Интересно будет посмотреть.
array_walk с использованием готовой функции – крепкий середнячок.
Так как при итерации по ссылке foreach работает несколько иначе (использует опкод FE_FETCH_RW вместо FE_FETCH_R), то сделал для него отдельный замер. Он действительно чуть-чуть быстрее получился.
Как оказалось, создание лямбды на лету – не самая дешёвая операция. Казалось бы, создаётся она всего 10 раз. Надо будет поизучать.
Все остальные способы показали примерно одинаковые результаты, с очень незначительным разрывом.
Спасибо за внимание!
Если есть предложения, что ещё можно «поковырять» – пишите в комментариях. Я пока подумываю о лямбдах – уж очень странна такая просадка производительности.
UPD
Добавил замер для array_walk со статической лямбдой. Разницы не видно.