今天这篇文章的内容其实很基础,但是现代开发大家都用框架,很少有人会封装或者经常自己写底层的数据库操作代码了。那么这次我们就来回顾一下数据库中相关扩展中preparedstatements的内容。什么是准备好的陈述?准备好的语句可以被认为是要运行的SQL语句的编译模板,可以使用可变参数进行控制。准备好的语句有两个主要好处:一个查询只需要被解析(或预处理)一次,但可以使用相同或不同的参数执行多次。准备好查询后,数据库会分析、编译和优化执行该查询的计划。对于复杂的查询,此过程可能需要很长时间,并且如果需要使用不同的参数多次重复相同的查询,则会显着降低应用程序的速度。使用准备好的语句可以避免重复的分析/编译/优化循环。简而言之,准备好的语句使用更少的资源,因此运行得更快。提供给准备好的语句的参数不需要用引号括起来,驱动程序会自动处理。如果您的应用程序仅使用准备好的语句,则可以确保不会发生SQL注入。(但是,如果查询的其他部分是从未转义的输入构造的,则仍然存在SQL注入的风险)。以上内容摘自官方文档,但其实preparedstatements最直观的好处就是可以有效的防止SQL注入。关于SQL注入的内容,我们以后研究MySQL的时候会深入研究,这里就不过多介绍了。反正preparedstatement可以完成这个工作。PDO操作PreparedStatements在PHP的扩展中,PDO已经是主流的核心数据库扩展库,自然其对PreparedStatements的支持也非常全面。$pdo=newPDO('mysql:host=localhost;port=3306;dbname=blog_test','root','');$pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);//:xxx占位符$stmt=$pdo->prepare("插入zyblog_test_user(用户名,密码,salt)值(:username,:password,:salt)");$stmt->bindParam(':username',$用户名);$stmt->bindParam(':password',$password);$stmt->bindParam(':salt',$salt);$username='one';$password='123123';$salt='aaa';$stmt->execute();$username='two';$password='123123';$salt='bbb';$stmt->execute();在代码中,我们使用prepare()方法定义了preparedstatement,这个方法会返回一个PDOStatement对象。在准备好的语句中使用占位符如:xxx,并使用PDOStatement对象的bindParam()方法在外部将变量绑定到这些占位符上。最后通过execute()真正执行SQL语句。从这段代码中,我们可以看到preparedstatements的两个优点的体现。第一个是占位符。使用占位符后,我们就不需要在SQL语句中写单引号了。单引号通常是SQL注入漏洞的主要来源。bindParam()方法自动转换绑定数据的类型。当然bindParam()方法也可以在可选参数中指定绑定的数据类型,这样可以让我们的代码更加安全,可以参考相关文档。另一个优势是模板的能力。我们只定义一个PDOStatement对象,然后通过改变数据的内容,就可以使用execute()方法多次执行preparedstatement。还有一种占位符的写法,就是用问号作为占位符号。在这种情况下,bindParam()方法的键名将使用数字下标。这里要注意,数字下标是从1开始的。//?placeholder$stmt=$pdo->prepare("insertintozyblog_test_user(username,password,salt)values(?,?,?)");$stmt->bindParam(1,$username);$stmt->bindParam(2,$password);$stmt->bindParam(3,$salt);$username='three';$password='123123';$salt='ccc';$stmt->execute();在我们的查询中,也可以方便的使用preparedstatement的功能进行数据查询。这里,我们直接使用execute()为占位符传递参数。//查询获取数据$stmt=$pdo->prepare("select*fromzyblog_test_userwhereusername=:username");$stmt->execute(['username'=>'one']);while($row=$stmt->fetch()){print_r($row);}mysqli操作preparedstatements虽然主流是PDO,而且大部分框架也用PDO,但是我们在写脚本的时候,或者需要快速测试一些功能的时候,mysqli仍将用于快速开发。当然,mysqli也支持preparedstatements相关的功能。//mysqli预处理$conn=newmysqli('127.0.0.1','root','','blog_test');$用户名='一个';$stmt=$conn->prepare("selectusernamefromzyblog_test_userwhereusername=?");$stmt->bind_param("s",$username);$stmt->execute();echo$stmt->bind_result($unames);var_dump($unames);while($stmt->fetch()){printf("%s\n",$unames);}可以看出,除了mysqli的方法名不同,绑定参数的键名并不完全相同。这里我们使用问号的占位符,在bind_param()方法中,使用s来表示符号的位置,如果有多个参数,应该写成sss...这样。当前框架中已经为我们封装了总结准备好的语句的能力。其实我们不需要太在意,就像在Laravel中使用DB::select()进行数据库操作时,可以看到语句的预处理应用。你可以自行查看vendor/laravel/framework/src/Illuminate/Database/Connection.php中的select()方法。测试代码:https://github.com/zhangyue0503/dev-blog/blob/master/php/202008/source/PHP%E4%B8%AD%E6%93%8D%E4%BD%9C%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E9%A2%84%E5%A4%84%E7%90%86%E8%AF%AD%E5%8F%A5.php参考文档:https://www.php.net/manual/zh/pdo.prepared-statements.php============各媒体平台均可搜索【硬核项目经理】
