PDO查询的速记法

3

目前,为了使用PDO执行查询,我使用以下代码:

$sql = "SELECT * FROM myTable WHERE id = :id";
$stmt = $conn->prepare($sql);
$stmt->bindParam(':id', $id);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);

经过一些研究,我发现可以更简单地执行相同的命令:

$stmt_test = $conn->prepare("SELECT * FROM status WHERE status_id = ?");
$stmt_test->execute([$id])->fetchAll(PDO::FETCH_ASSOC);
$result = $stmt_test->fetchAll(PDO::FETCH_ASSOC);

接下来我认为可以用以下代码进一步缩短:

$stmt_test = $conn->prepare("SELECT * FROM status WHERE status_id = ?");
$result = $stmt_test->execute([$id])->fetchAll(PDO::FETCH_ASSOC);

但我遇到了以下错误:

致命错误:在第20行的/home/.../index.php上调用一个非对象的成员函数fetchAll()

问题:为什么会出现这个错误?在我的理解中,$stmt_test->execute([$id]) 应该首先执行,然后执行 ->fetchAll(PDO::FETCH_ASSOC) 并将数组返回给 $result,但由于出现了错误,我的逻辑肯定有问题。我做错了什么?此外,有没有更好的缩写方法来执行前面的查询?


可能是重复的问题:PDO - Call to a member function fetch() on a non-object - johannes
@johannes 即使错误的名称相似,导致错误的情况是不同的。 - Webeng
啊,我没有仔细阅读,稍后会回答。 - johannes
@johannes 一切都好 :) - Webeng
可能是PDO:在非对象上调用成员函数fetch()?的重复问题。 - miken32
4个回答

7
所以你已经得到了关于“为什么我会得到这个错误”的答案,但却没有得到“PDO查询简写”的答案。
对此,我们需要一些称为“编程”的东西。
编程的一个有趣之处在于,我们不受现有工具的限制,就像其他职业一样。通过编程,我们总是可以创建自己的工具,然后开始使用它,而不是整套旧工具。
面向对象编程在这方面特别擅长,因为我们可以拿一个现有的对象,只需添加一些功能,其余部分保持原样。
例如,想象一下我们想要一种缩写方式来运行PDO中的预处理查询。我们所需要做的就是用一个新的缩写方法扩展 PDO对象。最难的部分是给新方法命名。
剩下的很简单:你只需要几行代码。
class MyPDO extends PDO
{
    public function run($sql, $bind = NULL)
    {
        $stmt = $this->prepare($sql);
        $stmt->execute($bind);
        return $stmt;
    }
}

这是您所需要的所有代码。您可以将其存储在与数据库凭据相同的文件中。请注意,此添加不会以任何方式影响您现有的代码-它仍然完全相同,您可以像往常一样继续使用所有现有的PDO功能。
现在您只需更改PDO构造函数中的2个字母,如下所示:
$conn = new MyPDO(...the rest is exactly the same...);

立即开始使用你的全新工具:

$sql = "SELECT * FROM myTable WHERE id = :id";
$result = $conn->run($sql, ['id' => $id])->fetchAll(PDO::FETCH_ASSOC);

或者,稍微进行一些优化:
$result = $conn->run("SELECT * FROM myTable WHERE id = ?", [$id])->fetchAll();

你可以始终为所有内容设置默认的获取模式,对于单个变量而言,没有必要使用命名占位符。这使得这段代码与已接受的答案相比成为了真正的速记

$stmt_test = $conn->prepare("SELECT * FROM status WHERE status_id = ?");
$stmt_test->execute([$id]);
$result = $stmt_test->fetchAll(PDO::FETCH_ASSOC);

甚至是到目前为止你得到的最好答案,

$result = $conn->prepare("SELECT * FROM status WHERE status_id = ?");
$result->execute([$id]);

更不用说后者并不总是可用的,因为它仅适用于获取数组。而使用一个真正的简写,则可以使用任何结果格式:

$result = $conn->run($sql, [$id])->fetchAll(); // array
$result = $conn->run($sql, [$id])->fetch(); // single row
$result = $conn->run($sql, [$id])->fetchColumn(); // single value
$result = $conn->run($sql, [$id])->fetchAll(PDO::FETCH_*); // dozens of different formats

1
谢谢。这个标记实际上不是为我而设的,而是为其他寻找PDO函数速记的开发人员准备的。因为我想,我的解决方案是唯一真正回答问题而不仅仅承认问题存在的方案。 - Your Common Sense
1
@JasonK 这不是一个包装类,而是一个类扩展 - 对于面向对象编程来说就像人类呼吸一样自然。你会把呼吸称为过度设计吗? - Your Common Sense
@YourCommonSense,你是否对pdo/mysql中的事务非常了解?以下问题急需一位pdo巫师:https://dev59.com/n1oU5IYBdhLWcg3wV1-8 - Webeng
@YourCommonSense 我通常会给正确的答案点赞,而你的回答我也认为是正确的。下次我会更加关注提问者想要什么,比如在这个问题中,他/她问是否可以缩短上面的查询语句。我失败了,实际上回答了与提问者发布的相同的查询语句。你认为你掌握了这个主题吗?你认为缩短解决方案就是把三行代码变成30行吗?使用类等编写可能是提问者应该做的事情,但真的是你缩短的答案吗?不要太傲慢了,伙计,这不好。 - user2560539
@PeterDarmis,你的结论有三个错误前提:1. OP想要缩短运行所有查询的方式,而不仅仅是这一个。2. 第一次你不仅被提供了相同的代码,而且是失败的代码。3. 在我的解决方案中没有30行代码,只有9行,最重要的是,只需要编写一次。考虑到它每个查询可以为你节省2行代码,你只需编写5个查询后就开始获得利润。所有其他查询都将免费缩短。这就是所谓的编程。 - Your Common Sense
显示剩余4条评论

4

$stmt_test->execute([$id]) 返回一个布尔值。这意味着

$result = $stmt_test->execute([$id])->fetchAll(PDO::FETCH_ASSOC);

不是有效的。相反,您应该这样做

$stmt_test->execute([$id]);
$result = $stmt_test->fetchAll(PDO::FETCH_ASSOC);

完美的答案。当时间限制到期时,我会接受这个答案。没有意识到$stmt_test返回一个布尔值,因此fetchAll被应用于该返回值而不是$stmt_test本身。 - Webeng
1
@Webeng 我不会称这个答案为完美,因为它没有让你的代码变得更短。 - Your Common Sense

1
你所遇到的错误源于PDO的设计方式。 PDOStatement :: execute()不返回语句,而是返回表示成功的布尔值。 因此,您想要的快捷方式是不可能的。
请参见http://php.net/manual/en/pdostatement.execute.php中的函数定义。
此外,让我补充一点,forEach()通常(并非总是)是一个代码气味,并且需要相对较多的内存,因为它必须将所有行存储为PHP值。

哦,所以你建议我改成 while ($row = $stmt_test->fetch(PDO::FETCH_ASSOC)){...} 这样我就不需要用上面的代码中的 foreach($result as ...)了吗? - Webeng
PDOStatement实现了可遍历接口,因此您可以使用foreach ($stmt_test as ...)(虽然我必须承认,我不确定这使用哪种模式,可能是PDO::FETCH_BOTH)来进行迭代。通过这种方法,您始终只在内存中拥有当前元素,并且只需要迭代一次而不是两次。 - johannes
@webeng 你可以在实例化时通过指定构造函数的第四个选项参数来指定获取模式,例如 [PDO::ATTR_DEFAULT_FETCH_MODE=> PDO::FETCH_ASSOC] - miken32
@YourCommonSense,不是foreach的问题,是fetchAll的问题。你可能会将迭代器传递到模板引擎中,或者进行预处理,然后传递最终结果或任何你需要的东西。 - johannes
对我来说,从数据库层使用迭代器是一种代码异味。我更喜欢在模板开始之前关闭所有数据库资源。特别是因为在常规网页上显示的合理数量的数据不会对内存消耗产生任何可见影响。 - Your Common Sense
显示剩余2条评论

0

我相信PDO的execute()方法返回true或false。正如错误信息所提示的那样:fetchAll()需要一个对象。使用三行代码可能是最短的方式。

另一个选择是使用像propel这样的ORM,它非常流畅,可以节省很多时间。


建议工具的答案总是从适用于提问者需求的现实生活示例中受益。否则,它们将不会得到关注。 - Your Common Sense

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