PHP foreach循环使用一个条目两次

wsj*_*ter 3 php mysql arrays foreach pdo

我只是尝试使用PHP和PDO与MySQL数据库一起工作,我有点难以理解为什么在获得结果后,将它们正确地存储在多维数组中并循环通过它们输出其中一个数组数据两次.

基本上这里是获取数据的查询:

SELECT b.Price, b.ImgURL, m.Name, f.ID, f.Family, f.URL FROM Products AS b INNER JOIN Manufacturers AS m ON m.ID = b.Manufacturer INNER JOIN FamilyLookUp AS l ON l.Product = b.ID INNER JOIN Families AS f ON f.ID = l.Family GROUP BY f.ID ORDER BY b.Price ASC
Run Code Online (Sandbox Code Playgroud)

我希望这能为每个系列返回1行,它在PHPMyAdmin查询中以及print_r()结果时都能正常工作.

然后我存储在:

$families[] = array('ID' => $f['ID'], 'Manufacturer' => $f['Name'], 'Family' => $f['Family'], 'URL' => $f['URL'], 'IMG' => $f['ImgURL'], 'Price' => $f['Price'], 'ScentCount' => 0);
Run Code Online (Sandbox Code Playgroud)

当执行print_r()并且只使用foreach循环循环时回显出每个条目的ID它返回1234567(所有7个系列ID)

然后我运行另一个查询:

try{
$sqlCmd = "SELECT COUNT(*) FROM FamilyLookUp WHERE Family=:fID";
$s = $pdo->prepare($sqlCmd);
foreach($families as &$fam){
$s->bindValue(':fID', $fam['ID']);
$s->execute();
$fam['ScentCount'] = $s->fetchColumn();
}
}
Run Code Online (Sandbox Code Playgroud)

这也可以获得正确的计数,并将它们正确地存储在数组中,以获得每个系列中的项目数.所以到目前为止一切都很好.

我遇到问题:

foreach($families as $fam):
        ?>

        <div class="product-listing">
        <?php echo $fam['ID']; ?>
            <div class="product-listing-image">
                <a href="<?php echo $fam['URL']; ?>"><img alt="" src="<?php echo $fam['IMG']; ?>"></a>
            </div>
            <div class="product-listing-details">

                <a href="<?php echo $fam['URL']; ?>"><h3><?php echo strtoupper($fam['Manufacturer']); if($fam['Family'] != ""){ echo strtoupper(' - ' . $fam['Family']);} ?></h3></a>
                <?php if($fam['ScentCount'] == 1): ?>
                <span class="product-scent-count"><?php echo $fam['ScentCount']; ?> Scent</span>
                <span class="product-price-value">£<?php echo $fam['Price']/100; ?></span>
                <?php elseif($fam['ScentCount']>1): ?>
                <span class="product-scent-count"><?php echo $fam['ScentCount']; ?> Scents</span>
                <span class="product-price-value">From £<?php echo $fam['Price']/100; ?></span>
                <?php endif;?>
            </div>
        </div>

        <?php
            endforeach;
        ?>
Run Code Online (Sandbox Code Playgroud)

执行此操作后,它会正确输出前6个数据系列,但由于某种原因,它会输出第6个副本而不是实际的第7个副本.当在foreach循环开始之前对行中的所有数据执行print_r时,它返回所有正确的数据,但在foreach循环中,在第7个最初正确的数组的位置变为1个重复数组.

任何建议都会很棒.

编辑Kohloth的答案(print_r直接跟着foreach vardump):

Array
(
    [0] => Array
        (
            [ID] => 1
        )

    [1] => Array
        (
            [ID] => 7
        )

    [2] => Array
        (
            [ID] => 2
        )

    [3] => Array
        (
            [ID] => 3
        )

    [4] => Array
        (
            [ID] => 4
        )

    [5] => Array
        (
            [ID] => 6
        )

    [6] => Array
        (
            [ID] => 5
        )

)
            array(7) {
  ["ID"]=>
  string(1) "1"
}

    array(7) {
  ["ID"]=>
  string(1) "7"
}

    array(7) {
  ["ID"]=>
  string(1) "2"
}

    array(7) {
  ["ID"]=>
  string(1) "3"
}

    array(7) {
  ["ID"]=>
  string(1) "4"
}

    array(7) {
  ["ID"]=>
  string(1) "6"
}

    array(7) {
  ["ID"]=>
  string(1) "6"
}
Run Code Online (Sandbox Code Playgroud)

tri*_*cot 10

这就是发生的事情:

在此循环的最后一次迭代中:

foreach($families as &$fam){
    $s->bindValue(':fID', $fam['ID']);
    $s->execute();
    $fam['ScentCount'] = $s->fetchColumn();
}
Run Code Online (Sandbox Code Playgroud)

... $ fam指的是$系列数组的最后一个元素.

然后当你的下一个循环开始时:

foreach($families as $fam){
Run Code Online (Sandbox Code Playgroud)

... $ fam点不会改变的内存位置,它仍然锁定到$ families数组的最后一个元素.因此在第一次迭代中,第一个元素的内容被复制到$ fam中,即在最后一个条目中,然后在第二次迭代时,第二个值被覆盖在那里,依此类推.当最后一次迭代开始时,最后一个元素包含一个 - 但是最后一个值,并且它被......本身覆盖,这又是一个但是最后一个值.

错误报告引发了同样的问题,给出的答案是这是预期的行为.在回答其中一个重复的错误报告时,这说明了这一点:

目前的实施是一致的.当然,不是很有用,但在这里随意破坏引用会不一致.
PHP没有块作用域,打破引用会在这里引入一个特殊情况的块作用域.

这个博客用漂亮的插图解释了相同的行为.

解决方案是在第二个循环中使用另一个新变量,如下所示:

foreach($families as $fam2){
Run Code Online (Sandbox Code Playgroud)

或者,对于您可能使用$ fam的任何其他代码更安全,就是unset($fam)在第二个循环之前,如下所示:

unset($fam);
foreach($families as $fam){
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为在foreach循环开始时,变量从头开始重新创建,因此指向它自己的新内存位置.

对文档foreach有一个有关此问题的警告,并建议unset:

警告

$value即使在foreach循环之后,a 和最后一个数组元素的引用仍然存在.建议将其销毁unset().

所有这些都很尴尬,当你阅读相关"bug"报告的回复时,很明显你并不是唯一一个碰到这种意想不到的副作用的人.因此,我想强调这一点:

避免使用危险品 &

几乎没有真正需要使用此&前缀.通过避免它,这些类型的奇怪副作用将属于过去.

您的代码可以不经&如下重写:

foreach($families as $i => $fam){
    $s->bindValue(':fID', $fam);
    $s->execute();
    // use the index to put the value in place in the array:
    $families[$i]['ScentCount'] = $s->fetchColumn();
}
Run Code Online (Sandbox Code Playgroud)

请注意,它也不会损害代码的可读性.