PHP - foreach теряет ссылку с нулевым оператором объединения - PullRequest
3 голосов
/ 17 марта 2020

Q1: я думаю, ?? ничего не сделает, когда:

$a = [1, 2];
foreach ($a ?? [] as &$v) {
    $v++;
}
var_dump($a);

Но почему?

array(2) {
  [0]=>
  int(1)
  [1]=>
  int(2)
}

Q2: Это более странно:

foreach ($a = [1, 2] as &$v) {
    $v++;
}
var_dump($a);
// output
array(2) {
  [0]=>
  int(1)
  [1]=>
  int(2)
}

Мое мышление: я думаю, что выражения не являются ссылочными, но foreach ловит ошибку или как-то еще и затем делает копию. Работающие ссылки:

$a = 1;
$c = &$a;

Не работают:

$a = 1;
$c = &($a);
$c = &($a ?? []);
$c = &($a + 1);

Дос ?? сделать копию? Я просто не хочу обернуть foreach if (isset($a)), если $a равно нулю и foreach не удастся.

Ответы [ 3 ]

3 голосов
/ 17 марта 2020

TL; DR Для вашего случая вы можете рассмотреть возможность использования оператора null coalesce следующим образом:

$a = $a ?? [];
foreach ($a as &$v) { ... }

Или вообще не использовать ссылки, используя либо array_map() или с помощью ключей для внесения изменений в базовый массив.

Q1

$a = [1, 2];
foreach ($a ?? [] as &$v) {
    $v++;
}
var_dump($a);

Оператор coalesce использует копию исходного массива, а затем применяет правый операнд если null. Следовательно, итерация выполняется для копии исходного массива.

Вы можете сравнить это со следующим:

$a = [1, 2];
$x = $a ?? [];
$x[1] = 4;
var_dump($a); // [1, 2]

Code Insight

compiled vars:  !0 = $a, !1 = $v
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   8     0  E >   ASSIGN                                                   !0, <array>
   9     1        COALESCE                                         ~3      !0
         2        QM_ASSIGN                                        ~3      <array>
         3      > FE_RESET_RW                                      $4      ~3, ->8
... rest of looping code

Первый операнд FE_RESET_RW - это переменная ha sh, которая будет перебираться, и вы можете видеть, что она ~3 вместо !0 ($a в вашем коде), что вы и ожидали.

Q2

foreach ($a = [1, 2] as &$v) {
    $v++;
}

Здесь происходит то, что возвращаемое значение присваивания $a = [1, 2] используется в качестве массива для итерации.

Вы можете сравнить это поведение с чем-то как это:

$x = $a = [1, 2];
$x[0] = 4; // modify in-place
var_dump($a); // [1, 2]

Code Insight

compiled vars:  !0 = $a, !1 = $v
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   3     0  E >   ASSIGN                                           $2      !0, <array>
         1      > FE_RESET_RW                                      $3      $2, ->6
... rest of looping code

Опять же, $2 - это первый операнд FE_RESET_RW, который является результатом присваивания, и поэтому итерация не произойдет с !0 ($a в вашем коде).

1 голос
/ 17 марта 2020

Вы можете использовать синтаксис расширенного массива для получения индекса, а затем использовать его для разыменования исходного значения массива:

$a = [1, 2];
foreach ($a ?? [] as $i => $v) {
    ++$a[$i];
}
var_dump($a);

Но обратите внимание, что в любом случае это, вероятно, бесполезно, потому что если $a isn ' Если установить (чтобы квалифицировать ??), то l oop выполнит ноль итераций, а $a все равно будет не установлен для var_dump(). (Если это не то, что вам нужно, я полагаю ...)

0 голосов
/ 17 марта 2020

Я просто не хочу заключать foreach в if (isset($a)), если $a равно нулю и foreach не удастся.

Это неизбежно, если у вас нет ' t инициализировал переменные с правильным типом, но вот некоторая хитрость в служебной функции, использующей передачу по ссылке, возврат по ссылке и значение по умолчанию:

function &test(&$array=[]) {
    return $array;
}

$a = [1, 2];

foreach (test($a) as &$v) {
    $v++;
}

Не генерирует ошибки, если $a не установлено и не l oop, однако в приведенном выше примере это дает:

array(2) {
  [0]=>
  int(2)
  [1]=>
  &int(3)
}

In PHP массивы назначаются по значению (копии присваивания), так что если $a !== null, то $a ?? [] возвращает значение $a или [1, 2]. Таким образом, $a не изменяется ссылкой на значения этого значения с использованием &$v.

Объекты назначаются ссылкой, поэтому в этом случае возвращается ссылка, а исходный объект изменяется, если только $a не установлено. Тогда вы, очевидно, получите:

Примечание: неопределенная переменная: a

$a = (object)[1, 2];

foreach ($a ?? [] as &$v) {
    $v++;
}
var_dump($a);

Это дает:

object(stdClass)#1 (2) {
  ["0"]=>
  int(2)
  ["1"]=>
  &int(3)
}

От Назначение по ссылке :

Также поддерживается назначение по ссылке с использованием синтаксиса "$var = &$othervar; ". Назначение по ссылке означает, что обе переменные в конечном итоге будут указывать на одни и те же данные и ничего нигде не копируется.

В этих случаях все они являются выражениями и на них нельзя ссылаться:

$c = &($a);
$c = &($a ?? []);
$c = &($a + 1);

Если это просто упражнение, это хорошо, но если вы пытаетесь решить конкретную проблему c, тогда вам нужно описать эту более широкую проблему.

...