使用PHP PDO结合foreach和fetch

22

以下代码:

<?php
try {
    $dbh = new PDO("mysql:host=$hostname;dbname=$dbname", $username, $password);
    echo "Connection is successful!<br/>";
    $sql = "SELECT * FROM users";
    $users = $dbh->query($sql);
    foreach ($users as $row) {
        print $row["name"] . "-" . $row["sex"] ."<br/>";
    }
    foreach ($users as $row) {
        print $row["name"] . "-" . $row["sex"] ."<br/>";
    }
    $dbh = null;
}
catch (PDOexception $e) {
    echo "Error is: " . $e-> etmessage();
}

输出:

Connection is successful!

person A-male
person B-female

运行两次“foreach”不是我的目的,我只是好奇为什么使用了两个“foreach”语句,结果只输出一次?

以下是类似的情况:

<?php
try {
    $dbh = new PDO("mysql:host=$hostname;dbname=$dbname", $username, $password);
    echo "Connection is successful!<br/>";
    $sql = "SELECT * FROM users";
    $users = $dbh->query($sql);
    foreach ($users as $row) {
        print $row["name"] . "-" . $row["sex"] ."<br/>";
    }
    echo "<br/>";
    $result = $users->fetch(PDO::FETCH_ASSOC);
    foreach($result as $key => $value) {
        echo $key . "-" . $value . "<br/>";
    }
    $dbh = null;
}
catch (PDOexception $e) {
    echo "Error is: " . $e-> etmessage();
}

输出:

Connection is successful!

person A-male
person B-female

SCREAM: Error suppression ignored for
Warning: Invalid argument supplied for foreach()

但是当我从上面的代码中删除第一个"foreach"时,输出将变为正常:

<?php
try {
    $dbh = new PDO("mysql:host=$hostname;dbname=$dbname", $username, $password);
    echo "Connection is successful!<br/>";
    $sql = "SELECT * FROM users";
    $users = $dbh->query($sql);

    echo "<br/>";
    $result = $users->fetch(PDO::FETCH_ASSOC);
    foreach($result as $key => $value) {
        echo $key . "-" . $value . "<br/>";
    }
    $dbh = null;
}
catch (PDOexception $e) {
    echo "Error is: " . $e-> etmessage();
}

输出:

Connection is successful!

user_id-0000000001
name-person A
sex-male
为什么会发生这种情况?
5个回答

29

PDOStatement(在$users中)是一个向前的光标。这意味着,一旦被使用(第一个foreach迭代),它不会倒回到结果集的开头。

您可以在foreach之后关闭游标并再次执行语句:

$users       = $dbh->query($sql);
foreach ($users as $row) {
    print $row["name"] . " - " . $row["sex"] . "<br/>";
}

$users->execute();

foreach ($users as $row) {
    print $row["name"] . " - " . $row["sex"] . "<br/>";
}

或者您可以使用定制的 CachingIterator 进行缓存,使用完整的缓存:

$users       = $dbh->query($sql);

$usersCached = new CachedPDOStatement($users);

foreach ($usersCached as $row) {
    print $row["name"] . " - " . $row["sex"] . "<br/>";
}
foreach ($usersCached as $row) {
    print $row["name"] . " - " . $row["sex"] . "<br/>";
}

你可以在这个Gist链接中找到CachedPDOStatement类。使用缓存迭代器可能比将结果集存储到数组中更加合理,因为它仍然提供了包装的PDOStatement对象的所有属性和方法。

谢谢,虽然我不能完全理解,但你的例子对我以后肯定有用。 - Eathen Nutt
请再看一下我做的修改,我有一个打字错误:在第一个foreach后面使用$users->execute();将游标重置到结果集的开头。这很可能就是你要找的。缓存是一个更具体的解决方案,不是真正必要的,因为你可以执行$users->execute();。我只是为了通用示例而添加它。 - hakre
谢谢,第一个对我来说似乎更清晰,我会查看 PHP 手册中的 execute 函数。 - Eathen Nutt
是的,这更加直接。方法在这里:http://php.net/manual/zh/pdostatement.execute.php - hakre

18
执行同样的查询,只是为了获得已经有的结果,如被接受的答案所建议的那样,是一种疯狂的行为。添加额外的代码来执行这样一个简单的任务也没有意义。我不知道为什么人们会设计如此复杂和低效的方法,以使这些原始、最基本的操作变得更加复杂。
PDOStatement不是一个数组。 使用foreach语句遍历语句只是一种内部使用熟悉的单向while循环的语法糖。如果您想多次循环处理数据,请首先像普通数组一样选择它。
$sql = "SELECT * FROM users";
$stm = $dbh->query($sql);
// here you go:
$users = $stm->fetchAll();

然后您可以根据需要多次使用此数组:

foreach ($users as $row) {
    print $row["name"] . "-" . $row["sex"] ."<br/>";
}
echo "<br/>";
foreach ($users as $row) {
    print $row["name"] . "-" . $row["sex"] ."<br/>";
}

还要放弃那个try..catch。不要使用它,而是为PHP和PDO设置正确的错误报告


谢谢您的建议,学习 prepare 和 execute 是我接下来的步骤。 - Eathen Nutt
5
为什么你在回答中有两个for循环?我不是要挑毛病,只是想学习... - Norman
4
使用pdo->query()绝对不是使用这个库的错误方式。exec()和query()存在是为了被使用的。唯一错误的用法是尝试通过语句两次,他应该只使用fetchAll(),然后可以多次迭代数组。如果您坚持认为使用query()是错误的,请给出适当的理由。 - John
1
@showdev 你说得对。我不记得这个答案的上下文,但肯定它看起来很陌生,所以我编辑了它。 - Your Common Sense
你好,Common Sense,你能详细说明一下“放弃使用try...catch”这个评论吗?你有没有一个好的链接介绍在使用PHP/PDO时正确捕获错误的方法?谢谢。 - Neal Davis
显示剩余2条评论

10
因为你正在读取游标,而不是数组。这意味着你正在顺序地读取结果,当你到达末尾时,你需要将游标重置到结果的开头才能再次读取它们。
如果你确实想要多次阅读结果,你可以使用fetchAll将结果读取到真正的数组中,然后它将按照你的期望工作。

1
$users = $dbh->query($sql);
foreach ($users as $row) {
    print $row["name"] . "-" . $row["sex"] ."<br/>";
}
foreach ($users as $row) {
    print $row["name"] . "-" . $row["sex"] ."<br/>";
}

这里的$users是一个PDOStatement对象,您可以对其进行迭代。第一次迭代会输出所有结果,第二次迭代不会有任何输出,因为您只能对结果进行一次迭代。这是因为数据正在从数据库中流式传输,并且使用foreach迭代结果本质上就是缩写为:

while ($row = $users->fetch()) ...

完成这个循环后,您需要在数据库端重置游标,然后才能再次循环。
$users = $dbh->query($sql);
foreach ($users as $row) {
    print $row["name"] . "-" . $row["sex"] ."<br/>";
}
echo "<br/>";
$result = $users->fetch(PDO::FETCH_ASSOC);
foreach($result as $key => $value) {
    echo $key . "-" . $value . "<br/>";
}

这里所有的结果都由第一个循环输出。调用fetch将返回false,因为您已经耗尽了结果集(请参见上文),所以在尝试循环遍历false时会出现错误。

在最后一个示例中,您只是获取第一行结果并对其进行循环。


-2
$row = $db->getAllRecords(DB_TBLPREFIX . '_payplans', '*', ' AND ppid = "' . $myid . '"');
foreach ($row as $value) {
    $bpprow = array_merge($bpprow, $value);
}

这是基于 PHP 函数的,您可以全局使用这些数据。


1
没有 PHP 函数 getAllRecords()(也不应该有) - Your Common Sense
这是一个自制的函数,它将从数据库中获取所有记录。 - Mubashir Majeed
是的。但这个答案对任何人都没有帮助,因为getAllRecords不是一个php函数。而且,这个函数非常糟糕,因为它允许SQL注入。 - Your Common Sense
好的,我会修改它。 - Mubashir Majeed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接