$dns='mysql:host=localhost;dbname=blog_test;port=3306;charset=utf8';$pdo=newPDO($dns,'root','',[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);$stmt=$pdo->prepare(34;selectfromzyblog_test_user");//PDOStatement工具的内容var_dump($stmt);//object(PDOStatement)#2(1){//["queryString"]=>//string(57)"selectfromzyblog_test_userwhereusername=:username"//}
PDOStatement 工具是通过 PDO 工具的 prepare() 方法返回的一个工具。它没有布局函数,也便是说我们不能直接实例化一个 PDOStatement 工具。它包含一个只读属性,也便是我们要实行的 SQL 语句,保存在 queryString 中。
PDOStatement 缺点处理接下来我们先看看 PDOStatement 的两个缺点信息方法。
//没有指定非常处理状态下的缺点信息函数$pdo_no_exception=newPDO($dns,'root','');$errStmt=$pdo_no_exception->prepare("selectfromerrtable");$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'texist"//}
在之前的文章中,我们学习过,如果不给 PDO 工具指定缺点处理格式的话。它会利用返回缺点码和缺点信息的办法处理缺点。在这种情形下,如果预处理的语句有问题,我们就可以通过 errorCode() 和 errorInfo() 方法来得到缺点的代码和缺点的详细信息。不过,还是更加推举指定 PDO 的缺点处理办法为抛出非常,就像最上面我们定义的 PDO 工具那样。这样我们就可以通过 try...catch 来处理缺点非常了。
PDOStatement FETCH_MODE 指定
//为语句设置默认的获取模式。$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"//}//……
为查询构造指定 FETCH_MODE 是通过 setFetchMode() 方法来实现的。之前我们也讲过,通过 PDO 工具的属性可以指定默认的查询结果集模式,不过在 PDOStatement 中,也可以通过这个方法来为当前的这一次预处理语句的查询指定 FETCH_MODE 。
PDOStatement 获取列数量及字段信息//返回结果集列数、返回结果集中一列的元数据$stmt=$pdo->prepare("selectfromzyblog_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() 可以返回我们当前查询结果集中的列的数量。关于行的数量得到的方法我们将不才篇文章中再先容。
getColumnMeta() 方法则是获取结果集中一列的元数据,它的参数是列的序号,从 1 开始的序号,在这里我们获取的是第一列,也便是 id 列的信息。从打印的结果,可以看到这个列的名称、精确度(precisiion)、长度、类型、所属的表名、属性(主键、非空)这些信息。是不是觉得非常有用。不过这个方法是实验性子的,有可能在未来的 PHP 版本中进行修正,不是正式的固定方法。而且并不是所有数据库连接驱动都支持这个方法。
PDOStatement 打印出一条预处理语句包含的信息$stmt=$pdo->prepare("selectfromzyblog_test_userwhereusername=?andsalt=?");$username='aaa';$stmt->bindParam(1,$username,PDO::PARAM_STR);$stmt->bindValue(2,'aaa',PDO::PARAM_STR);$stmt->execute();var_dump($stmt->debugDumpParams());//SQL:[60]selectfromzyblog_test_userwhereusername=?andsalt=?//SentSQL:[68]selectfromzyblog_test_userwhereusername='aaa'andsalt='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() 也是很好玩的一个方法,它直接打印出当前实行的 SQL 语句的信息,把稳,它和 var_dump() 、 php_info() 这类函数一样,是直接打印的,不是将结果返回到一个变量中。还记得我们怎么将这种函数的内容保存到变量中吗?还搞不懂PHP中的输出缓冲掌握?。
从打印的结果来看,它能返回真实实行的 SQL 语句以及干系的一些参数信息。对付日常的开拓调试来说绝对是一个神器啊。很多小伙伴都会受困于 PDO 预处理的语句如果获取到真实的实行语句。而这个方法只须要我们大略的封装一下,就可以从里面提取出真实的实行语句了哦!
//MySQL驱动不支持setAttribute$stmt->setAttribute(PDO::ATTR_CURSOR,PDO::CURSOR_FWDONLY);//Fatalerror:UncaughtPDOException:SQLSTATE[IM001]:Driverdoesnotsupportthisfunction:Thisdriverdoesn'tsupportsettingattributes//MySQL驱动不支持getAttributevar_dump($stmt->getAttribute(PDO::ATTR_AUTOCOMMIT));//Fatalerror:UncaughtPDOException:SQLSTATE[IM001]:Driverdoesnotsupportthisfunction:Thisdriverdoesn'tsupportgettingattributes
这两个方法对付 MySQL 扩展驱动来说是不支持的,但是有其它的数据库是支持的,笔者没有测试过其它数据库,大家可以自行测试一下。
绑定字段接下来便是重点内容了,在预处理语句中,我们可以利用占位符来绑定变量,从而达到安全处理查询语句的浸染。通过占位符,我们就不用去自己拼装处理带单引号的字段内容了,从而避免了 SQL 注入的发生。把稳,这里并不是可以处理所有的 SQL 注入问题,比如字符集问题的 宽字节 注入 。
占位符包含两种形式,一种是利用 :xxx 这种形式的名称占位符,: 后面的内容可以是自己定义的一个名称。另一种形式便是利用问号占位符,当利用问号占位符的时候,我们绑定的是字段的下标,下标是从 1 开始的,这点是须要把稳的地方。我们直接通过示例来看看。
bindParam$stmt=$pdo->prepare("insertintozyblog_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("insertintozyblog_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() 的差异时再详细讲解。一些驱动支持调用存储过程的输入/输出操作,也可以利用这个方法来绑定,我们将在后面的文章中讲解。
bindValue$stmt=$pdo->prepare("insertintozyblog_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() 是绑定到变量。在正常情形下,你可以将它们看作是一样的操作,但是,实在它们有很大的不同,我们直接就来看它们的差异。
bindParam 和 bindValue 的差异首先,bindValue() 是可以绑定常量的。
$stmt=$pdo->prepare("selectfromzyblog_test_userwhereusername=:username");//$stmt->bindParam(':username','ccc');//Fatalerror:UncaughtError:Cannotpassparameter2byreference$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("selectfromzyblog_test_userwhereusername=: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("selectfromzyblog_test_userwhereusername=: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() 语句实行之前完成,否则绑定的便是一个空的数据bindColumn这个方法是用于绑定查询结果集的内容的。我们可以将查询结果集中指定的列绑定到一个特定的变量中,这样就可以在 fetch() 或 fetchAll() 遍历结果集时通过变量来得到列的值。
这个方法在实际运用中用到的比较少,以是很多小伙伴可能是只闻其名不见其身。我们还是通过代码来看看。
$stmt=$pdo->prepare("selectfromzyblog_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);//Fatalerror:UncaughtPDOException:SQLSTATE[HY000]:Generalerror:Invalidcolumnindexwhile($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 直接通报参数末了,如果我们不想这么麻烦地去绑定字段或者变量,也可以直接在 execute() 方法中直接通报参数,它是类似于 bindValue() 的形式进行字段绑定的。
$stmt=$pdo->prepare("insertintozyblog_test_user(username,password,salt)values(:username,:pass,:salt)");$stmt->execute([':username'=>'jjj',':pass'=>'888',':salt'=>'j8']);//利用问号占位符的话是按从0开始的下标$stmt=$pdo->prepare("insertintozyblog_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
参考文档:
https://www.php.net/manual/zh/class.pdostatement.php