В то время как каждый устарел, замена 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;
}

Спасибо

РЕДАКТИРОВАТЬ: вот массив:
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 breaks. Они работают так же.

ОБНОВЛЕНИЕ № 2:
Я пересмотрел } elseif ($this->show_weight() == 0) { в } elseif (2 == 0) { и while цикл теперь выполняется столько раз, сколько foreach петля.
var_dump($this->show_weight()); Результаты float 4466.54,
Проблема все еще не имеет никакого смысла для меня.

еще раз спасибо

11

Решение

Это на самом деле очень простая алгоритмическая проблема, и это связано с тем, что когда 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 должен решить проблему.

15

Другие решения

Как решение для резервного копирования, как насчет падения замены при выполнении того же действия?

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;
}
2

Я не совсем понимаю основную проблему здесь, но вы можете начать с оптимизации циклов 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

1

В исходном коде, если следующее возвращает true в первой итерации:

$this->show_weight() == 0

Код проходит по $ this-> content с помощью each (), устанавливая указатель массива на $ this-> contents до конца. Поэтому, когда мы возвращаемся к первому оператору while (), он предполагает, что он уже выполнен, потому что следующий вызов каждого ($ this-> contents) возвращает false.

1
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector