Задать вопрос

Почему массив не меняется в цикле foreach?

Почему такое поведение? Оператор foreach копирует массив перед проходом? Как сделать, чтобы массив изменялся по ходу выполнения цикла?

$arr = ['a', 'b', 'c', 'd'];

foreach ($arr as $key => $val) {
  echo "key=$key, val=$val\n";

  foreach ($arr as $k => $v) {
    if ($v == 'b') {
      $arr[$k] = 'x';
      echo "arr[$k] is set to x\n";
    }
  }
}

Результат:
key=0, val=a
arr[1] is set to x
key=1, val=b
key=2, val=c
key=3, val=d


UPD: Без вложенного цикла то же самое
$arr = ['a', 'b', 'c', 'd'];

foreach ($arr as $key => $val) {
  echo "key=$key, val=$val\n";

  if ($key == 0) {
    $arr[1] = 'x';
    echo "arr[1] is set to x\n";
  }
}
  • Вопрос задан
  • 166 просмотров
Подписаться 1 Простой 15 комментариев
Помогут разобраться в теме Все курсы
  • Нетология
    Веб-разработчик с нуля: профессия с выбором специализации
    14 месяцев
    Далее
  • Skillbox
    Профессия PHP-разработчик с нуля до PRO
    7 месяцев
    Далее
  • Хекслет
    PHP-разработчик
    10 месяцев
    Далее
Решения вопроса 2
@rPman
foreach не трогает массив во время прохода по нему, т.е. можно использовать вложенные циклы по одному и тому же массиву (а так же можно пользоваться current/begin/end для этого у массива отдельная переменная состояния).

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

Еще есть не очевидный момент, foreach($mas as $k=>$v) возвращает копию элемента $v, но поведение будет зависеть от типа этого элемента (это не foreach такой это в основе php передача по копированию идет так), например если элемент это массив, $v будет копией массива (но его элементы-объекты будут переданы все еще по ссылке), и изменение в нем не отразится на исходном, а вот если элемент массива будет объектом (экземпляр объекта) то значение его полей будет изменено. Это 'странное' поведение можно детерминировать, определив элемент в итераторе по ссылке &:
$mas=[[1,2],[2,3]];
foreach($mas as $k=>$v) $v[0]=0;
echo json_encode($mas)."\n";
// [[1,2],[2,3]]

$mas=[(object)["a"=>1],(object)["a"=>2]];
foreach($mas as $k=>$v) $v->a=0;
echo json_encode($mas)."\n";
// [{"a":0},{"a":0}]

$mas=[[1,(object)["a"=>1]],[2,(object)["a"=>2]]];
foreach($mas as $k=>$v) $v[1]->a=0;
echo json_encode($mas)."\n";
// [[1,{"a":0}],[2,{"a":0}]] смотрим что атрибут объектов внутри массива все же изменился!


// по ссылке
$mas=[[1,2],[2,3]];
foreach($mas as $k=>&$v) $v[0]=0;
unset($v); // об этом приходится помнить, что бы не получить нежданчик, если после цикла сделать $v=10 изменит последний элемент массива $mas
echo json_encode($mas)."\n";
// [[0,2],[0,3]]

вроде бы бездумное использование $k=>&$v (всегда по ссылке) считается вредным советом, ломает оптимизацию 'интерпретатора' и всегда нужно помнить делать unset после цикла, что бы не случайно не воспользоваться $v, ведь она будет ссылкой на последний элемент перебираемого массива.
Ответ написан
Комментировать
@alexalexes
Евгений Обыкновенный, нет, foreach временно создает объект-итератор.
Чтобы сообщить интерпретатору PHP, что вы хотите иметь связь внутри цикла с объектом-итератором, берите его элементы по ссылке:
foreach ($arr as $key => &$val) {
...
  foreach ($arr as $k => &$v) {
   ...
  }
}
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
https://www.php.net/manual/ru/migration70.incompat... :
foreach по значениям оперирует копией массива
Если foreach используется для стандартного перебора по значению, то он оперирует копией массива, а не самим массивом. Это значит, что изменения внесённые в массив внутри цикла не затронут перебираемые значения.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы