Хотя каждый из них устарел, замена Foreach занимает намного больше времени
С PHP 7.2 each
устарел. В документации говорится:
Предупреждение Эта функция была отключена с PHP 7.2.0. Полагаться на эту функцию крайне не рекомендуется.
Я работаю над адаптацией приложения электронной коммерции и конвертированием всех в while-each
цикл в (предположительно) эквивалент foreach
.
Как вы можете видеть ниже, я уже заменил все циклы reset
& while
эквивалентным foreach
.
Это в основном работало нормально. Тем не менее, у нас был клиент с очень длинным списком предметов в своей тележке, которые пытались проверить и жаловались, что она получает ошибку 502 с сервера. Я попытался воспроизвести это и обнаружил, что только ее тележка терпит неудачу, для загрузки страницы проверки занимает более 2 минут, а затем раз с 502. Затем я начал отлаживать большое количество файлов, которые я недавно изменил, проб и ошибок, до тех пор, пока я выяснили, что проблема связана с этим конкретным файлом и конкретной функцией. Всякий раз, когда я включаю первый foreach
цикл обратно в while
цикл, клиент может загрузить страницу оформления заказа менее чем за секунду. Переключение обратно в foreach
- и снова это займет минуты, но php истекает до завершения выполнения.
Я сделал выполнять тесты, конечно, на выходе этого foreach
против в while
петля (var_dump $products_id
и $this->contents
, например), и все они кажутся одинаковыми. Я уже переписал код, чтобы он работал плавно и оставался совместимым с PHP 7.2, но я до сих пор не могу понять, почему это происходит.
Это полная функция:
function get_content_type() {
$this->content_type = false;
if ( (DOWNLOAD_ENABLED == 'true') && ($this->count_contents() > 0) ) {
// reset($this->contents);
// while (list($products_id, ) = each($this->contents)) {
foreach(array_keys($this->contents) as $products_id) {
if (isset($this->contents[$products_id]['attributes'])) {
// reset($this->contents[$products_id]['attributes']);
// while (list(, $value) = each($this->contents[$products_id]['attributes'])) {
foreach ($this->contents[$products_id]['attributes'] as $value) {
$virtual_check_query = tep_db_query("select count(*) as total from " . TABLE_PRODUCTS_ATTRIBUTES . " pa, " . TABLE_PRODUCTS_ATTRIBUTES_DOWNLOAD . " pad where pa.products_id = '" . (int)$products_id . "' and pa.options_values_id = '" . (int)$value . "' and pa.products_attributes_id = pad.products_attributes_id");
$virtual_check = tep_db_fetch_array($virtual_check_query);
if ($virtual_check['total'] > 0) {
switch ($this->content_type) {
case 'physical':
$this->content_type = 'mixed';
return $this->content_type;
break;
default:
$this->content_type = 'virtual';
break;
}
} else {
switch ($this->content_type) {
case 'virtual':
$this->content_type = 'mixed';
return $this->content_type;
break;
default:
$this->content_type = 'physical';
break;
}
}
}
} elseif ($this->show_weight() == 0) {
// reset($this->contents);
// while (list($products_id, ) = each($this->contents)) {
foreach (array_keys($this->contents) as $products_id) {
$virtual_check_query = tep_db_query("select products_weight from " . TABLE_PRODUCTS . " where products_id = '" . $products_id . "'");
$virtual_check = tep_db_fetch_array($virtual_check_query);
if ($virtual_check['products_weight'] == 0) {
switch ($this->content_type) {
case 'physical':
$this->content_type = 'mixed';
return $this->content_type;
break;
default:
$this->content_type = 'virtual';
break;
}
} else {
switch ($this->content_type) {
case 'virtual':
$this->content_type = 'mixed';
return $this->content_type;
break;
default:
$this->content_type = 'physical';
break;
}
}
}
} else {
switch ($this->content_type) {
case 'virtual':
$this->content_type = 'mixed';
return $this->content_type;
break;
default:
$this->content_type = 'physical';
break;
}
}
}
} else {
$this->content_type = 'physical';
}
return $this->content_type;
}
Спасибо
EDIT: вот массив: https://pastebin.com/VawX3XpW
Проблема была протестирована и воспроизведена во всех конфигурациях, которые я пробовал:
1) Высококачественные окна 10 шт + WAMP (Apache 2.4 + MariaDB 10.2 + PHP 5. 6+/7 +/7. 1+/7. 2+)
2) Высокопроизводительный сервер CentOS/cPanel + Litespeed + MariaDB 10.1 + PHP 5. 6+
Просто чтобы подчеркнуть, я не хочу переписывать код или подражать each
а затем переписывать код, поскольку мы мало что узнаем из этого. Я просто пытаюсь найти логическое объяснение или способ решить/отладить эту тайну. Возможно, кто-то где-то столкнулся с такой проблемой и может пролить свет на это.
ОБНОВЛЕНИЕ 01/авг /2018
Я пытался отлаживать это в течение нескольких дней, в конце концов заметил что-то интересное. Я добавил "эхо-точки" и exit
в первый цикл foreach
while
цикле:
function get_content_type() {
$this->content_type = false;
if ( (DOWNLOAD_ENABLED == 'true') && ($this->count_contents() > 0) ) {
// reset($this->contents);
// while (list($products_id, ) = each($this->contents)) { echo '1 ';
foreach(array_keys($this->contents) as $products_id) { echo '1 ';
if (isset($this->contents[$products_id]['attributes'])) { echo '2 ';
// reset($this->contents[$products_id]['attributes']);
// while (list(, $value) = each($this->contents[$products_id]['attributes'])) {
foreach ($this->contents[$products_id]['attributes'] as $value) { echo '3 ';
$virtual_check_query = tep_db_query("select count(*) as total from " . TABLE_PRODUCTS_ATTRIBUTES . " pa, " . TABLE_PRODUCTS_ATTRIBUTES_DOWNLOAD . " pad where pa.products_id = '" . (int)$products_id . "' and pa.options_values_id = '" . (int)$value . "' and pa.products_attributes_id = pad.products_attributes_id");
$virtual_check = tep_db_fetch_array($virtual_check_query);
if ($virtual_check['total'] > 0) {
switch ($this->content_type) {
case 'physical':
$this->content_type = 'mixed'; echo '4 ';
return $this->content_type;
break;
default:
$this->content_type = 'virtual'; echo '5 ';
break;
}
} else {
switch ($this->content_type) {
case 'virtual':
$this->content_type = 'mixed'; echo '6 ';
return $this->content_type;
break;
default:
$this->content_type = 'physical'; echo '7 ';
break;
}
}
}
} elseif ($this->show_weight() == 0) {
// reset($this->contents);
// while (list($products_id, ) = each($this->contents)) {
foreach (array_keys($this->contents) as $products_id) {
$virtual_check_query = tep_db_query("select products_weight from " . TABLE_PRODUCTS . " where products_id = '" . $products_id . "'");
$virtual_check = tep_db_fetch_array($virtual_check_query);
if ($virtual_check['products_weight'] == 0) {
switch ($this->content_type) {
case 'physical':
$this->content_type = 'mixed'; echo '8 ';
return $this->content_type;
break;
default:
$this->content_type = 'virtual'; echo '9 ';
break;
}
} else {
switch ($this->content_type) {
case 'virtual':
$this->content_type = 'mixed'; echo '10 ';
return $this->content_type;
break;
default:
$this->content_type = 'physical'; echo '11 ';
break;
}
}
}
} else {
switch ($this->content_type) {
case 'virtual':
$this->content_type = 'mixed'; echo '12 ';
return $this->content_type;
break;
default:
$this->content_type = 'physical'; echo '13 ';
break;
}
}
} exit; //Exiting from the loop to check output
} else {
$this->content_type = 'physical';
}
return $this->content_type;
}
Когда я работал с циклом while
, вывод, который я получил, был всего лишь "1 13" один раз, что означает, что цикл работает только один раз и останавливается. Однако, когда я изменил его на foreach
, у меня появился длинный список "1 13 1 13 1 13...", то есть он многократно зацикливался. Я пошел дальше исследовать, если есть какая-либо разница между breaks
циклов while
и foreach
, и я до сих пор не нашел никакой вспомогательной информации. Затем я переписал последний break;
break 2;
и протестировали foreach
снова и на этот раз он, кажется, был запущен только один раз, так же, как когда он был в while
петля с break;
(не break 2;
) РЕДАКТИРОВАТЬ: просто уточнить - нет разницы между break
и break
. Они работают одинаково.
UPDATE # 2: я пересмотрел } elseif ($this->show_weight() == 0) {
к } elseif (2 == 0) {
и в while
цикл в настоящее время работает как много раз, как foreach
цикла. var_dump($this->show_weight());
результаты float 4466.54
. Вопрос до сих пор не имеет для меня никакого смысла.
еще раз спасибо
Ответы
Ответ 1
Это на самом деле очень простая алгоритмическая проблема, и это связано с тем, что когда show_weight()
равен 0
вы выполняете цикл одного и того же массива (редактирование: и на основе ваших комментариев сам show_weight()
также выполняет цикл одного и того же массива),
TL; DR С в while
все эти циклы разделяли один и тот же внутренний указатель и воздействуя друг друга. С foreach
каждый цикл является независимым, и поэтому он запускает больше итераций, следовательно, является проблемой производительности.
Так как пример стоит тысячи слов, мы надеемся, что следующий код упростит ситуацию:
<?php
$array = ['foo','bar','baz'];
foreach ( array_keys($array) as $key ) {
echo $array[$key],"\n";
foreach ( array_keys($array) as $key ) {
echo "\t",$array[$key],"\n";
}
}
echo "---------------\n";
while ( list($key,) = each($array) ) {
echo $array[$key],"\n";
reset($array);
while ( list($key,) = each($array) ) {
echo "\t",$array[$key],"\n";
}
}
Это приведет к выводу:
foo
foo
bar
baz
bar
foo
bar
baz
baz
foo
bar
baz
---------------
foo
foo
bar
baz
Как вы можете видеть, для массива размером 3, foreach
принимает итерации 3 ², while
занимает всего 3. Это ваша проблема с производительностью.
Почему в while
быстрее?
Потому что в конце второго (внутреннего) while
внутренний указатель $array
указывал бы на конец массива, поэтому первый (внешний) while
просто остановился.
С помощью foreach
, поскольку вы используете результат из двух разных вызовов array_keys
, вы используете 2 разных массива, которые не используют один и тот же внутренний указатель, поэтому нет причин для остановки цикла. Простое return
после второго (внутреннего) foreach
должно решить проблему.
Ответ 2
Как решение для резервного копирования, а как насчет падения в замене, выполняющего одно и то же действие?
function eachLegacy( &$array )
{
if( current( $array ) !== false )
{
$return = array( 1 => current( $array ), 'value' => current( $array ), 0 => key( $array ), 'key' => key( $array ) ); // Get the current values
next( $array ); // Advance the cursor
return $return;
}
return false;
}
Ответ 3
Я не совсем понимаю основную проблему здесь, но вы можете начать с оптимизации ваших циклов foreach с использованием подхода с ключевыми значениями:
foreach($this->contents as $products_id => $products_value) {
echo '1 ';
if (isset($products_value['attributes'])) {
echo '2 ';
foreach ($products_value['attributes'] as $value) {
echo '3 ';
...
использование return in switch приведет к разрыву всей функции, выйдите также. если вы хотите разбить цикл из оператора switch, используйте:
break NUMBER_OF_PARENT_STATEMENTS;
перерыв 2; сломает переключатель и родительский foreach
перерыв 3; сломает переключатель, а первый и второй родительский foreach
Ответ 4
В исходном коде, если на первой итерации возвращается true:
$this->show_weight() == 0
Код циклически проходит через $this-> содержимое, используя each(), устанавливая указатель массива в $this-> содержимое до конца. Поэтому, когда мы возвращаемся к первому оператору while(), он предполагает, что это уже сделано, потому что следующий вызов каждого ($this-> содержимого) возвращает false.