Пользователь
0,0
рейтинг
23 января 2012 в 17:48

Разработка → Подводный камень в foreach($items as &$item)

PHP*
Многие любят писать такие конструкции в том или ином виде, каждый сталкивался:
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. И все они когда-то обсуждались. Однако, этот случай, судя по моему опыту, так распространён на практике, что заслуживает отдельного внимания.
@Stdit
карма
51,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (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
          unset($a,$b,$c.....);
  • +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ка.

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