PDO

PDO(PHP Data Object)提供通用接口访问多种数据库,是建立PHP以及PHP连接数据库之间的抽象层。使用传统的mysql_connect() mysql_query()方法连接查询数据库,如果过滤不严,很容易产生SQL注入的风险。虽然可以使用mysql_real_escape_string()等函数过滤用户提交的值,但还是在安全、性能方面有缺陷。

连接与获取

在使用PDO连接到数据库需要实例化一个PDO对象并且传递一个DSN(Data Source Name),包括数据库类型、主机地址、数据库名、用户名、密码。

1
2
$dsn = "$db_type:host = $db_host; dbname = $db_name";//数据库类型、主机地址、数据库名
$conn = new PDO($dsn, $db_user, $db_password);//数据源名称、用户名、密码

♠以user表为例,获取其中的所有数据

1
2
3
4
5
6
$sql = "SELECT * FROM `user`";
$stmt = $conn->prepare($sql);//预编译
$stmt->execute();//执行
$result = $stmt->fetchAll();//获取
var_dump($result);
$conn = NULL;//释放

获取有两种类型fetch()(获取下一条记录)和fetchAll()(获取所有记录)都接受fetch_style参数,该参数定义如何格式化结果集。

  • PDO::FETCH_ASSOC:返回列名键组
  • PDO::FETCH_NUM:返回数字键组
  • PDO::FETCH_BOTH:结合了PDO::FETCH_ASSOC和PDO::NUM,返回每个值都出现两次的数组,一次使用列名,一次使用数字索引
  • PDO::FETCH_CLASS:返回一个类的对象,列的名字设置到其属性当中。

参数绑定与预编译

PDO最大的特点就是参数绑定和预编译,绑定有两种:参数绑定和变量绑定。

♠以从user表里获取name为iat的用户为例

1
2
3
4
5
6
$name = 'iat';
$sql = "SELECT * FROM `user` WHERE name = :name";
$stmt = $conn->prepare($sql);
$stmt->bindParam(':name', $name);//绑定参数
$stmt->bindValue(':name', 'iat');//绑定变量
$stmt->execute();

PDO中的bindParam()方法还有第三个可选参数,即可把传入的参数转化为需要的类型,类型对应如下

  • PDO::PARAM_BOOL:布尔类型
  • PDO::PARAM_NULL:NULL类型
  • PDO::PARAM_INT:整数类型
  • PDO::PARAM_STR:字符串类型

等等,这样的话数据经过相应的处理就相对安全些了。

bindValue()为绑定具体的值,变量作为引用被绑定,并只在execute() 被调用的时候才取其值。

占位符有两种形式:名称和问号,用问号的话则需要注意在传入execute()的时候,数组的值需要与对应的问号保持一致。如果SQL语句比较复杂,命名占位符更容易理解,更容易看出哪个值属于哪个参数。

♠以user表绑定name为例

1
2
3
4
5
6
7
8
//名称形式占位
sql = "SELECT * FROM `user` WHERE name = :name";
$stmt = $conn->prepare($sql);
$stmt->execute(array(':name' => 'iat'));
//问号形式占位
sql = "SELECT * FROM `user` WHERE name = ?";
$stmt = $conn->prepare($sql);
$stmt->execute(array('iat'));

在mysql中,为了防止注入攻击,通常使用

  • addslashes():特殊字符前加反斜线
  • mysql_escape_string():转义SQL字符串的特殊字符
  • mysql_real_escape_string():转义SQL字符串的特殊字符,并考虑连接的当前字符
  • htmlspecialchars():把一些预定义的字符转换为html实体
  • strip_tags():去除一些html和php标签

等等。当调用prepare()的时候,查询语句已经发送给服务器,此时是占位符发送了过去,没有用户提交的数据。而当调用execute()时,用户提交过来的数据才会传送过去,两者是相互独立的。在使用普通的mysql函数时需要使用mysql_escape_string()等函数过滤特殊的字符,而使用PDO的预处理语句已经帮我们完成了这一步骤。

使用预编译的好处是,查询只需被解析一次,但可以使用不同的参数执行多次。对于复杂查询来说,如果需要重复执行许多次有不同参数但结构相同的查询,这个过程会占用大量的时间。通过一个预编译语句可以避免重复分析、编译、优化环节。

异常与错误

PDO提供异常处理类PDOException,使用getMessage()方法可以获得抛出异常的原因。

1
2
3
4
5
6
try{
$conn = new PDO($dsn, $db_user, $db_password);
...
}catch(PDOException $e){
echo $e->getMessage();
}

如果不是由于异常引起的错误,PDO并不会有任何解释的,这时候就需要使用PDO或PDOStatement类的errorInfo()方法找出错误的原因,该方法返回一个数组,包括

  • SQLSTATE,错误产生原因的ANSI SQL标准码
  • 数据库驱动的错误代码
  • 数据库驱动的错误消息

♠以select数据失败为例,返回false

1
2
3
4
5
$sql = "SELECT * FROM `user`";
$stmt = $conn->prepare($sql);
if (!$stmt->execute()) {
echo $stmt->errorInfo[2];//直接echo第三个数字键错误消息
}

事务

使用PDO中的事务的时候只需要

  • 语句运行之前调用beginTransaction()方法启动事务
  • 语句执行后调用commit()方法提交事务
  • 出错的时候调用rollback()回滚
1
2
3
4
5
6
7
8
9
10
try{
$conn = new PDO($dsn, $db_user, $db_password);
$conn->beginTransaction();
...
$conn->commit();
...
}catch(PDOException $e){
$conn->rollBack();
echo $e->getMessage();
}

参考资料:

Just a beginner.<br /><a href='https://github.com/yaoshanliang/about' target='_blank'>profile</a>