Перейти к содержимому
Теория · Циклы · урок 4 из 6

Цикл foreach

До сих пор бегун считал круги сам: счётчик $i, условие и шаг. Но часто нужно другое — пройти по готовому набору значений: по ценам товаров в корзине, по оценкам за тест. Для этого в PHP есть цикл foreach.

Представьте ряд коробок вдоль беговой дорожки. В каждой лежит одна вещь — например, цена товара. Бегун идёт вдоль ряда от первой коробки к последней и у каждой получает ксерокопию того, что лежит внутри. Сколько коробок, столько и витков.

Вот сумма цен в корзине:

PHP
<?php

$prices = [10, 20, 30];
$total = 0;
foreach ($prices as $price) {
    $total += $price;
}
echo $total, "\n"; // 60

Давайте разберём по строкам:

  1. $prices = [10, 20, 30]; кладёт три цены в одну переменную — это ряд из трёх коробок.
  2. $total = 0; заводит сумму.
  3. foreach ($prices as $price) проходит по ряду: на каждом витке в $price попадает копия очередной цены.
  4. $total += $price; прибавляет эту цену к сумме.
  5. После трёх витков echo печатает 60.

Счётчика, условия и шага здесь нет: foreach сам знает, где ряд начинается и где кончается. Если ряд пустой, то есть [], тело не выполнится ни разу.

Список в квадратных скобках

Запись [10, 20, 30] создаёт список — набор значений, которые хранятся по порядку. Каждое значение в нём называют элементом.

Элементы пронумерованы с нуля: у первого номер 0, у второго — 1, у третьего — 2. Номер элемента называют индексом. В PHP список — частный случай массива, то есть набора значений в одной переменной.

Элемент читают по индексу в квадратных скобках, а функция count возвращает число элементов:

PHP
echo $prices[0], ' ', $prices[2], "\n"; // 10 30
echo count($prices), "\n"; // 3

Обратите внимание: элементов три, а последний индекс — 2. Индекс последнего элемента всегда на единицу меньше, чем count.

Тот же обход можно записать и через for с индексом: for ($i = 0; $i < count($prices); $i++) и $prices[$i] в теле. foreach короче и не даёт ошибиться в границах: не нужно помнить, что индексы идут от 0 до count минус один.

Переменная цикла — копия

На каждом витке foreach кладёт в $price копию элемента, а не сам элемент. Поэтому присваивание переменной $price список не меняет:

PHP
foreach ($prices as $price) {
    $price = $price * 2;
}
echo $prices[0], ' ', $prices[1], "\n"; // 10 20
echo $price, "\n"; // 60

Разберём, что произошло:

  1. На каждом витке $price получает копию: 10, потом 20, потом 30.
  2. $price = $price * 2; удваивает копию, а коробка в ряду остаётся прежней.
  3. После цикла в списке по-прежнему 10, 20 и 30.
  4. $price живёт и после цикла, как $i в for: в ней последняя удвоенная копия, 60.

На собеседовании любят спрашивать, меняет ли foreach элементы, если присвоить что-то переменной цикла. Ответ: нет, переменная цикла — копия, и список остаётся прежним.

Картинка с ксерокопией верна, пока в цикле нет знака &. Запись foreach ($prices as &$price) отдаёт в цикл саму коробку, а не копию, и тогда присваивание меняет элемент. После такого цикла $price остаётся привязанной к последнему элементу списка; в этом уроке запись не нужна.

Частая ошибка

По строке пытаются пройти циклом foreach, чтобы получить её буквы:

PHP
$word = 'php';
foreach ($word as $letter) { // ✗
    echo $letter, "\n";
}
TEXT
Warning: foreach() argument must be of type array|object, string given

Строка — не список, и foreach по ней не ходит. В сообщении сказано, что цикл принимает array или object, то есть массив или объект, а получил string.

Это предупреждение, а не остановка: тело не выполняется ни разу, а код после цикла работает дальше. Чтобы пройти по буквам, их кладут в список: foreach (['p', 'h', 'p'] as $letter).

Главное

  • [10, 20, 30] — список: элементы хранятся по порядку, индексы начинаются с 0, а count возвращает число элементов.
  • foreach ($prices as $price) проходит по всем элементам по порядку, без счётчика и условия.
  • Переменная цикла — копия элемента: присваивание ей список не меняет.
  • По строке foreach не ходит: PHP выдаёт Warning, и тело не выполняется.