Подводный камень в foreach($items as &$item)

    Многие любят писать такие конструкции в том или ином виде, каждый сталкивался:
    foreach ($items as &$item) {
        $item += 2;
    }
    

    Но не многие подозревают о том, какая опасность тут скрывается.
    Рассмотрим пример.

    Вася Пупкин взял массив, прошелся по нему, увеличив на два все элементы:
    $items = array(
        'a' => 10,
        'b' => 20,
        'c' => 30,
    );
    
    foreach ($items as &$item) {
        $item += 2;
    }
    
    print_r($items);
    

    Посмотрел дамп, увидел что задача решена, и ушел довольный:
    Array
    (
        [a] => 12
        [b] => 22
        [c] => 32
    )
    

    Спустя некоторое время, Петрович решил дополнить этот участок кода другим перебором, дописав ниже:
    $newitems = array(
        'a' => 10,
        'b' => 20,
        'c' => 30,
    );
    
    foreach ($newitems as $key=>$item) {
        $newitems[$key] += 5;
    }
    
    print_r($newitems);
    

    Посмотрел, что его задача тоже решена, и с чувством выполненного долга закрыл файл:
    Array
    (
        [a] => 15
        [b] => 25
        [c] => 35
    )
    

    Спустя какое-то время, стали вылезать необъяснимые баги. Почему?
    Сделаем в конце кода var_dump($items):
    array(3) {
      ["a"]=>
      int(12)
      ["b"]=>
      int(22)
      ["c"]=>
      &int(30)
    }
    

    30! Вася Пупкин клянётся, что проверял. Почему было 32, а после кода Петровича 30?

    Причина кроется в амперсанде. Он сообщает, что на отмеченные данные ссылается кто-то ещё. Уходя, Вася не подтёр за собой временную переменную, которую использовал для перебора ($item). Переменная использовалась с разрешением на изменение источника ("&"), которое также называют «присваиванием по ссылке». Он был уверен, что переменная будет использоваться только внутри цикла. Петрович, используя переменную с таким же именем, в ходе своего перебора, менял её значение, и каждый раз менялось то место, где эта переменная хранилась. А хранилась она там же, где последний элемент массива Пупкина.

    Конечно, в случай в статье утрирован. На практике такие связи могут быть очень сложными, особенно если проект недорогой, и в нём участвуют недостаточно опытные и разрозненные веб-разработчики.

    Как можно с этим оброться?
    • Уничтожать временные переменные после использования, особенно если они имеют какие-то связи с используемыми данными:
      foreach ($items as &$item) $item += 2;
      unset($item);
      
    • Быть осторожнее с переменными, которые уже кем-то использовались.
    • Инкапсулировать свои действия в отдельные функции, методы или пространства имён.
    • Использовать var_dump, вместо print_r, и обращать внимание на амперсанд. Чтобы дампить в файл, а не в браузер, альтернативой print_r($var,true) будет такая конструкция:
      function dump() {
          ob_start();
          foreach(func_get_args() as $var) var_dump($var);
          return ob_get_clean();
      }
      

    В заключение скажу, что баги, связанные со ссылками, могут быть не только в foreach. И все они когда-то обсуждались. Однако, этот случай, судя по моему опыту, так распространён на практике, что заслуживает отдельного внимания.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 145
    • +12
      Сам на это натыкался. Кстати, в мануале прямо указано — делайте unset. Я даже в фреймворке yii нашел участок кода где есть эта бага.
      • +2
        Да, многие разработчики на этом деле подрывались, и описание этой проблемы действительно есть в мануале.

        Но, я не встречал (около года назад) нормального описания этой проблемы на русском языке, что могло сильно попортить нервы молодым разработчикам. Поэтому, автору респект за описание проблемы в доступной форме.
        • НЛО прилетело и опубликовало эту надпись здесь
          • +1
            Не всё, скаляры по значению.
            • +1
              Тьфу, в смысле для скаляров присваивание по значению, а иногда нужна ссылка.
              • 0
                Если эти скаляры длинной меньше длинны указателя, то по значению они будут передаваться быстрее!
                • +1
                  В общем не найти мне статьи где это подробно расписано, вот вам простейший тест:
                  // Начальная переменная со строкой в 1000 символов
                  $a0 = str_repeat('*', 1000);
                  
                  // Создаем 1000 переменных
                  extract(range(1, 1000), EXTR_OVERWRITE, 'a');
                  
                  // Исходное потребление памяти
                  echo memory_get_usage() . '<br />';
                  
                  // Копируем по значению
                  for($i = 1; $i < 1000; $i++) {
                  	${'a' . $i} = $a0;
                  }
                  
                  // Память остается прежней
                  echo memory_get_usage() . '<br />';
                  
                  // Создаем ту-же строку с нуля
                  for($i = 1; $i < 1000; $i++) {
                  	${'a' . $i} = str_repeat('*', 1000);
                  }
                  
                  // Потребление памяти увеличивается в разы
                  echo memory_get_usage() . '<br />';
                  
                  • 0
                    Похоже ваша правда, погонял даже на числах, чтоб не было соблазна написать, что строка это массив символов :)

                    Правда тест ваш у меня чуть-чуть по другому вёл. extract ввобще объём памяти не изменил (добавил строчку с mem_get_usage и перед ним), а там где «память остаётся прежней» объём вырос, судя по всему на размещение памяти на сами переменные.
                    • 0
                      Да это я с extract'ом напортачил, префикс работает только когда указан один из ключей EXTR_PREFIX_*, ну и после префикса еще подчеркивание, соот-но правильно будет так:
                      $a_0 = str_repeat('*', 1000);
                      
                      extract(array_combine(range(1, 1000), array_fill(1, 1000, null)), EXTR_PREFIX_ALL, 'a');
                      
                      echo memory_get_usage() . '<br />';
                      
                      for($i = 1; $i < 1000; $i++) {
                      	${'a_' . $i} = $a_0;
                      }
                      
                      echo memory_get_usage() . '<br />';
                      
                      for($i = 1; $i < 1000; $i++) {
                      	${'a_' . $i} = str_repeat('*', 1000);
                      }
                      
                      echo memory_get_usage() . '<br />';
                      
                    • 0
                      Какой знакомый код. Вот тут это писал.
                    • +1
                      Всё всегда передаётся по ссылке, но в случае скаляров, при их изменении, PHP делает копию и изменяет именно её.
                    • 0
                      Эм… ну я даже затрудняюсь ответить… по назначению? :)
                      • +1
                        В представленном случае очевидно, зачем — чтобы модифицировать элементы массива путем записи в $item.
                        • +10
                          Тоже наталкивался на эту проблему, для себя взял за правило всегда использовать перебор $key => $value, а изменять используя $items[$key] =…
                          Сравнивать этот способ со ссылками по производительности — это как экономить на спичках.
                          С точки зрения читаемости — код чуть длиннее, согласен, но не критично. Зато надёжно!
                          • +1
                            Угу, мне тоже козломатерь не позволяет. Глаз мозолит, как и собака
                            • +4
                              Во, я когда читал, всё не мог сообразить, как я без ссылок в таких случаях обхожусь :)
                        • 0
                          Была, а не есть.
                          • 0
                            Точно. Немного дезинформировал. Она сохранилась только в истории свн.
                        • +20
                          Опять 25.
                          Вообще-то об этой особенности в мануале написано.
                          • –14
                            Никогда такой конструкцией не пользовался, хоть и пишу достаточно сложный проект, я даже не представляю задач где можно пользоваться присвоением по ссылке. И вообще ссылками в управляемых средах не работаем. Обзор интересен.
                            • +2
                              Придумал. Может быть для оптимизации какой-нибудь?
                              • 0
                                Как бы проще объяснить… Это тоже самое, что и этот код:

                                switch ($var)
                                {
                                    case 1:
                                        # some code
                                        break;
                                    case 2:
                                        # one more
                                        break;
                                    case 3:
                                    default:
                                        # code block
                                        break;
                                }
                                

                                Заменить на

                                if ($var == 1)
                                {
                                    # some code
                                }
                                elseif ($var == 2)
                                {
                                    # one more
                                }
                                else
                                {
                                    # code block
                                }
                                


                                Сделать можно и так, и так. Однако под каждую задачу есть свои, более оптимальные, конструкции.
                                • 0
                                  Да это понятно. И приведенной вами конструкцией я пользуюсь сразу двумя, так как case не всегда позволяет выполнять требуемые задачи. Но вот с этой ситуацией не очевидно.

                                  Пожалуйста объясните сложнее.
                                  • –4
                                    Понятно. Для переборки индексных массивов, включая разряженные.
                                    Мне кажется лучше пользоваться кейвалью, будет очевидней код, что бы он был более унифицирован, а то от оного апперсанда зависит как работает то что внутри.
                                    • 0
                                      $array = array('string', 1, '4', 8, 'second', 'word', 12, '42');
                                      
                                      function valuesToInt(array $array = array())
                                      {
                                          foreach ($array as &$value)
                                          {
                                              if (!is_int($value))
                                              {
                                                  $value = (int) $value;
                                              }
                                          }
                                      
                                          return $array;
                                      }
                                      
                                      $arrayOnlyIntValues = valuesToInt($array);
                                      
                                      • –2
                                        Дай допишу
                                        unset($value);
                                        
                                        • +2
                                          *и ушел довольный.

                                          :-)
                                        • 0
                                          Если честно, то сейчас такие конструкции можно заменить на более короткие.

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

                                          Можно более конкретный и полезный пример?
                                          • 0
                                            То же самое можно ещё и так:
                                            $arrayOnlyIntValues = array_map(function($value) {
                                            	if (is_int($value)) {
                                            		return $value;
                                            	} else {
                                            		return (int) $value;
                                            	}
                                            }, $array);
                                            


                                            Или, для этого конкретного случая, так:
                                            $arrayOnlyIntValues = array_map("intval", $array);
                                            


                                            Или сразу на месте:
                                            array_walk($array, function(&$value) {
                                            	$value = intval($value);
                                            });
                                            // $array === $arrayOnlyIntValues
                                            

                                            В этих случаях и читается код так же легко как с foreach, и негативных побочных эффектов нет.
                                            • 0
                                              А можно и так:
                                              $arrayOnlyIntValues = array_map(function($value) {
                                                      return (int) $value;
                                              }, $array);
                                              


                                              Можно, конечно, по разному, просто я пытался привести пример использования именно по ключам. С самого начала я сказал, что большинство задач можно решить разными путями, и намеряно привёл путь решения через foreach и ссылки.
                                  • –1
                                    Простая задача — экономия памяти. При передачи по ссылке значение переменной не копируется.
                                    • 0
                                      Так и есть.
                                      • +7
                                        При передаче не по ссылке значение так же не копируется =) Только при изменении.
                                        • +1
                                          Так-то оно так, но в этом конкретном примере — нет резона использовать foreach по ссылке если не нужно изменять переменную.
                                      • 0
                                        А что имеете в виду под управляемыми средами? Не это?
                                        Причем переменные объектного типа и объекты в Java — совершенно разные сущности. Переменные объектного типа являются ссылками, то есть неявными указателями на динамически создаваемые объекты.
                                        или это?
                                        Кроме того, в C# решено было перенести некоторые возможности C++, отсутствовавшие в Java: ... передача параметров в метод по ссылке... Также в C# оставили ограниченную возможность работы с указателями

                                        • –3
                                          Конечно давайте будем растить память! Она ведь и так в php не чиститься от мусора, а мы добьем лежачего :)
                                          • 0
                                            > не чиститься от мусора

                                            Поясните.
                                            • 0
                                              я имел ввиду то что до версии 5.3 в php не было как такого сборщика мусора, а используется подсчёт ссылок, что приводит к утечки памяти при «циклических ссылках».

                                              то-есть:
                                              $a = 10; // выделяем область в памяти, одна ссылка
                                              $b = $a; // две ссылки
                                              $b = 1; // выделяем вторую область в памяти под значение 1, одна ссылка на 1, одна ссылка на 10
                                              


                                              Однако в PHP 5.3 боле умный сборщик мусора, но срабатывает он только при наполнении буфера ссылок ru.php.net/manual/en/features.gc.performance-considerations.php

                                              тем самым частичка памяти все же утекает(

                                              а что касается Конечно давайте будем растить память! это был сарказм.
                                              • 0
                                                Либо Вы не понимаете что такое «циклические ссылки», либо пытаетесь их объяснить без использования объектов, что странно.

                                                Попробуйте объяснить по-другому.
                                        • +25
                                          Видимо, правду говорят, что есть две категории людей — которые понимают указатели и которые не понимают указатели.
                                          • +2
                                            Это точно, хотя в PHP они в общем-то простые как 2 + 2. Однако конечно умение ими пользоваться иногда позволяет делать вещи намного эффективнее (но не факт, что понятнее).
                                            • –5
                                              Очень важна понятность кода, даже в ущерб производительности, ведь почему люди на ассемблере перестали писать? Почему потом поняли, что лучше функциональное, потом ООП, потом сборка мусора, все для удобства. Люди пользуйтесь указателями в исключительных случаях.
                                              • +5
                                                У меня была одна ситуация, когда мне пришлось использовать ссылки — обрабатывалась древовидная структура с неограниченной вложенностью и приводилась к двумерному массиву, причём там ещё были какие-то дополнительные условия, которые использование рекурсии делали невозможным. В общем этот кусок кода разросся бы до неприличных размеров, а ссылками уложился в 30-40 строк.
                                                Подробностей не скажу, давно было. Но реально выручило знание ссылок.
                                            • 0
                                              Понимание указателей не причем. Для любой переменной справедливо, что если мы её что-то присвоили, то она и будет это содержать, не имеет значения, что в ней было до этого и была ли она до этого объявлена. А тут появляется Вася, который выше твоего кода, не посмотрев на всю портянку, дописал одну строчку, используя то же название переменной. В PHP с указателями очень тяжело.
                                              • 0
                                                Да, но топик упирает на то, что катастрофа приключилась из-за указателей. Хотя да, указатели тут действительно не при чем.
                                                • 0
                                                  Понимание указателей не причем. Указатели очень причем.
                                            • +1
                                              :[|||]:
                                              • +23
                                                У Вас кнопки за пределы инструмента вылезли.
                                              • +2
                                                Боян несусветной давности :)
                                                • –23
                                                  Поэтому мои переменные всегда имеют вид: yazArray, yazVar, yazItem etc. (:
                                                  • +16
                                                    Страхи какие
                                                    • +34
                                                      $yaPeremenko, $yaObyekteg, $yaMassivcheg…
                                                      • +9
                                                        Знал я одного программиста которы на вопрос «почему для всех переменных ты делаешь префикс?» сказал: «Это мой фирменный знак, моя фишка».
                                                        • 0
                                                          Ну если программист хороший — то почему нет, ради действительно хорошего программиста не жалко будет и кодовые соглашения переписать :)
                                                          • +5
                                                            Да щас! Если программист хороший, он не будет изобретать подобные бредовые «фирменные знаки», не несущие никакого смысла. В нормальном коде не место школьным понтам.
                                                            • +2
                                                              О такие вещи реально глаза ломаются.
                                                              По-настоящему хороший программист напишет нормальные имена функций, переменных и т.д., а «фирменные префиксы» — это фирменные понты. Видел я таких: перед тем как насрать в код затирают копирайты настоящего автора и меняют толковые имена на фирменную ерунду. Пару раз натыкался на такое — у одного «VasylFunction», у другого «OlegFunction». Убил бы обоих. Случаи, когда это можно простить — древние CMS-ки, в которых всё в куче и условие if-else расписано на полторы тысячи строк.
                                                              • 0
                                                                PSR-0 требует vendor name… Наткнулся случайно, так почти целый день убил придумывая…
                                                      • +11
                                                        хорошо что не yaaaaazArray, yaaaaaazVar…
                                                        • +22
                                                          int aaaaazArray[111]; // Вот он массив моей мечты!11
                                                          • +3
                                                            int a[MAX_INT]; //здоровенный!
                                                            
                                                            • 0
                                                              #!/usr/bin/python

                                                              def infinity():
                                                              while True:
                                                              yield None

                                                              yaaaaaaaaz_array = infinity() #здоооорооовеееееннныыыыыый!!!11
                                                        • +1
                                                          В бывшей фирме я встретился с конвенцией поименования переменных:
                                                          l (local), p (private), итд… + a (array), s (string), i (integer), итд… + + название…
                                                          Ну и потом количество рогов скота выглядело примерно: $pirog.
                                                          Очень сильно помогало понимать, что находится в коде и какого типа данные.

                                                          • +2
                                                            Это называется венгерская нотация.
                                                            При наличии современных IDE с code completion смысла в её использовании лично я не вижу.
                                                            • –1
                                                              Уже очень давно пользуюсь такой системой именования переменных. Действительно укоряет понимание кода.
                                                              • –1
                                                                И без большой буквы после префикса?
                                                                • 0
                                                                  С большой, конечно) Собственно как в венгерский нотации. Видимо с изучения си++ это у меня и осталось.
                                                          • +2
                                                            Так давно известно же
                                                            • +2
                                                              Использовать var_dump, вместо print_r, и обращать внимание на амперсанд. Чтобы дампить в файл, а не в браузер, альтернативой print_r($var,true) будет такая конструкция:


                                                              Есть функция var_export, делает как раз то что вы хотите
                                                              var_export($var,true)
                                                              Второй параметр аналогичен параметру в print_r возвращает содержимое а не печатает в буфер.

                                                              Единственное отличие, принимает только один параметр, когда var_dump принимает бесконечное кол-во параметров
                                                              • +1
                                                                Функция var_export нужна для преобразования структуры данных в php-код, формирующий эти данные. Она может быть полезна, например, при обработке конфигов. В var_dump же можно увидеть тип данных и тот самый амперсанд, означающий, что данные используются кем-то еще.
                                                                • 0
                                                                  Спасибо что пояснили :)
                                                                • 0
                                                                  В print_r тоже второй необязательный параметр, который указывает возвращать или выводить данные.
                                                                • +3
                                                                  Как много людей указывают на то, что это давно известно, да еще и не раз обсуждалось. Ну раз еще раз всплыло, значит может быть действительно проблема есть?

                                                                  В каком еще языке вы не можете знать, что произойдет с переменной, когда вы присвоите ей значение?

                                                                  В Си во-первых, все переменные нужно объявлять, поэтому вздумай вы использовать переменную item в качестве указателя вы пойдете её объявить и увидите, что такая уже есть. Во-вторых там даже положив в указатель число и проведя арифметические операции, его можно безопасно извлечь, потому что для доступа к тому, куда указывает указатель используется специальный синтаксис.

                                                                  В питоне и яваскрипте указателей вообще нет.

                                                                  Php — самый опасный язык для работы с указателями, или я что-то упускаю?
                                                                  • –1
                                                                    Не будьте столь категоричным. В любом языке есть свои нюансы, хорошие и плохие стороны, начинающие и опытные разработчики.
                                                                    • +8
                                                                      Меня реально заколебали ответы «у дргих еще хуже». И в политике и в повседневной жизни. Как вообще может прийти в голову считать это аргументом?
                                                                      • +2
                                                                        В php любая переменная — это указатель. При присваивании или передаче, новая переменная становится указателем на те же данные, а у данных увеличивается счетчик ссылок на них. При изменении одной из переменных, для нее создается копия данных. Амперсанд относится не к переменной, а к выполняемой операции (прямого или косвенного присваивания). Он означает, что при изменении переменной-приёмника не будет создаваться копия, а данные будут изменяться в источнике. В этом плане php работает хорошо и никому обычно не мешает. Однако, разработчики, вероятнее всего, изучавшие программирование на примере Си, часто не знают об этой особенноси php, и не учитывают её при составлении скриптов. Так возникают баги, подобные описанному.
                                                                        • 0
                                                                          Что-то изменилось с тех пор как я разбирался с этим и в мане врут?
                                                                          По умолчанию, переменные всегда присваиваются по значению. То есть, когда вы присваиваете выражение переменной, все значение оригинального выражения копируется в эту переменную.
                                                                          • 0
                                                                            Тут про «copy on write», значение (в нутрях php, в zend engine), копирует только когда это действительно необходимо, иже когда оно изменяется, а так если иметь 10к переменных с одинаковым значением (скопировав их с какого-то одного) — память будет потребляться только на сами структуры переменных. И в мане кстати все ок, в мане про это знают :)
                                                                            • 0
                                                                              Насколько я знаю к скалярным типам это не относится, только массивы и объекты. Нет?
                                                                              • 0
                                                                                Почему, это в первую очередь к скалярным типам и относится, ну и к массивам, а объекты всегда передаются по ссылке, они с copy on write никак не связаны, при наличии двух ссылок на объект и изменении объекта в одной — копия создана не будет.
                                                                                • 0
                                                                                  Блин да достали вы со своими скалярами :) Они не просто так по значению передаются. Это тоже оптимизация ;)
                                                                                  • 0
                                                                                    Отписался по поводу скаляров выше: habrahabr.ru/blogs/php/136835/?reply_to=4554710#comment_4554800
                                                                                • 0
                                                                                  На хабре есть хорошая статья про это.
                                                                                  • 0
                                                                                    Там примеры не со скалярными типами.
                                                                                • 0
                                                                                  Как эта особенность проявляется внешне, и как она связана с обсуждаемой проблемой?
                                                                            • –3
                                                                              В .NET есть указатели, но ими я помню очень редко пользовался, в основном, что бы перейти на нативный уровень, блокировав сборщик мусора. И то там были проблемы — например char в .NET 16 разрядный. Не надо пользоваться в управляемых языках указателями.
                                                                            • –1
                                                                              foreach ($newitems as $key=>$item) обещали сделать deprecated
                                                                              • +1
                                                                                Что конкретно из этого? Весь foreach? $key=>$item?
                                                                                • 0
                                                                                  Рекомендуется конструкция foreach ($array => &$item)
                                                                                  • –1
                                                                                    вместо => as конечно же.
                                                                                    • 0
                                                                                      Круто, а как же быдлоконструкция if(!$key) echo 'br';?
                                                                                      • –1
                                                                                        Это только в планах. Может быть, планы изменились, я плохо следил последнее время. Без $c as $b=>$c ключ нормальными способами не вытащить сейчас.
                                                                                        • 0
                                                                                          «ключ нормальными способами не вытащить сейчас»

                                                                                          Иногда, когда мне нужны только ключи и обход по ним, делаю foreach (array_keys($items) as $key) {}
                                                                                          Чем этот способ плох?
                                                                                          • +1
                                                                                            А если и то и то?
                                                                                            // Думаю конструкция выше может быть deprecated только в пользу ArrayIterator.
                                                                                      • 0
                                                                                        То есть ключи не рекомендуются? В манах что-то ничего не нашёл.
                                                                                        • –1
                                                                                          Скорее рекомендуется без ключа. Вот только я не помню, где именно это читал. На официальном сайте php 100%
                                                                                  • 0
                                                                                    Предпочитаю всё unset'ить как только отпала надобность, чтоб не переживать.
                                                                                    • 0
                                                                                      Мне бы было лень, я тупо не юзаю переменные из-за скобок (только для «передачи» параметров беру из-за скобки), а если надо то практически всегда это отдельная сущность, которая выносится в метод.
                                                                                      • 0
                                                                                        Вася Пупкин тоже не переживал :)
                                                                                      • 0
                                                                                        Когда пишешь unset для каждой переменной, консольный демон практически не течет, так что очень рекомендую. Тем более в СИ программист сам следит за памятью, что в PHP и подразумевается, но почему то, никто этого не делает.
                                                                                        • 0
                                                                                          собственно привычка с С/C++ malloc/free и new/delete должны быть парными и крайне желательно в одном блоке, край — конструктор/деструктор.
                                                                                          • 0
                                                                                            unset'ы код загрязняют сильно. В С++ то это легко обходится всякими Locker'ами и RAII, но там область видимости хорошая и строгая.
                                                                                      • +1
                                                                                        1. Это не баг, а вполне разумное и ожидаемое поведение.
                                                                                        2. Всегда надо чистить пространство имён с помощью unset, особенно тогда, когда используете передачу по ссылке. Не заботиться о чистке можно только тогда, когда у вас сразу после этого заканчивается область видимости, но и тогда лучше поставить unset на случай последующих доработок.
                                                                                        • +14
                                                                                          На самом деле беда в том, что у PHP слишком долгая память переменных… по уму — за блоком foreach $item уже не должна быть определена (что и делается в других, более строгих языках). Хочешь сохранять — делай это явно, вот и вся история ;-)
                                                                                          • +2
                                                                                            Только это не долгая память переменных называется, а область видимости %) А так да, в пхп как всегда отличились.
                                                                                            • 0
                                                                                              В С++ разве не аналогично? Переменная объявленная в цикле for имеет область видимости до конца функции?
                                                                                              • +2
                                                                                                В C вроде как каждый блок {} имеет свою область видимости, то есть если объявить переменную прямо внутри цикла — то она заменит собой переменную объявленную извне, но только внутри цикла.
                                                                                                • +1
                                                                                                  Вот вот. И это очень правильно! Поэтому в таких языках не нужны костыли в виде unset.
                                                                                                  • +1
                                                                                                    Ну php проектировался с одной главной идеей — он должен быть простым языком, понятным всем, а блочные области видимости это довольно таки сложно для понимания новичка.
                                                                                                    P.S. не удивлюсь если лет через 100 на нём разговаривать начнут, вместо теперешнего эсперанто :)
                                                                                                    • 0
                                                                                                      Понятнее и проще? Оо ну не скажи, по мне так это наоборот сложнее и не очевиднее и ведет к веселым багам.
                                                                                                  • 0
                                                                                                    В C вроде вообще переменные объявляются только в контексте программы или функции, да ещё в самом начале функции. А вот в C++ допустимо, например, for(int i=0; i<10; i++) {… } и тут переменная объявлена вне {} цикла.
                                                                                                    • +1
                                                                                                      В C можно объявлять переменные в начале любого блока, а не только функции. А в С99 их можно объявлять в любом месте, так же как и в C++, в т.ч. и условиях.
                                                                                                      Далее, конструкция for(int i=0; i<10; i++) {… } все же объявляет i только в пределах цикла.
                                                                                                      • +1
                                                                                                        В C переменные можно объявлять в начале абсолютно любого блока, будь то тело функции, тело цикла или просто блок внутри другого блока (очень удобный лайфхак, когда нужна временная переменная, однако не хочется выносить ее в начало функции)…
                                                                                                    • 0
                                                                                                      В С++ не силен, но в том же компиляторе от microsoft есть возможность выбора поведения. А по этой ссылке: docs.freebsd.org/info/g++FAQ/g++FAQ.info.for_scope.html написано что в gcc теперь имеет область до конца цикла.
                                                                                                • +27
                                                                                                  • +6
                                                                                                    Поэтому надо делать всегда так
                                                                                                    foreach ($items as $key=>$item) {
                                                                                                    $items[$key] += 2;
                                                                                                    }
                                                                                                    • 0
                                                                                                      На больших значениях в массиве рискуете серьезно потратится на память, как минимум на объем самой структуры массива.
                                                                                                      • +2
                                                                                                        А разве $items[$key] это не та же ссылка?
                                                                                                        • 0
                                                                                                          Нет.
                                                                                                          • 0
                                                                                                            Ну в таком случае вероятность ниже написать $item[$key] =… намного ниже чем просто $item
                                                                                                            • 0
                                                                                                              Для любителей поминусовать вот тут очень подробно расписано почему это не ссылка и как происходит потребление памяти.
                                                                                                              • 0
                                                                                                                Вы не поняли. После цикла за скобками останется в силе последние значения $items и $key. Соответственно если написать $items[$key] = «12» то вы перепишете последний элемент массива
                                                                                                                • 0
                                                                                                                  Полагаю что всё же наоброт.
                                                                                                                  Я не говорю про то, как и где можно что-то перезаписать. Как я сказал в этом комментарии, при использовании подхода:
                                                                                                                  foreach ($items as $key=>$item) {
                                                                                                                  $items[$key] += 2;
                                                                                                                  }
                                                                                                                  увеличивается потребление памяти.
                                                                                                                  Еще раз: ничего про присвоение переменных за скобками цикла я не говорил.

                                                                                                                  А то что написали вы — очевидно, правда с уточнением, что остается только переменная $key, которая была записана в последней итерации. Массив $items уже был. Ничего «багнутого» тут нет.
                                                                                                          • 0
                                                                                                            array_walk($arr, function(&$value){
                                                                                                            $value+=2;
                                                                                                            });

                                                                                                            Или так :)
                                                                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                              • 0
                                                                                                                У вас это узкое место в приложении? :)
                                                                                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                  • 0
                                                                                                                    Я к тому, что перед тем как мерять спички, надо сначала взять в руки профайлер и посмотреть что у вас действитлеьно тормозит. Подозреваю, что в 99.99% метод обхода массива будет влиять на приложение «никак».
                                                                                                                    • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                            • 0
                                                                                                              Когда-то сталкивались в реальных условиях, конечно был бы функциональный тест, проблему бы заметили, а так проморгали. Вообще "&" где его можно не использовать, лучше не использовать.
                                                                                                              • 0
                                                                                                                тут об этом написано
                                                                                                                Вообще zend замудренно оптимизирует все это дело, imho с++ подобное поведение было бы куда более понятным и правильным
                                                                                                              • –5
                                                                                                                даже в Smarty есть этот баг.

                                                                                                                {foreach item=param from=$params}
                                                                                                                {$param}
                                                                                                                {/foreach}

                                                                                                                а после завершения {foreach} переменная {$param} остаётся. Если уже раньшще была определена {$param} — она изменяется.
                                                                                                                • +4
                                                                                                                  Это не баг
                                                                                                                  • 0
                                                                                                                    Я тоже так думал пока у меня на Девелопе отрабатывало нормально а на продакшне посыпалось!
                                                                                                                • +1
                                                                                                                  foreach($new as $key => $value)
                                                                                                                  $new[$key] = $value+2;

                                                                                                                  Так делал с первого дня, не так красиво — зато без багов.
                                                                                                                  • 0
                                                                                                                    Пока писал — опередили:)
                                                                                                                  • +2
                                                                                                                    Используйте чаще array_map
                                                                                                                    • 0
                                                                                                                      Вот при работе с встроенными функциями array_map действительно хорош, а при вызове пользовательских идут расходы на смену контекста выполнения, для задачи вроде $a += 2 — это имхо неоправданные траты.
                                                                                                                      • 0
                                                                                                                        он уже давно быстрее и при вывове пользовательских. К тому же, для array_map можно использовать лямбды, которые, в принципе, inline.
                                                                                                                    • 0
                                                                                                                      Я вам больше скажу. При работе с кастомными массивами (Traversable) вам такой перебор вообще не поможет изменять данные. Надо всегда быть готовым вместо любого массива использовать Traversable, поэтому стоит ограничивать функционалом, который поддерживается как настоящими массивами, так и кастомными. До тех пор, пока команда разработчиков PHP не доведет поддержку кастомных массивов до того же уровня, что и нативных.
                                                                                                                      • 0
                                                                                                                        в любом случае, спасибо огромное!
                                                                                                                        • 0
                                                                                                                          Спасибо за статью! Только сейчас столкнулся на аналогичный баг, вспомнил про статью и нашел решение проблемы :)
                                                                                                                          • +1
                                                                                                                            !!! Важно, данная бага вылезает не на всех версиях ПХП. Недавно при переносе класса с девелоп на продакшн — вылезла эта бага! Причем на девелопе все работало стабильно без нареканий, на продакшне был крайне удивлен. Огромное спасибо за статью, хорошо, что я ее читал и сразу вспомнил про нее, низко кланяюсь. На девелопе и продакшене стоит 5ка.

                                                                                                                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.