Разбираем foreach в PHP: от А до Я (с примерами)

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему foreach — это не просто «для каждого»

Если вы пишете на PHP хотя бы неделю, вы уже сто раз использовали foreach. Это, пожалуй, самый любимый цикл каждого разработчика: коротко, элегантно, без лишних счетчиков. Но задумывались ли вы, что на самом деле происходит под капотом, когда вы пишете foreach ($array as $value)?

Вопрос «How does PHP 'foreach' actually work?» набрал тысячи просмотров на Stack Overflow. И это не случайно. Поведение foreach таит в себе несколько сюрпризов, особенно когда речь заходит о ссылках, вложенных циклах и нестандартных массивах.

В этом туториале мы не просто повторим синтаксис. Мы заглянем внутрь движка PHP, разберемся с внутренним указателем массива, увидим, как работают итераторы, и научимся избегать подводных камней. Поехали!

1. Анатомия foreach: что говорит документация

Начнем с базы. foreach предназначен для перебора массивов и объектов. Синтаксис прост:

<?php
$fruits = ['apple', 'banana', 'cherry'];

foreach ($fruits as $fruit) {
    echo $fruit . "\n";
}
// Вывод: apple banana cherry
?>

Но есть и вторая форма — с ключом:

<?php
$prices = ['apple' => 100, 'banana' => 50];

foreach ($prices as $name => $price) {
    echo "$name стоит $price руб.\n";
}
?>

Кажется, всё очевидно. Но давайте копнем глубже. PHP обрабатывает foreach не так, как for или while. Внутри он использует внутренний указатель массива (internal array pointer) и специальный механизм итерации.

2. Внутренний указатель массива: секретный агент

У каждого массива в PHP есть невидимый «курсор» — внутренний указатель. Он показывает на текущий элемент. Функции current(), next(), prev(), end() и reset() управляют именно им.

Когда вы запускаете foreach, PHP делает следующее:

  • Сбрасывает внутренний указатель на первый элемент (как reset()).
  • На каждой итерации берет элемент, на который указывает указатель.
  • Сдвигает указатель на следующий элемент (как next()).
  • Повторяет, пока не дойдет до конца.

Посмотрим на это в действии:

<?php
$array = [10, 20, 30];

// Ручная имитация foreach
reset($array); // Указатель на первый элемент
while ($value = current($array)) {
    echo $value . " ";
    next($array); // Сдвигаем указатель
}
// Вывод: 10 20 30
?>

Но есть нюанс: если массив пуст или содержит false, этот трюк сломается. foreach же работает корректно всегда, потому что использует более надежный внутренний механизм — итераторы.

3. Итераторы и foreach: современный подход

Начиная с PHP 5, foreach может работать не только с массивами, но и с любыми объектами, реализующими интерфейс Traversable (или его наследника Iterator).

Когда вы передаете объект в foreach, PHP вызывает методы итератора:

  • rewind() — сброс в начало
  • valid() — проверка, есть ли элемент
  • current() — получить значение
  • key() — получить ключ
  • next() — перейти к следующему

Пример пользовательского итератора:

<?php
class NumbersIterator implements Iterator {
    private $position = 0;
    private $numbers = [1, 2, 3, 4, 5];

    public function rewind() {
        $this->position = 0;
    }

    public function current() {
        return $this->numbers[$this->position];
    }

    public function key() {
        return $this->position;
    }

    public function next() {
        ++$this->position;
    }

    public function valid() {
        return isset($this->numbers[$this->position]);
    }
}

$iterator = new NumbersIterator();
foreach ($iterator as $key => $value) {
    echo "$key: $value\n";
}
// Вывод: 0: 1, 1: 2, 2: 3, 3: 4, 4: 5
?>

Это дает невероятную гибкость: вы можете перебирать файлы, строки базы данных, результаты API — всё, что угодно, если это упаковано в итератор.

4. Ссылки в foreach: мощный инструмент и источник багов

Один из самых коварных моментов — использование & (ссылки) в foreach. Рассмотрим пример:

<?php
$numbers = [1, 2, 3, 4];

// Перебор по ссылке
foreach ($numbers as &$number) {
    $number *= 2;
}
unset($number); // ВАЖНО! Удаляем ссылку

print_r($numbers);
// Вывод: [2, 4, 6, 8]
?>

Всё работает как часы. Но что произойдет, если не удалить ссылку после цикла? Переменная $number останется ссылкой на последний элемент массива. Если вы потом измените $number, вы измените и массив!

Классический баг:

<?php
$array = [1, 2, 3];
foreach ($array as &$value) {
    // ничего не делаем
}
// Забыли unset($value)

foreach ($array as $value) {
    // Теперь $value — это ссылка на последний элемент!
    // На каждой итерации мы меняем последний элемент массива
}
print_r($array);
// Вывод: [1, 2, 2] — третий элемент стал равен второму!
?>

Золотое правило: всегда используйте unset($var) после цикла foreach со ссылкой. Или, еще лучше, избегайте ссылок, если это не критично.

5. Вложенные foreach: как не запутаться

Когда вы вкладываете один foreach в другой, внутренний указатель массива может вести себя неожиданно. PHP 7+ справляется с этим лучше, но в старых версиях были проблемы.

Пример безопасного вложенного цикла:

<?php
$matrix = [
    [1, 2],
    [3, 4],
    [5, 6]
];

foreach ($matrix as $row) {
    foreach ($row as $cell) {
        echo $cell . " ";
    }
    echo "\n";
}
// Вывод:                

Блоги

Рекомендаций по книгам