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將變數作為參數綁定到預備語句

說明

物件導向風格

公開 mysqli_stmt::bind_param(字串 $types, 混合 &$var, 混合 &...$vars): 布林值

程序式風格

mysqli_stmt_bind_param(
    mysqli_stmt $statement,
    字串 $types,
    混合 &$var,
    混合 &...$vars
): 布林值

將變數綁定到由 mysqli_prepare()mysqli_stmt_prepare() 準備好的 SQL 陳述式中的參數標記。

注意事項:

如果變數的資料大小超過最大允許封包大小 (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 對應的變數類型為 整數
d 對應的變數類型為 浮點數
s 對應的變數類型為 字串
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 列資料。\n", $stmt->affected_rows);

/* 清理 CountryLanguage 資料表 */
$mysqli->query("DELETE FROM CountryLanguage WHERE Language='Bavarian'");
printf("已刪除 %d 列資料。\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 個資料列被找到。\n", $stmt->num_rows());

上述範例會輸出

10 rows found.

另請參閱

新增註釋

使用者貢獻的註釋 19 個註釋

jk at jankriedner dot de
13 年前
使用 mysqli::bind_param() 和陣列元素時,需要注意一些事項。
重新賦值陣列將會破壞任何參考,無論鍵是否相同。
您必須明確地重新賦值陣列中的每個值,才能保留參考。
最好用一個例子來說明
<?php
函式 getData() {
返回 陣列(
0=>陣列(
"name"=>"test_0",
"email"=>"test_0@example.com"
),
1=>陣列(
"name"=>"test_1",
"email"=>"test_1@example.com"
)
);
}
$db = new mysqli("localhost","root","","tests");
$sql = "INSERT INTO `user` SET `name`=?,`email`=?";
$res = $db->prepare($sql);
// 如果您將陣列元素綁定到預備語句,則必須先使用使用的鍵宣告陣列:
$arr = 陣列("name"=>"","email"=>"");
$res->bind_param("ss",$arr['name'],$arr['email']);
//到目前為止的介紹...

/*
例子 1(不會按預期工作,會建立兩個空的項目)
在 while()-head 中重新賦值陣列會產生一個新的陣列,而來自 bind_param 的參考會保留在舊陣列中
*/
foreach( getData() as $arr ) {
$res->execute();
}

/*
例子 2(將按預期工作)
顯式地重新賦值每個單一值會使參考保持活動狀態
*/
foreach( getData() as $tempArr ) {
foreach(
$tempArr as $k=>$v) {
$arr[$k] = $v;
}
$res->execute();
}
?>
匿名
13 年前
除了 Blob 和 null 的處理之外,還有一些關於參數值如何根據您的類型字串參數自動轉換並轉發到 Mysql 引擎的注意事項

1) PHP 會根據您的綁定類型字串自動將值轉換為對應的底層類型。例如:

<?php

$var
= true;
bind_param('i', $var); // 轉發到 Mysql 作為 1

?>

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 類別並新增一個 __toString 方法來輸出 MySQL 預期的日期時間格式,則您可以使用 's' 類型直接繫結到該 DateTime_Extended 物件。例如:

<?php

// DateTime_Extended 定義了 __toString 方法,用於返回 MySQL 格式的日期時間
$var = new DateTime_Extended;
bind_param('s', $var); // 傳遞給 MySQL 時會變成 '2011-03-14 17:00:01'

?>
davidharrison at gmail dot com
7 年前
此頁面中有兩個解決方案,可以透過 call_user_func_array() 呼叫 bind_param(),其中涉及使用一個名為 refValues() 的使用者自訂函式,以便您可以將參數以參考方式傳遞給 bind_param()。

這在 PHP v5.3(以及我猜想更早的版本)中可以完美運作,但自從升級到 PHP v7.1.7 後,這裡的 refValues() 函式就無法正確地將陣列轉換為參考陣列。反而會收到警告

「PHP 警告:mysqli_stmt_bind_param() 的第 3 個參數預期為參考,但給定的是值」

我認為這是由於「從 PHP 5.6.x 遷移到 PHP 7.0.x」指南中「向下不相容性」(變更:「以值傳遞的 foreach 會操作陣列的副本」)中提到的陣列和參考處理方式的變更所致。

因此,至少在 PHP v7.1.7 中,使用者自訂函式 refValues() 不再返回參考陣列,而是返回一般的值陣列。

將 refValues() 的函式定義更改為以參考方式接受陣列似乎可以解決此問題——正如預期的那樣,它會返回一個參考陣列,因此 bind_param() 可以如預期般運作(雖然我沒有對此進行非常徹底的測試,以確保沒有其他不良影響,尤其是在舊版本的 PHP 中)。

新的 refValues() 函式定義很簡單

<?php
function refValues(&$arr) // 針對 PHP v7.1.7 修改 $arr 為引用傳遞
{
if (
strnatcmp("phpversion(),'5.3') >= 0) //PHP 5.3+ 需要引用傳遞
{
$refs = array();
foreach(
$arr as $key => $value)
$refs[$key] = &$arr[$key];
return
$refs;
}
return
$arr;
}
?>
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 樹並執行全表掃描。對於較小的表格,這可能永遠不會表現為顯著的效能問題。然而,當你的表格達到數千萬行時……
匿名
13 年前
您可以將變數綁定到 NULL 值,並且在更新和插入查詢中,無論您將其與哪種類型的綁定字串關聯,相應的欄位都將更新為 NULL。但是,對於 WHERE 子句中的參數(例如 where field = ?),查詢將無效且不會產生任何結果。

在 MySQL 中,比較一個值是否為 NULL 時,語法應該是 "value IS NULL" 或 "value IS NOT NULL"。因此,您不能使用像是 "WHERE (value = ?)" 這樣的語法,並期望它在參數為 NULL 時能正常運作。

您可以在 WHERE 子句中這樣做:

"WHERE (IF(ISNULL(?), field1 IS NULL, field1 = ?))"

然後,將您要測試的值傳入兩次

bind_param('ss', $value1, $value1);
tomasz at marcinkowski dot pl
10 年前
當您嘗試綁定字串參數時,如果收到「變數數量與預備語句中的參數數量不符」的錯誤,請確保您沒有用引號將問號括起來。

我曾經錯誤地使用了這樣的查詢:
SELECT something FROM table WHERE param_name = "?"

使用 <?php $stmt->bind('s', $param_value); ?> 綁定參數時會失敗。我只需要移除問號周圍的引號即可解決問題。
希望這可以節省一些人的時間。
eisoft
14 年前
我使用預備語句將資料插入一個簡單的表格 - images(blob 類型)和它們的唯一識別碼(字串類型)。所有 blob 的大小都小於 MAX-ALLOWED-PACKET 的值。

我發現,在綁定 BLOB 參數時,我需要將它作為字串傳遞,否則它在表格中會被截斷為零長度。所以我必須這樣做:

<?php
$ok
= $stmt->bind_param( 'ss', $id, $im ) ;
?>
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(
$bad_stmt->fetch()){
echo
$ias . PHP_EOL;
}

$bad_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(
$good_stmt->fetch()){
echo
$ias . PHP_EOL;
}
$good_stmt->close();

$db->close();
?>
會印出以下結果

錯誤的結果
one

正確的結果
one
two
three
four

在預處理語句中使用 "IN(?)" 只會從資料表/視圖中返回一行(第一行)。這不是 PHP 的錯誤,而只是 MySQL 處理預處理語句的方式。
rejohns at nOsPaMpost dot harvard dot edu
14 年前
事實上,您可以使用 mysqli_bind_parameter 將 NULL 值傳遞給資料庫。只需建立一個變數,并将 NULL 值(請參閱其說明頁面)儲存到該變數中,然後綁定即可。對我來說效果很好。
xianrenb at gmail dot com
12 年前
如果在 $types 中指定了 'b',則對應的變數應設為 null,並且必須使用 mysqli_stmt::send_long_data() 或 mysqli_stmt_send_long_data() 來傳送 blob,否則 blob 值將被視為空值。
flame
17 年前
bigint 類型的欄位需要指定為 'd' 類型,而不是 'i' 類型。

使用 'i' 會導致大數字(例如 3000169151)被截斷。

--
flame
accountant
7 年前
如果 bind_param() 因為類型定義字串中的元素數量與綁定變數的數量不符而失敗,它會觸發 E_WARNING 錯誤。您不會在 $stmt->error 屬性中找到該錯誤。
andersmmg at gmail dot com
5 年前
我有時會忘記不能在裡面放函式。例如

如果我想像這樣對一個值使用 md5()
<?php
$stmt
->bind_param("s",md5($val));
?>
這樣是行不通的。因為它是透過綁定變數來使用它們,所以您需要像這樣事先更改它們
<?php
$val
= md5($val);
$stmt->bind_param("s",$val);
?>
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 = "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();

希望這能幫助某些人睡個好覺 :)
c at zp1 dot net
3 年前
理解您不能直接提供 bind_param 值非常重要

這樣是行不通的

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


您必須這樣做

$var = "value";
$stmt -> bind_param("s", $var);
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")
這只是我在我的專案中觀察到的,希望它是正確的。
laurence dot mackenzie at stream dot com
11 年前
我最近在使用 `bind_param()` 搭配反射類別時,遇到一個非常奇怪的行為。我想我應該把它發布出來,以免其他人遇到同樣問題時,也像我一樣浪費一個小時(就像我剛才那樣)苦思不得其解。

首先,一些背景:我有一組類別,每個檔案格式(例如 CSV、HTML 表格等)都有一個,它們會將資料從平面檔案導入到資料庫中的暫存表格。然後該類別會將資料轉換為 3NF。

我使用反射類別將陣列傳遞給 `mysqli->bind_param()`,因為欄位數量和類型是可變的。我遇到問題的程式碼(簡化後)如下:

<?php

/* 迴圈處理平面檔案中的列和欄,
* 將 MySQLi 的「類型」字母附加到 $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();

?>

我不確定這裡發生了什麼事,但就像我說的,希望這能避免下一個人認為他們完全瘋了。
alex dot deleyn at gmail dot com
13 年前
MySQL 有一個「NULL 安全等於」運算子(我猜是從 5.0 版開始)。
https://mysqldev.dev.org.tw/doc/refman/5.0/en/comparison-operators.html#operator_equal-to

如果您使用這個運算子而不是平常的 =,您可以在 where 子句中交換值和 null。

然而,當這個運算子與 datetime 或 timestamp 欄位一起使用時,有一個已知的錯誤:http://bugs.mysql.com/bug.php?id=36100
匿名
16 年前
值得注意的是,您必須一次性綁定所有參數 - 您不能逐一呼叫 bind_param。
To Top