С 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;
}
Спасибо
РЕДАКТИРОВАТЬ: вот массив:
https://pastebin.com/VawX3XpW
Проблема была проверена и воспроизведена на всех конфигурациях, которые я пробовал:
1) High End Windows 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 / Aug / 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
с и foreach break
s. Они работают так же.
ОБНОВЛЕНИЕ № 2:
Я пересмотрел } elseif ($this->show_weight() == 0) {
в } elseif (2 == 0) {
и while
цикл теперь выполняется столько раз, сколько foreach
петля.
var_dump($this->show_weight());
Результаты float 4466.54
,
Проблема все еще не имеет никакого смысла для меня.
еще раз спасибо
Это на самом деле очень простая алгоритмическая проблема, и это связано с тем, что когда 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
, так как вы используете результат 2 разных вызовов array_keys
, вы используете 2 разных массива, которые не используют один и тот же внутренний указатель, поэтому нет причин останавливать цикл. Просто return
после второго (внутреннего) foreach
должен решить проблему.
Как решение для резервного копирования, как насчет падения замены при выполнении того же действия?
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;
}
Я не совсем понимаю основную проблему здесь, но вы можете начать с оптимизации циклов 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 ';
...
использование возврата в переключателе нарушит всю функцию, также завершится.
если вы хотите разорвать цикл из оператора switch, используйте:
break NUMBER_OF_PARENT_STATEMENTS;
перерыв 2; сломает переключатель и родительский foreach
перерыв 3; сломает переключатель и первый и второй родительский foreach
В исходном коде, если следующее возвращает true в первой итерации:
$this->show_weight() == 0
Код проходит по $ this-> content с помощью each (), устанавливая указатель массива на $ this-> contents до конца. Поэтому, когда мы возвращаемся к первому оператору while (), он предполагает, что он уже выполнен, потому что следующий вызов каждого ($ this-> contents) возвращает false.