如何使用mysqli准备好的语句?

Posted

技术标签:

【中文标题】如何使用mysqli准备好的语句?【英文标题】:How to use mysqli prepared statements? 【发布时间】:2012-03-26 14:59:41 【问题描述】:

我正在尝试准备好的语句,但下面的代码不起作用。我收到错误消息:

致命错误:在非对象上调用成员函数 execute() /var/www/prepared.php 第 12 行

<?php

    $mysqli = new mysqli("localhost", "root", "root", "test");
    if ($mysqli->connect_errno) 
        echo "Failed to connect to MySQL: " . $mysqli->connect_error;
    

    $stmt = $mysqli->prepare("INSERT INTO users (name, age) VALUES (?,?)");

    // insert one row
    $stmt->execute(array('one',1));

    // insert another row with different values
    $stmt->execute(array('two',1));
?>

另外,我需要为准备好的语句使用 mysqli 吗?谁能指出一个完整的示例,说明从连接到插入到选择以及错误处理的准备语句?

【问题讨论】:

“谁能给我一个完整的例子,说明从连接到插入到选择错误处理的准备语句” 是的。 php.net/manual/en/mysqli.prepare.php。注意 "mysqli_prepare() 行返回一个语句对象或 FALSE 如果发生错误。". 【参考方案1】:

来自mysqli::prepare docs:

在执行语句或获取行之前,必须使用 mysqli_stmt_bind_param() 和/或 mysqli_stmt_bind_result() 将参数标记绑定到应用程序变量。

bind_param docs.

即:

$name = 'one';
$age  = 1;

$stmt = $mysqli->prepare("INSERT INTO users (name, age) VALUES (?,?)");

// bind parameters. I'm guessing 'string' & 'integer', but read documentation.
$stmt->bind_param('si', $name, $age);

// *now* we can execute
$stmt->execute();

【讨论】:

哦,还有@Tomalak 建议首先检查$stmt 是否为FALSE(即发生错误)。【参考方案2】:

我还需要为准备好的语句使用 mysqli。任何人都可以指出一个完整的示例,说明从连接到插入到选择以及错误处理的准备语句

您也可以使用我更喜欢的 PDO。事实上,您在代码示例中似乎混淆了 PDO 和 Mysqli。

$db = new PDO($dsn, $user, $pass);
$stmt = $db->prepare("INSERT INTO users (name, age) VALUES (?,?)");
$stmt->execute(array($name1, $age1));
$stmt->execute(array($name2, $age2));

与 mysqli 不同,您不必调用单独的绑定函数,但如果您喜欢/想要/需要使用该功能,则可以使用它。

关于 PDO 的另一个有趣的事情是命名占位符,它可以在复杂的查询中减少混乱:

$db = new PDO($dsn, $user, $pass);
$stmt = $db->prepare("INSERT INTO users (name, age) VALUES (:name,:age)");
$stmt->execute(array(':name' => $name1, ':age' => $age1));
$stmt->execute(array(':name' => $name2, ':age' => $age2));

【讨论】:

【参考方案3】:

谁能给我一个完整的例子,说明从连接到插入到选择以及错误处理的准备语句?

这个问题很老,但直到最近我才完成了一些研究,产生了一系列完全涵盖该请求的文章,所以你去吧:

连接

mysqli 连接的重要性经常被忽视,被贬低为一行。而正确的连接代码可以解决许多问题,从安全性到可用性。

鉴于您的代码是通常的 PHP 程序,这里有一个简单的 mysqli 连接代码将包含在您的脚本中:

$host = '127.0.0.1';
$db   = 'test';
$user = 'root';
$pass = '';
$charset = 'utf8mb4';

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try 
    $mysqli = new mysqli($host, $user, $pass, $db);
    $mysqli->set_charset($charset);
 catch (\mysqli_sql_exception $e) 
     throw new \mysqli_sql_exception($e->getMessage(), $e->getCode());

unset($host, $db, $user, $pass, $charset); // we don't need them anymore

完整的解释可以在我的文章How to connect properly using mysqli 中找到(以及许多有用的提示),但只是一个小引用来突出最重要的部分:

为连接设置正确的字符集将消除整类错误,例如奇怪的字符/问号而不是您的数据、空的 json_encode() 输出、存储表情符号的问题等。 设置正确的错误报告模式将消除神秘的错误消息,如 mysqli_fetch_assoc() 需要参数... / 调用成员函数 bind_param()...,而是为您提供来自 MySQL 的实际错误消息。 安全性不是笑话,不应该有机会向外界泄露您的数据库详细信息

插入

Insert query比较简单,其他答案已经介绍过了。

您只需用问号替换查询中的所有变量(以及周围的引号!),然后准备查询,然后将所有变量及其类型推入bind_param(),最后执行查询。

只是一个快速提示:MySQL 很乐意将所有变量作为字符串接受,所以不要为某个变量寻找正确的类型,只需使用 "s" 即可。

所以基本上插入是这样的

$sql = "INSERT INTO users (name, email, password) VALUES (?,?,?)";
$stmt= $conn->prepare($sql);
$stmt->bind_param("sss", $name, $email, $password_hash);
$stmt->execute();

同样的原则也适用于所有其他查询类型,例如 UPDATE 或 DELETE。

选择

运行select query 几乎相同,但有一个小技巧。由于某些未知的原因,您不能在准备好的语句中使用熟悉的 fetch 函数。所以你需要先得到mysqli_result,然后才能使用fetch_assoc()fetch_obj()等:

$sql = "SELECT * FROM users WHERE id=?"; // SQL with parameters
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $id);
$stmt->execute();
$result = $stmt->get_result(); // get the mysqli result
$user = $result->fetch_assoc(); // fetch data 

提示:绝对不需要熟悉的mysqli_num_rows() 函数。如果你想到了,你总是可以使用数据本身来查看你的查询是否返回了任何行:

$user = $result->fetch_assoc();
if ($user) 
    // found!

多行也是如此,感谢

另一个提示:有一个方便的函数fetch_all() 可以一次性获取所有选定行的数组。例如,如果查询返回多行,您可以通过将最后一行更改为

来将它们放入数组中
$users = $result->fetch_all(MYSQLI_ASSOC); // fetch data 

错误处理

错误处理是最重要但又有点令人惊讶的部分。尽管有许多文章和示例都说了,但作为一项规则,您根本不应该编写任何错误处理代码。这听起来绝对是疯狂的,但这正是事情必须要做的事情。大多数时候,您需要做的只是报告错误。并且 mysqli/PHP 已经可以为您完成,无需帮助。因此,您不应该编写任何验证查询执行结果的代码——如果出现错误,mysqli 会自动报告,这要归功于#Connection 部分中提到的mysqli_report() 函数调用。同样,这个原理的完整解释可以在另一篇文章中找到,献给将军PHP error reporting。

在极少数情况下,您确实需要处理错误,即在出现错误时执行一些操作,而不是仅仅报告错误,然后包装您的查询(es)try..catch

【讨论】:

【参考方案4】:

试试这个,清理传入的数据。 并且不要忘记 php 标签。

function clean($data)
 
  $data = trim(strip_tags(htmlspecialchars($data)));
  return $data;


$field1 = isset($_POST['field1']) ? clean($_POST['field1']): NULL;
$field2 = isset($_POST['field2']) ? clean($_POST['field2']): NULL;
$field3 = isset($_POST['field3']) ? clean($_POST['field3']): NULL;
$field4 = isset($_POST['field4']) ? clean($_POST['field4']): NULL;
$field5 = isset($_POST['field5']) ? clean($_POST['field5']): NULL;
$field6 = isset($_POST['field6']) ? clean($_POST['field6']): NULL;
$field7 = isset($_POST['field7']) ? clean($_POST['field7']): NULL;
$database = new mysqli("localhost", "username", "password", "database");
if ($database->errno) die("Error opening database: " . $database->error());
$query = 'INSERT INTO `tablename` (`field1`, `field2`, `field3`, `field4`, `field5`, `field6`, `field7`) VALUES (?, ?, ?, ?, ?, ?, ?)'; 
$result = $database->prepare($query); 
$result->bind_param('sssssss', $field1, $field2, $field3, $field4, $field5, $field6, $field7); 
$result->execute(); 
$database->close();

    header("Location: http://www.somewebsite.com");

【讨论】:

这个函数不会清理任何东西,与mysqli准备好的语句无关 这是一个经过测试且功能正常的脚本,请您解释一下您声明的原因。

以上是关于如何使用mysqli准备好的语句?的主要内容,如果未能解决你的问题,请参考以下文章

如何在mysqli准备好的语句中使用SQL LIKE子句[重复]

如何将 MySQLi 准备好的语句与存储过程一起使用

如何使用索引号将数据发送到mysqli(准备好的语句)

如果在循环中使用 MySQLi 准备好的语句,我啥时候调用 bind_param?

带有变量的Mysqli准备好的语句列[重复]

2个准备好的语句,2个存储过程,1个mysqli连接