yii框架遇到的数组引用循环问题

目前所在的公司用的yii版本是2.0.3,最近上线了一个新功能后,有个地方突然出现了问题,出错的地方的代码简化如下:

1
2
3
4
5
6
$userIds = ['1', '2'];
foreach ($userIds as &$userId) {

}
$count = \common\entities\User::find()->where(['user_id' => $userIds])->count();
// var_dump($userIds);

这里是用了yii的orm查询了数据,但是最后发现$userIds数组的最后一个元素被串改了,导致接下来的逻辑就出现了错误。在我把foreach循环中的引用&去掉后,$userIds就没有被修改覆盖了,所以基本可以断定yii框架中应该是有地方改动了$userIds,同时也跟foreach循环使用&有关。调试进去yii的框架代码中,追踪到这个方法会引起变量的改动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public function buildInCondition($operator, $operands, &$params)
{
if (!isset($operands[0], $operands[1])) {
throw new Exception("Operator '$operator' requires two operands.");
}

list($column, $values) = $operands;

if ($values === [] || $column === []) {
return $operator === 'IN' ? '0=1' : '';
}

if ($values instanceof Query) {
return $this->buildSubqueryInCondition($operator, $column, $values, $params);
}

$values = (array) $values;

if (count($column) > 1) {
return $this->buildCompositeInCondition($operator, $column, $values, $params);
}

if (is_array($column)) {
$column = reset($column);
}
foreach ($values as $i => $value) {
if (is_array($value)) {
$value = isset($value[$column]) ? $value[$column] : null;
}
if ($value === null) {
$values[$i] = 'NULL';
} elseif ($value instanceof Expression) {
$values[$i] = $value->expression;
foreach ($value->params as $n => $v) {
$params[$n] = $v;
}
} else {
$phName = self::PARAM_PREFIX . count($params);
$params[$phName] = $value;
$values[$i] = $phName;
}
}
if (strpos($column, '(') === false) {
$column = $this->db->quoteColumnName($column);
}

if (count($values) > 1) {
return "$column $operator (" . implode(', ', $values) . ')';
} else {
$operator = $operator === 'IN' ? '=' : '<>';
return $column . $operator . reset($values);
}
}

为了进一步验证问题,写了一段简化代码去模拟,最终顺利找到了问题所在:

1
2
3
4
5
6
7
8
9
10
11
12
function test($data) {
foreach ($data as $k => $v) {
$data[$k] = $k;
}
}

$data = ['1', '2'];
foreach ($data as &$item) {

}
test($data);
var_dump($data); // 输出:['1', 1]

查阅了官方文档中对foreach的使用描述,里面有谈到一个使用警告:数组最后一个元素的 $value 引用在 foreach 循环之后仍会保留,建议使用 unset() 来将其销毁。所以在使用foreach引用&变量的时候,最好能在循环后unset变量,避免不必要的问题。
yii框架开发组应该也意识到了这个问题,经过测试,在2.0.10版中这个问题已经被修复了。