PHP Conference Japan 2024

mysqli_stmt::bind_param

mysqli_stmt_bind_param

(PHP 5, PHP 7, PHP 8)

mysqli_stmt::bind_param -- mysqli_stmt_bind_param将变量绑定到准备好的语句作为参数

描述

面向对象风格

public mysqli_stmt::bind_param(string $types, mixed &$var, mixed &...$vars): bool

过程化风格

mysqli_stmt_bind_param(
    mysqli_stmt $statement,
    string $types,
    mixed &$var,
    mixed &...$vars
): bool

绑定SQL语句中参数标记的变量,SQL语句由mysqli_prepare()mysqli_stmt_prepare()准备。

注意:

如果变量的数据大小超过最大允许的数据包大小(max_allowed_packet),则必须在types中指定b,并使用mysqli_stmt_send_long_data()以数据包的形式发送数据。

注意:

在将mysqli_stmt_bind_param()call_user_func_array()结合使用时必须小心。请注意,mysqli_stmt_bind_param()要求参数通过引用传递,而call_user_func_array()可以接受一个参数列表,该列表可以表示引用或值。

参数

statement

仅限过程化风格:由mysqli_stmt_init()返回的mysqli_stmt对象。

types

包含一个或多个字符的字符串,这些字符指定对应绑定变量的类型

类型规范字符
字符 描述
i 对应变量的类型为int
d 对应变量的类型为float
s 对应变量的类型为string
b 对应变量是一个 Blob,并将以数据包的形式发送

var
vars

变量的数量和字符串types的长度必须与语句中的参数匹配。

返回值

成功时返回true,失败时返回false

错误/异常

如果启用了 mysqli 错误报告 (MYSQLI_REPORT_ERROR),并且请求的操作失败,则会生成警告。此外,如果模式设置为MYSQLI_REPORT_STRICT,则会抛出mysqli_sql_exception

示例

示例 #1 mysqli_stmt::bind_param() 示例

面向对象风格

<?php

mysqli_report
(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli('localhost', 'my_user', 'my_password', 'world');

$stmt = $mysqli->prepare("INSERT INTO CountryLanguage VALUES (?, ?, ?, ?)");
$stmt->bind_param('sssd', $code, $language, $official, $percent);

$code = 'DEU';
$language = 'Bavarian';
$official = "F";
$percent = 11.2;

$stmt->execute();

printf("%d row inserted.\n", $stmt->affected_rows);

/* 清理 CountryLanguage 表 */
$mysqli->query("DELETE FROM CountryLanguage WHERE Language='Bavarian'");
printf("%d row deleted.\n", $mysqli->affected_rows);

过程化风格

<?php

mysqli_report
(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$link = mysqli_connect('localhost', 'my_user', 'my_password', 'world');

$stmt = mysqli_prepare($link, "INSERT INTO CountryLanguage VALUES (?, ?, ?, ?)");
mysqli_stmt_bind_param($stmt, 'sssd', $code, $language, $official, $percent);

$code = 'DEU';
$language = 'Bavarian';
$official = "F";
$percent = 11.2;

mysqli_stmt_execute($stmt);

printf("%d row inserted.\n", mysqli_stmt_affected_rows($stmt));

/* 清理 CountryLanguage 表 */
mysqli_query($link, "DELETE FROM CountryLanguage WHERE Language='Bavarian'");
printf("%d row deleted.\n", mysqli_affected_rows($link));

以上示例将输出

1 row inserted.
1 row deleted.

示例 #2 使用 ... 提供参数

... 运算符可用于提供可变长度的参数列表,例如在 WHERE IN 子句中。

<?php

mysqli_report
(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli('localhost', 'my_user', 'my_password', 'world');

$stmt = $mysqli->prepare("SELECT Language FROM CountryLanguage WHERE CountryCode IN (?, ?)");
/* 使用 ... 提供参数 */
$stmt->bind_param('ss', ...['DEU', 'POL']);
$stmt->execute();
$stmt->store_result();

printf("%d rows found.\n", $stmt->num_rows());

以上示例将输出

10 rows found.

参见

添加注释

用户贡献的注释 19 条注释

70
jk at jankriedner dot de
13 年前
在使用 mysqli::bind_param() 和数组元素时,需要注意一些事项。
重新分配数组将破坏所有引用,无论键是否相同。
您必须显式地重新分配数组中的每个值,才能保持引用。
最好通过示例说明
<?php
function getData() {
return array(
0=>array(
"name"=>"test_0",
"email"=>"[email protected]"
),
1=>array(
"name"=>"test_1",
"email"=>"[email protected]"
)
);
}
$db = new mysqli("localhost","root","","tests");
$sql = "INSERT INTO `user` SET `name`=?,`email`=?";
$res = $db->prepare($sql);
// 如果您将数组元素绑定到预处理语句,则必须首先声明具有所用键的数组:
$arr = array("name"=>"","email"=>"");
$res->bind_param("ss",$arr['name'],$arr['email']);
// 到目前为止,这是介绍...

/*
示例 1(不会按预期工作,创建两个空条目)
在 while() 头部重新分配数组会生成一个新数组,而 bind_param 的引用会保留在旧数组中
*/
foreach( getData() as $arr ) {
$res->execute();
}

/*
示例 2(将按预期工作)
显式地重新分配每个值可以使引用保持有效
*/
foreach( getData() as $tempArr ) {
foreach(
$tempArr as $k=>$v) {
$arr[$k] = $v;
}
$res->execute();
}
?>
32
匿名
13 年前
除了 Blob 和 null 处理之外,以下是一些关于如何根据您的类型字符串参数自动转换参数值并将其转发到 Mysql 引擎的说明

1) PHP 将在幕后自动将值转换为与您的绑定类型字符串对应的底层类型。即

<?php

$var
= true;
bind_param('i', $var); // 作为 1 转发到 Mysql

?>

2) 尽管如果 PHP 数字大于 PHP_INT_MAX 则无法可靠地强制转换为 (int),但在幕后,该值仍将转换为最多 long long,具体取决于大小。这意味着,牢记精度限制并避免首先手动将变量强制转换为 (int),您仍然可以对较大的数字使用 'i' 绑定类型。即

<?php

$var
= '429496729479896';
bind_param('i', $var); // 传递给 Mysql 时变为 429496729479900

?>

3) 在大多数情况下,您可以将大多数参数参数默认为 's'。然后,该值将在后端自动转换为字符串,然后再传递给 Mysql 引擎。然后,Mysql 会在其自己的转换中使用从 PHP 中接收到的值进行执行。这允许您不仅可以绑定到更大的数字而无需考虑精度,还可以绑定到对象,只要该对象具有 '__toString' 方法。

这种自动字符串转换行为极大地改善了诸如日期时间处理之类的事情。例如:如果您扩展了 DateTime 类以添加输出 Mysql 期望的日期时间格式的 __toString 方法,则可以使用类型 's' 绑定到该 DateTime_Extended 对象。即

<?php

// DateTime_Extended 已定义 __toString 以返回 Mysql 格式化的日期时间
$var = new DateTime_Extended;
bind_param('s', $var); // 传递给 Mysql 时变为 '2011-03-14 17:00:01'

?>
5
davidharrison at gmail dot com
7 年前
此页面中提供了两种通过 call_user_func_array() 调用 bind_param() 的解决方案,这些解决方案涉及使用名为 refValues() 的用户创建函数,以便您可以将参数作为引用传递给 bind_param()。

这在 PHP v5.3(以及我假设之前的版本)中完美运行,但是自升级到 PHP v7.1.7 以来,此处的 refValues() 函数不再正确地将数组转换为引用数组。相反,您将收到警告

"PHP Warning: Parameter 3 to mysqli_stmt_bind_param() expected to be a reference, value given"

我相信这是由于数组和引用处理的更改导致的,如“从 PHP 5.6.x 迁移到 PHP 7.0.x”指南中“向后不兼容性”(更改:“按值 foreach 对数组的副本进行操作”)中所述。

因此,至少在 PHP v7.1.7 中,用户创建的函数 refValues() 不再返回引用数组,而是返回普通值数组。

将 refValues() 的函数定义更改为接受数组作为引用似乎可以解决此问题 - 正如预期的那样,它返回一个引用数组,因此 bind_param() 按预期工作(尽管我还没有对此进行超级彻底的测试以确保没有其他不良影响,尤其是在旧版本的 PHP 中)。

新的 refValues() 定义很简单

<?php
function refValues(&$arr) // 将 $arr 更改为 PHP v7.1.7 的引用
{
if (
strnatcmp(phpversion(),'5.3') >= 0) // PHP 5.3+ 需要引用
{
$refs = array();
foreach(
$arr as $key => $value)
$refs[$key] = &$arr[$key];
return
$refs;
}
return
$arr;
}
?>
6
travis at twooutrally dot com
8 年前
参数类型比您想象的更重要!

对任何试图为自动准备好的语句找到不太彻底的解决方案的人来说,这是一个警示性的故事。例如,请考虑以下 mysqli_stmt 扩展方法。

<?php

public function param_type($param)
{
if (
ctype_digit((string) $param)
return
$param <= PHP_INT_MAX ? 'i' : 's';

if (
is_numeric($param))
return
'd';

return
's';
}

?>

从表面上看,这似乎是一个非常简单且无害的函数。类似这样的东西充当了一个更大的自动化扩展中的一小部分,该扩展忠实地有效地执行了其目的,每天处理数十万个查询。

现在我知道你在想什么:它没有处理 blob 类型。好吧,我们没有使用 blob 类型(现在也没有),所以这从来都不是问题。这个问题远比这更阴险,最终也更具危害性。

那么哪里出了问题呢?当我们开始针对为存储电话号码而设计的列的新创建索引自动执行 SELECT 查询时,问题开始浮出水面。该列的类型为 VARCHAR,但存储的数据始终格式化为整数。在执行写入操作时这不是问题,但一旦我们开始从此索引上的表中读取数据,一切就都乱套了。

我们不太确定,但据我们所知,将参数绑定到 VARCHAR 索引为 'i' 而不是 's' 的读取查询会产生以下不利影响:MySQL 将忽略索引上的 b 树并执行全表扫描。对于较小的表,这可能永远不会表现为重大的性能问题。但是,当您的表达到数千万行时……
8
Anonymous
13 年前
您可以绑定到具有 NULL 值的变量,并且在更新和插入查询中,无论您为其关联的绑定字符串类型是什么,相应的字段都将更新为 NULL。但是,对于 WHERE 子句中使用的参数(即 where field = ?),查询将不起作用并且不会产生任何结果。

在将值与 NULL 进行比较时,MYSQL 语法为“value IS NULL”或“value IS NOT NULL”。因此,您不能传递类似“WHERE (value = ?)”的内容并期望使用空值参数使其工作。

相反,您可以在 WHERE 子句中执行以下操作

"WHERE (IF(ISNULL(?), field1 is null, field1 = ?))"

然后,两次传递您要测试的值

bind_param('ss', $value1, $value1);
5
tomasz at marcinkowski dot pl
10 年前
当尝试绑定字符串参数时,出现“变量数量与预处理语句中的参数数量不匹配”错误,请确保您没有用引号括住问号。

我错误地使用了以下查询
SELECT something FROM table WHERE param_name = "?"

使用 <?php $stmt->bind('s', $param_value); ?> 绑定一直失败。我所要做的就是删除“?”周围的引号。
希望这能节省一些人的时间。
1
eisoft
14 年前
我为在一个简单的表中插入图像(blob)及其唯一标识符(字符串)创建了一个预处理语句。我的所有 blob 大小都小于 MAX-ALLOWED-PACKET 值。

我发现,当绑定我的 BLOB 参数时,需要将其作为字符串传递,否则它将在我的表中被截断为零长度。所以我必须这样做

<?php
$ok
= $stmt->bind_param( 'ss', $id, $im ) ;
?>
3
asb(.d o,t )han(a t)n i h e i(d.o_t)dk
13 年前
需要注意的是,MySQL 在预处理语句中使用 IN 子句方面存在一些问题。

即代码
<?php

$idArr
= "1, 2, 3, 4";
$int_one = 1;
$int_two = 2;
$int_three = 3;
$int_four = 4;

$db = new MySQLi();
$bad_stmt = $db->prepare(SELECT `idAsLetters` FROM `tbl` WHERE `id` IN(?));
$bad_stmt->bind_param("s", $idArr);
$bad_stmt->bind_result($ias);
$bad_stmt->execute();

echo
"错误结果:" . PHP_EOL;
while(
$stmt->fetch()){
echo
$ias . PHP_EOL;
}

$good_stmt->close();

$good_stmt = $db->prepare(SELECT `idAsLetters` FROM `tbl` WHERE `id` IN(?, ?, ?, ?));
$good_stmt->bind_param("iiii", $int_one, $int_two, $int_three, $int_four);
$good_stmt->bind_result($ias);
$good_stmt->execute();

echo
"正确结果:" . PHP_EOL;
while(
$stmt->fetch()){
echo
$ias . PHP_EOL;
}
$bad_stmt->close();

$db->close();
?>
将打印以下结果

错误结果
one

正确结果
one
two
three
four

在预处理语句中使用“IN(?)”将只返回表/视图中的第一行。这并非 PHP 中的错误,而仅仅是 MySQL 处理预处理语句的方式。
3
rejohns at nOsPaMpost dot harvard dot edu
14 年前
事实上,您可以使用 mysqli_bind_parameter 将 NULL 值传递给数据库。只需创建一个变量并将 NULL 值(请参阅手册页)存储到该变量中并进行绑定。对我来说效果很好。
3
xianrenb at gmail dot com
12年前
据信,如果在 $types 中指定了 'b',则相应的变量应设置为 null,并且必须使用 mysqli_stmt::send_long_data() 或 mysqli_stmt_send_long_data() 来发送 blob,否则 blob 值将被视为为空。
1
flame
17年前
类型为 bigint 的列需要指定为类型 'd' 而不是 'i'。

使用 'i' 会导致大数字(例如 3000169151)被截断。

--
flame
1
accountant
7 年前
如果 bind_param() 由于“类型定义字符串中的元素数量与绑定变量的数量不匹配”而失败,则会触发 E_WARNING 错误。并且您不会在 $stmt->error 属性中找到该错误。
2
andersmmg at gmail dot com
5年前
我有时会忘记不能在其中放置函数。例如

如果我想对某个值使用 md5(),如下所示
<?php
$stmt
->bind_param("s",md5($val));
?>
这是行不通的。因为它通过绑定变量来使用变量,因此需要事先更改它们,如下所示
<?php
$val
= md5($val);
$stmt->bind_param("s",$val);
?>
2
Matze
8 年前
大家好,

只是想提一下,参数只能用于输入数据,不能用于表、列或数据库名称。
这昨天让我头疼了好一阵!
因此,此代码将无法工作

$searchtype = "Title";
$searchterm = "The Fellowship of the Ring: The Lord of the Rings";

$query =
"SELECT ISBN, Author, Title, Price
FROM books
WHERE ? = ?";

$mySql_stmt = $db->prepare($query);
$mySql_stmt->bind_param("ss" , $searchtype, $searchterm);
$mySql_stmt->execute();

相反,您必须直接在查询中包含 searchtype,如下所示

$searchtype = "Title";
$searchterm = "The Fellowship of the Ring: The Lord of the Rings";
$query =
"SELECT ISBN, Author, Title, Price
FROM books
WHERE $searchtype = ?";

$mySql_stmt = $db->prepare($query);
$mySql_stmt->bind_param("s", $searchterm);
$mySql_stmt->execute();

希望这对某人有所帮助,让他们睡个好觉 :)
0
c at zp1 dot net
3年前
理解这一点非常重要,您不能提供 bind_param 值

这将不起作用

$stmt -> bind_param("s", "value");


您必须像这样操作

$var = "value";
$stmt -> bind_param("s", $var);
0
Darky
7 年前
根据我的尝试,一个小小的说明
- 如果您将预处理语句与 bind_param 一起使用,并且您的查询如下所示
"SELECT user_id FROM users WHERE ... = ?",然后您将整数参数绑定到此,则您获得的 user_id 将被强制转换为 int。另一方面,如果您不使用预处理语句,而是使用类似“SELECT user_id FROM users WHERE ... = $var”的东西,其中 $var 是一个 int,并且只执行查询,则获取的结果将是字符串。(例如,在 var_dump 中,对于某些行,["user_id"]=> string(1) "6")
这只是我从我的项目中观察到的,希望它是正确的。
0
laurence dot mackenzie at stream dot com
11年前
我在使用 bind_param() 和反射类时遇到了非常奇怪的行为。我认为我应该在这里发布它,以防止任何遇到它的人对着桌子猛撞一个小时(就像我刚刚做的那样)。

首先,一些背景信息:我有一组类,每个文件格式一个(即 CSV、HTML 表格等),它们将数据从平面文件导入到数据库中的临时表中。然后,该类将数据转换为 3NF。

我正在使用反射类将数组传递给 mysqli->bind_param(),因为列计数和类型是可变的。我遇到的问题(简化)的代码是

<?php

/* 将平面文件中的行和列循环,并将 MySQLi 'type' 字母追加到
* $typeString 变量中,并将实际值
* 追加到 $data 数组中。我省略了代码,因为这
*(可能)不相关,并且会使帖子膨胀。
*/
$stmtInsert = $db->prepare('INSERT.....');
$typeString = 'ississis';
$data = array(1, 'two', 'three', 4, 'five', 'six', 7, 'eight');

/* 这里开始出现实际的奇怪现象
*/

// 将参数类型与参数值合并
$data = array_merge((array) $typeString, $data);

// 创建反射类
$ref = new \ReflectionClass('mysqli_stmt');

// 获取 bind_param 方法
$method = $ref->getMethod('bind_param');

// 使用 $data 调用它
$method->invokeArgs($stmtInsert, $data);

// 执行语句
$stmtInsert->execute();

}
?>

奇怪的是,在一个(且仅在一个)情况下,它开始抛出“警告:mysqli_stmt::bind_param() 的参数 41 预期为引用,但提供的值”。反射类抛出异常。使用此代码的其他导入集工作正常。参数 41 是最后一个参数。按如下方式更改受影响的代码可以解决此问题

<?php

$ref
= new \ReflectionClass("mysqli_stmt");
$method = $ref->getMethod("bind_param");
$data[count($data)-1] = (string) $data[count($data)-1];
$method->invokeArgs($stmtInsert, $data);
$stmtInsert->execute();

?>

不确定这里发生了什么,但就像我说的,希望这能阻止下一个人认为自己完全疯了。
-2
alex dot deleyn at gmail dot com
13 年前
MySQL 具有“NULL 安全等于”运算符(我猜从 5.0 开始)
https://dev.mysqlserver.cn/doc/refman/5.0/en/comparison-operators.html#operator_equal-to

如果使用此运算符而不是通常的 =,则可以在 where 子句中互换值和 null。

但是,在使用此运算符与日期时间或时间戳字段时存在一个已知的错误:http://bugs.mysql.com/bug.php?id=36100
-2
匿名
16年前
值得注意的是,您必须一次性绑定所有参数 - 您不能逐个调用 bind_param。
To Top