This article will introduce you to the preprocessing classes and bound data in PHP. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to everyone.
It should be said that PDO is the most powerful In addition to providing a unified interface for different databases, the more important function is its preprocessing capability, which is the function provided by PDOStatement. Because of its existence, we can use it with peace of mind without worrying about the security risks caused by poor splicing of SQL statements. Of course, preprocessing also improves the execution efficiency of statements for us, which can be said to be another big killer of PDO.
The PDOStatement class actually represents a prepared statement and represents a related result set after the statement is executed. It provides some methods that allow us to operate on this prepared statement.
$dns = 'mysql:host=localhost;dbname=blog_test;port=3306;charset=utf8'; $pdo = new PDO($dns, 'root', '', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); $stmt = $pdo->prepare("select * from zyblog_test_user"); // PDOStatement 对象的内容 var_dump($stmt); // object(PDOStatement)#2 (1) { // ["queryString"]=> // string(57) "select * from zyblog_test_user where username = :username" // }
The PDOStatement object is an object returned by the prepare() method of the PDO object. It has no constructor, which means we cannot instantiate a PDOStatement object directly. It contains a read-only property, which is the SQL statement we want to execute, stored in queryString.
Next, let’s take a look at the two error message methods of PDOStatement.
// 没有指定异常处理状态下的错误信息函数 $pdo_no_exception = new PDO($dns, 'root', ''); $errStmt = $pdo_no_exception->prepare("select * from errtable"); $errStmt->execute(); var_dump($errStmt->errorCode()); // string(5) "42S02" var_dump($errStmt->errorInfo()); // array(3) { // [0]=> // string(5) "42S02" // [1]=> // int(1146) // [2]=> // string(40) "Table 'blog_test.errtable' doesn't exist" // }
In the previous article, we learned that if the error handling format is not specified for the PDO object. It handles errors by returning error codes and error messages. In this case, if there is a problem with the prepared statement, we can get the error code and error details through the errorCode() and errorInfo() methods. However, it is still more recommended to specify the error handling method of PDO to throw an exception, just like the PDO object we defined above. In this way we can handle error exceptions through try...catch.
// 为语句设置默认的获取模式。 $stmt->setFetchMode(PDO::FETCH_ASSOC); $stmt->execute(); while($row = $stmt->fetch(PDO::FETCH_ASSOC)){ var_dump($row); } // array(4) { // ["id"]=> // string(1) "1" // ["username"]=> // string(3) "aaa" // ["password"]=> // string(3) "aaa" // ["salt"]=> // string(3) "aaa" // } // ……
Specifying FETCH_MODE for the query structure is achieved through the setFetchMode() method. We have also mentioned before that the default query result set mode can be specified through the properties of the PDO object. However, in PDOStatement, this method can also be used to specify FETCH_MODE for the current query of the prepared statement.
// 返回结果集列数、返回结果集中一列的元数据 $stmt = $pdo->prepare("select * from zyblog_test_user"); $stmt->execute(); var_dump($stmt->columnCount()); // int(4) var_dump($stmt->getColumnMeta(0)); // array(7) { // ["native_type"]=> // string(4) "LONG" // ["pdo_type"]=> // int(2) // ["flags"]=> // array(2) { // [0]=> // string(8) "not_null" // [1]=> // string(11) "primary_key" // } // ["table"]=> // string(16) "zyblog_test_user" // ["name"]=> // string(2) "id" // ["len"]=> // int(11) // ["precision"]=> // int(0) // }
columnCount() can return the number of columns in our current query result set. We will introduce the method of obtaining the number of rows in the next article.
The getColumnMeta() method is to get the metadata of a column in the result set. Its parameter is the serial number of the column, starting from 1. Here we get the first column, which is the information of the id column. . From the printed results, you can see the column's name, precision, length, type, table name, attributes (primary key, non-null) and other information. Doesn't it feel very useful? However, this method is experimental and may be modified in future PHP versions. It is not a formal fixed method. And not all database connection drivers support this method.
$stmt = $pdo->prepare("select * from zyblog_test_user where username=? and salt = ?"); $username = 'aaa'; $stmt->bindParam(1, $username, PDO::PARAM_STR); $stmt->bindValue(2, 'aaa', PDO::PARAM_STR); $stmt->execute(); var_dump($stmt->debugDumpParams()); // SQL: [60] select * from zyblog_test_user where username=? and salt = ? // Sent SQL: [68] select * from zyblog_test_user where username='aaa' and salt = 'aaa' // Params: 2 // Key: Position #0: // paramno=0 // name=[0] "" // is_param=1 // param_type=2 // Key: Position #1: // paramno=1 // name=[0] "" // is_param=1 // param_type=2
debugDumpParams() is also a very interesting method. It directly prints out the information of the currently executed SQL statement. Note that it is the same as var_dump () and php_info() are the same functions, they print directly instead of returning the result to a variable. Remember how we saved the contents of such a function into a variable? [Still confused about output buffering control in PHP? ]().
Judging from the printed results, it can return the actual executed SQL statement and some related parameter information. It is definitely an artifact for daily development and debugging. Many friends will be trapped in the PDO preprocessing statement if they get the real execution statement. This method only requires us to simply encapsulate it, and then we can extract the real execution statement from it!
// MySQL 驱动不支持 setAttribute $stmt->setAttribute(PDO::ATTR_CURSOR, PDO::CURSOR_FWDONLY); // Fatal error: Uncaught PDOException: SQLSTATE[IM001]: Driver does not support this function: This driver doesn't support setting attributes // MySQL 驱动不支持 getAttribute var_dump($stmt->getAttribute(PDO::ATTR_AUTOCOMMIT)); // Fatal error: Uncaught PDOException: SQLSTATE[IM001]: Driver does not support this function: This driver doesn't support getting attributes
These two methods are not supported by the MySQL extension driver, but other databases support it. The author has not tested the others. Database, you can test it yourself.
The next step is the key content. In the preprocessing statement, we can use placeholders to bind variables to achieve safe processing of queries. The function of the statement. Through placeholders, we don’t have to assemble and process the field contents with single quotes ourselves, thus avoiding the occurrence of SQL injection. Note that not all SQL injection issues can be handled here, such as wide byte injection of character set issues.
The placeholder contains two forms. One is to use the name placeholder in the form of :xxx. The content after : can be a name defined by yourself. Another form is to use the question mark placeholder. When using the question mark placeholder, we bind the subscript of the field, and the subscript starts from 1. This is something to pay attention to. Let’s take a look directly through an example.
$stmt = $pdo->prepare("insert into zyblog_test_user(username, password, salt) values(:username, :pass, :salt)"); $username = 'ccc'; $passwrod = '333'; $salt = 'c3'; $stmt->bindParam(':username', $username); $stmt->bindParam(':pass', $password); $stmt->bindParam(':salt', $salt); $stmt->execute(); // bindParam 问号占位符 $stmt = $pdo->prepare("insert into zyblog_test_user(username, password, salt) values(?, ?, ?)"); $username = 'ccc'; $passwrod = '333'; $salt = 'c3'; $stmt->bindParam(1, $username); $stmt->bindParam(2, $password); $stmt->bindParam(3, $salt); $stmt->execute();
在这段代码中,我们分别使用了两种形式的占位符来实现了数据的插入。当然,预处理语句和占位符是任何操作语句都可以使用的。它的作用就是用绑定的值来替换语句中的占位符所在位置的内容。不过它只是使用在 values 、 set 、 where 、 order by 、 group by 、 having 这些条件及对字段的操作中,有兴趣的同学可以试试用占位符来表示一个表名会是什么结果。
bindParam() 方法是绑定一个参数到指定的变量名。在这个方法中,绑定的变量是作为引用被绑定,并且只能是一个变量,不能直接给一个常量。这点我们在后面讲和 bindValue() 的区别时再详细讲解。一些驱动支持调用存储过程的输入/输出操作,也可以使用这个方法来绑定,我们将在后面的文章中讲解。
$stmt = $pdo->prepare("insert into zyblog_test_user(username, password, salt) values(:username, :pass, :salt)"); $username = 'ddd'; $passwrod = '444'; $salt = 'd4'; $stmt->bindValue(':username', $username); $stmt->bindValue(':pass', $password); $stmt->bindValue(':salt', $salt); $stmt->execute();
咦?它的用法和 bindParam() 一样呀?没错,它们的作用也是一样的,绑定一个参数到值。注意,这里是绑定到值,而 bindParam() 是绑定到变量。在正常情况下,你可以将它们看作是一样的操作,但是,其实它们有很大的不同,我们直接就来看它们的区别。
首先,bindValue() 是可以绑定常量的。
$stmt = $pdo->prepare("select * from zyblog_test_user where username = :username"); //$stmt->bindParam(':username', 'ccc'); // Fatal error: Uncaught Error: Cannot pass parameter 2 by reference $stmt->bindValue(':username', 'ccc'); $stmt->execute(); while($row = $stmt->fetch(PDO::FETCH_ASSOC)){ var_dump($row); } // array(4) { // ["id"]=> // string(2) "19" // ["username"]=> // string(3) "ccc" // ["password"]=> // string(3) "bbb" // ["salt"]=> // string(2) "c3" // } // ……
如果我们使用 bindParam() 来指定第二个参数值为常量的话,它会直接报错。bindParam() 的第二个参数是作为引用类型的变量,不能指定为一个常量。
其次,因为bindParam() 是以引用方式绑定,它的变量内容是可变的,所以在任何位置定义绑定的变量都不影响它的预处理,而 bindValue() 是定义后就立即将参数进行绑定的,所以下面的代码使用 bindValue() 是无法获得结果的($username 在 bindValue() 之后才赋值)。
$stmt = $pdo->prepare("select * from zyblog_test_user where username = :username"); $stmt->bindValue(':username', $username); $username = 'ccc'; $stmt->execute(); while($row = $stmt->fetch(PDO::FETCH_ASSOC)){ var_dump($row); } //
必须要保证变量在 bindValue() 之前被赋值。
$username = 'ccc'; $stmt->bindValue(':username', $username);
当然,bindParam() 就不存在这样的问题了,我们可以在 bindParam() 之后再给它指定的变量赋值。
$stmt = $pdo->prepare("select * from zyblog_test_user where username = :username"); $stmt->bindParam(':username', $username); $username = 'ddd'; $stmt->execute(); while($row = $stmt->fetch(PDO::FETCH_ASSOC)){ var_dump($row); } // array(4) { // ["id"]=> // string(1) "8" // ["username"]=> // string(3) "ddd" // ["password"]=> // string(3) "bbb" // ["salt"]=> // string(2) "d4" // } // ……
这下对 bindParam() 和 bindValue() 的区别就非常清楚了吧?总结一下:
bindParam() 必须绑定变量,变量是引用形式的参数,只要在 execute() 之前完成绑定都可以
bindValue() 可以绑定常量,如果是绑定的变量,那么变量赋值要在 bindValue() 语句执行之前完成,否则绑定的就是一个空的数据
这个方法是用于绑定查询结果集的内容的。我们可以将查询结果集中指定的列绑定到一个特定的变量中,这样就可以在 fetch() 或 fetchAll() 遍历结果集时通过变量来得到列的值。
这个方法在实际应用中用到的比较少,所以很多小伙伴可能是只闻其名不见其身。我们还是通过代码来看看。
$stmt = $pdo->prepare("select * from zyblog_test_user"); $stmt->execute(); $stmt->bindColumn(1, $id); $stmt->bindColumn(2, $username, PDO::PARAM_STR); $stmt->bindColumn("password", $password); $stmt->bindColumn("salt", $salt, PDO::PARAM_INT); // 指定类型强转成了 INT 类型 // 不存在的字段 // $stmt->bindColumn(5, $t); //Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: Invalid column index while($row = $stmt->fetch(PDO::FETCH_BOUND)){ $data = [ 'id'=>$id, 'username'=>$username, 'password'=>$password, 'salt'=>$salt, ]; var_dump($data); } // array(4) { // ["id"]=> // string(1) "1" // ["username"]=> // string(3) "aaa" // ["password"]=> // string(3) "aaa" // ["salt"]=> // int(0) // } // array(4) { // ["id"]=> // string(1) "2" // ["username"]=> // string(3) "bbb" // ["password"]=> // string(3) "bbb" // ["salt"]=> // int(123) // } // …… // 外部获取变量就是最后一条数据的信息 $data = [ 'id'=>$id, 'username'=>$username, 'password'=>$password, 'salt'=>$salt, ]; print_r($data); // Array // ( // [id] => 2 // [username] => bbb // [password] => bbb // [salt] => bbb // )
在代码中,我们使用的是 * 来获得的查询结果集。然后就可以通过问号占位符或者列名来将列绑定到变量中。接着在 fetch() 的遍历过程中,就可以通过变量直接获取每一条数据的相关列的值。需要注意的是,为变量赋值的作用域仅限于在执行 fetch() 方法之后。从代码的结构中我们就可以看出,bindColumn() 方法对于变量也是作为引用的方式绑定到 PDOStatement 对象内部的,所以 fetch() 在处理的时候就直接为这些变量赋上了值。
bindCloumn() 方法后面的参数是可选的字段类型,这个参数在 bindParam() 和 bindValue() 中都是存在的,也都是可选的。如果获取的类型和我们绑定时定义的类型不同,那么 PDOStatement 就会强转为绑定时指定的类型。例如上面例子中我们将本身为 varchar 类型的 salt 字段强转为 int 类型之后就输出的都是 int 类型了。除了这个参数之外,还有一些其它可选的参数,大家可以自行查阅相关的文档。
fetch() 循环结束后,变量中依然保留着最后一行结果集的内容。所以在使用的时候要注意如果外部有其它地方使用这些变量的话,是否需要重新赋值或者清理掉它们。
最后,如果我们不想这么麻烦地去绑定字段或者变量,也可以直接在 execute() 方法中直接传递参数,它是类似于 bindValue() 的形式进行字段绑定的。
$stmt = $pdo->prepare("insert into zyblog_test_user(username, password, salt) values(:username, :pass, :salt)"); $stmt->execute([ ':username'=>'jjj', ':pass'=>'888', ':salt'=>'j8' ]); // 使用问号占位符的话是按从0开始的下标 $stmt = $pdo->prepare("insert into zyblog_test_user(username, password, salt) values(?, ?, ?)"); $stmt->execute(['jjjj','8888','j8']);
execute() 的这个绑定参数是一个数组,在使用问号占位符的时候需要注意,在这里,按数组的下标来说,它们是从 0 开始算位置的。
另外需要注意的是,PDOStatement 对象的操作都是使用 execute() 方法来进行语句执行的。这个方法只会返回一个布尔值,也就是成功或者失败。不像 PDO 对象的 exec() 方法返回的是受影响的条数。如果是查询类的语句,我们需要在 execute() 之后调用 fetch() 之类的方法遍历结果集。而增、删、改之类的操作,则需要通过 rowCount() 来获得返回的执行结果条数。相关的内容我们也将在之后的文章一起详细讲解。
划重点的时刻又到咯!今天我们学习的主要是 PDOStatement 对象的一些不太常用但很好玩的方法,另外就是占位符绑定的问题。其中最主要的就是 bindParam() 和 bindValue() 的区别。下篇文章我们主要就是要学习 PDOStatement 中的查询相关的操作,这个可不能丢呀,大家一定不要迟到!
测试代码:
https://github.com/zhangyue0503/dev-blog/blob/master/php/202009/source/PHP%E4%B8%AD%E7%9A%84PDO%E6%93%8D%E4%BD%9C%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%89%EF%BC%89%E9%A2%84%E5%A4%84%E7%90%86%E7%B1%BB%E5%8F%8A%E7%BB%91%E5%AE%9A%E6%95%B0%E6%8D%AE.php
推荐学习:php视频教程
The above is the detailed content of Learn about preprocessing classes and bound data in PHP in three minutes. For more information, please follow other related articles on the PHP Chinese website!