PHP foreach — как легко переполнить память даже с крошечным массивом

PHP foreach пример

Рассмотрим простой пример перебора элементов одномерного массива с 4мя элементами

<?php
$elements = [1,2,3,4];
echo PHP_EOL;
foreach ($elements as $element) {
    echo $element;
}
echo PHP_EOL;
echo json_encode($elements);

при выполнении все ожидаемо:

1234
[1,2,3,4]

Теперь, перейдем к какой то задаче с пользой, например, если встречается какой то элемент, необходимо добавить его копию в исходный массив

<?php
$elements = [1,2,3,4];
echo PHP_EOL;
foreach ($elements as $element) {
    if ($element === 3) {
        $copy = $element;
        $elements[] = $copy;
    }
    echo $element;
}
echo PHP_EOL;
echo json_encode($elements);

В результате получаем:

1234
[1,2,3,4,3]

А теперь, рассмотрим вполне реальную ситуацию, когда в цикле с элементами массива нужно что то сделать и сохранить это:

<?php
$elements = [1,2,3,4];
echo PHP_EOL;
foreach ($elements as &$element) {
    // do something with every element
    if ($element === 3) {
        $copy = $element;
        $elements[] = $copy;
    }
    echo $element;
}
echo PHP_EOL;
echo json_encode($elements);

И вот тут мы сталкиваемся с проблемой похлеще рекурсии без предусмотренного выхода из нее:

123.....333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 134217736 bytes) in /php on line 7
PHP Stack trace:
PHP   1. {main}() /php:0

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 134217736 bytes) in /php on line 7

Call Stack:
    0.0025     392288   1. {main}() /php:0

Это последствия передачи массива по ссылке

И лучше обойти это с помощью временного массива:

<?php
$elements = [1,2,3,4];
echo PHP_EOL;
$additionalElements = [];
foreach ($elements as &$element) {
    // do something with every element
    if ($element === 3) {
        $additionalElements[] = $element;
    }
    echo $element;
}
echo PHP_EOL;
echo json_encode(array_merge($elements, $additionalElements));

И, необходимый нам результат:

1234
[1,2,3,4,3]