2024年PHP日本大会

预处理语句和存储过程

许多比较成熟的数据库都支持预处理语句的概念。它们是什么?可以将它们视为应用程序想要运行的SQL的一种编译模板,可以使用可变参数进行自定义。预处理语句提供两大好处:

  • 查询只需要解析(或准备)一次,但可以使用相同或不同的参数执行多次。当准备查询时,数据库将分析、编译和优化其执行查询的计划。对于复杂的查询,此过程可能花费足够的时间,如果需要使用不同的参数多次重复相同的查询,则会明显减慢应用程序的速度。通过使用预处理语句,应用程序避免了重复分析/编译/优化周期。这意味着预处理语句使用更少的资源,因此运行速度更快。
  • 预处理语句的参数不需要加引号;驱动程序会自动处理。如果应用程序专门使用预处理语句,开发人员可以确保不会发生SQL注入(但是,如果查询的其他部分使用未转义的输入构建,则仍然可能发生SQL注入)。

预处理语句非常有用,它是PDO唯一模拟不支持它们的驱动程序的功能。这确保了应用程序能够使用相同的数据访问范例,而不管数据库的功能如何。

示例 #1 使用预处理语句重复插入

此示例通过为命名占位符替换namevalue来执行INSERT查询。

<?php
$stmt
= $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
$stmt->bindParam(':name', $name);
$stmt->bindParam(':value', $value);

// 插入一行
$name = 'one';
$value = 1;
$stmt->execute();

// 使用不同的值插入另一行
$name = 'two';
$value = 2;
$stmt->execute();
?>

示例 #2 使用预处理语句重复插入

此示例通过为位置?占位符替换namevalue来执行INSERT查询。

<?php
$stmt
= $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (?, ?)");
$stmt->bindParam(1, $name);
$stmt->bindParam(2, $value);

// 插入一行
$name = 'one';
$value = 1;
$stmt->execute();

// 使用不同的值插入另一行
$name = 'two';
$value = 2;
$stmt->execute();
?>

示例 #3 使用预处理语句获取数据

此示例根据表单提供的键值获取数据。用户输入会自动加引号,因此没有SQL注入攻击的风险。

<?php
$stmt
= $dbh->prepare("SELECT * FROM REGISTRY where name = ?");
$stmt->execute([$_GET['name']]);
foreach (
$stmt as $row) {
print_r($row);
}
?>

示例 #4 调用带有输出参数的存储过程

如果数据库驱动程序支持,应用程序还可以绑定输入和输出参数。输出参数通常用于从存储过程检索值。输出参数比输入参数稍微复杂一些,因为开发人员必须知道在绑定给定参数时该参数可能有多大。如果值大于他们建议的大小,则会引发错误。

<?php
$stmt
= $dbh->prepare("CALL sp_returns_string(?)");
$stmt->bindParam(1, $return_value, PDO::PARAM_STR, 4000);

// 调用存储过程
$stmt->execute();

print
"procedure returned $return_value\n";
?>

示例 #5 调用带有输入/输出参数的存储过程

开发人员还可以指定同时保存输入和输出值的参数;语法类似于输出参数。在下一个示例中,字符串“hello”被传递到存储过程,当它返回时,“hello”将被过程的返回值替换。

<?php
$stmt
= $dbh->prepare("CALL sp_takes_string_returns_string(?)");
$value = 'hello';
$stmt->bindParam(1, $value, PDO::PARAM_STR|PDO::PARAM_INPUT_OUTPUT, 4000);

// 调用存储过程
$stmt->execute();

print
"过程返回 $value\n";
?>

示例 #6 占位符的无效使用

<?php
$stmt
= $dbh->prepare("SELECT * FROM REGISTRY where name LIKE '%?%'");
$stmt->execute([$_GET['name']]);

// 占位符必须用于整个值的位置
$stmt = $dbh->prepare("SELECT * FROM REGISTRY where name LIKE ?");
$stmt->execute(["%$_GET[name]%"]);
?>

添加注释

用户贡献的注释 3 条注释

219
adam at pyramidpower dot com dot au
14 年前
请注意,当使用 bindParam 与命名参数时,名称本身不能包含短划线“-”。

示例
<?php
$stmt
= $dbh->prepare ("INSERT INTO user (firstname, surname) VALUES (:f-name, :s-name)");
$stmt -> bindParam(':f-name', 'John');
$stmt -> bindParam(':s-name', 'Smith');
$stmt -> execute();
?>

'f-name' 和 's-name' 中的短划线应替换为下划线或完全删除。

参见 http://bugs.php.net/43130

Adam
10
w37090 at yandex dot ru
4年前
通过预处理查询将多维数组插入数据库
我们有一个数组来写入表单

$dataArr
数组
(
[0] => 数组
(
[0] => 2020
[1] => 23
[2] => 111111
)

[1] => 数组
(
[0] => 2020
[1] => 24
[2] => 222222222
)
....

任务:准备请求并通过绑定传递
$array = [];
foreach ($dataArr as $k=>$v) {
// $x = 2020,变量预先确定,不会改变本质
$array[] = [$x, $k, $v];
}
$sql = ("INSERT INTO `table` (`field`,`field`,`field`) VALUES (?,?,?)");

$db->queryBindInsert($sql,$array);

public function queryBindInsert($sql,$bind) {
$stmt = $this->pdo->prepare($sql);

if(count($bind)) {
foreach($bind as $param => $value) {
$c = 1;
for ($i=0; $i<count($value); $i++) {
$stmt->bindValue($c++, $value[$i]);
}
$stmt->execute();
}
}
}
-1
theking2(at)king.ma
9个月前
示例 #5 在 MariaDB 上尝试时会返回 1414。使用此函数调用存储过程,最后一个参数为 INOUT,返回 (uu)id 或受影响的行数等值;

<?php
/**
* call_sp 使用给定参数调用指定的存储过程。
* 第一个参数是要调用的存储过程的名称。
* 其余参数是存储过程的 (in) 参数。
* 最后一个 (out) 参数应为整数,例如状态或受影响的行数。
*
* @param mixed $sp_name 要调用的存储过程的名称。
* @param mixed $params 要传递给存储过程的参数。
* @return int 受影响的行数。
*/
function call_sp( \PDO $db, string $sp_name, ...$params ): mixed
{
$placeholders = array_fill( 0, count( $params ), "?" );
$placeholders[] = "@new_id";

$sql = "CALL $sp_name( " . implode( ", ", $placeholders ) . " ); SELECT @new_id AS `new_id`";

try {
LOG->debug( "calling Stored Procedure", [ "sql" => $sql ] );

$stmt = $db->prepare( $sql );
$i = 0;
foreach(
$params as $param ) {
$stmt->bindValue( ++$i, $param );
}
$stmt->execute();
$new_id = $stmt->fetch( PDO::FETCH_ASSOC )['new_id'];

return
$new_id;

} catch (
\Exception $e ) {
LOG->error( "Error calling Stored Procedure", [ "sql" => $sql, "params" => $params, "error" => $e->getMessage() ] );
throw
$e;
}
To Top