Ссылки внутри цикла foreach в Php

Тот случай, когда php работает согласно спецификации, но вопреки здравому смыслу.

Ниже приведён пример отлично работающего кода:

$list = array(1, 2, 3);
foreach ($list as &$item) {
    $item++;
}
print_r($list);

Запускаем, получаем логичный вывод:

Array
(
    [0] => 2
    [1] => 3
    [2] => 4
)

А теперь лёгким движением руки этот код превращается… превращается… в конструкцию со странным неочевидным функционалом.

$list = array(1, 2, 3);
foreach ($list as &$item) {
    $item++;
}
print_r($list);

foreach ($list as $item) {
    ;
}
print_r($list);

Мы всего лишь организовали второй цикл foreach по одному и тому массиву. И в этот раз print_r выдал совсем иные результаты.

Array
(
    [0] => 2
    [1] => 3
    [2] => 3
)

Что же произошло? Собственно, ничего такого, чтобы мы сами не просили сделать PHP. В первом цикле мы объявили ссылку &$item, которая после завершения работы цикла указывает на элемент массива $list[2]. Далее мы пробегаемся ещё раз по массиву, на каждом шаге присваивая переменной $item очередное значение. Т.к. в PHP область видимости переменных не ограничивается блоком составного оператора, то переменная $item во втором цикле - это та же самая переменная из первого цикла. Поэтому, одновременно с установкой значения переменной $item, это же значение присваивается и элементу $list[2].

Никаких ошибок нет, но получить-то мы хотели несколько другой результат. Есть несколько вариантов, чтобы обезопасить себя от таких неожиданностей. Первый вариант - никогда не забываем принудительно чистить переменные. Вот этот код всегда работает так, как задумано.

$list = array(1, 2, 3);
foreach ($list as &$item) {
    $item++;
}
print_r($list);

unset($item); // !!!

foreach ($list as $item) {
    ;
}
print_r($list);

Второй вариант - для изменения элементов массива не используем ссылки, используем конструкцию $key => $value. С ней также нет никаких проблем.

$list = array(1, 2, 3);
foreach ($list as $key => $item) {
    $list[$key]++;
}
print_r($list);

foreach ($list as $key => $item) {
    ;
}
print_r($list);

Ну и самый правильный вариант, при котором мы не только избегаем описанной проблемы, но и многих других - максимально сокращаем область видимости переменных, выделяя логические части кода в отдельные функции. Этот вариант неплохо бы совмещать с одним из первых двух.

$list = array(1, 2, 3);

$incrementList = function ($list) {
    foreach ($list as $key => $item) {
        $list[$key]++;
    }
    return $list;
};
$list = $incrementList($list);
print_r($list);

$doList = function ($list) {
    foreach ($list as $key => $item) {
        ;
    }
    return $list;
};
$list = $doList($list);
print_r($list);

Здесь $item первого цикла и $item второго цикла - разные переменные.

Пишите на PHP и прибудет с вами сила!

Comments

© 2016. All rights reserved.