PDO 解决数据库乱码方法
发布时间:2017-02-27, 16:45:34 分类:PHP | 编辑 off 网址 | 辅助
正文 1037字数 786,854阅读
当使用 PDO 连接操作数据库的时候,有时会出现:保存在数据库中的汉字为乱码。以文件为 UTF-8 格式,其解决方法如下:(1)实例化的对象直接执行 query() 方法或者 exec() 方法:
<?php
class DB {
static public function getDB() {
try {
$_opts_values = array(PDO::ATTR_PERSISTENT=>true,PDO::ATTR_ERRMODE=>2);
$_pdo = new PDO(DB_DSN, DB_NAME, DB_PASS, $_opts_values);
} catch (PDOException $e) {
exit('数据库连接错误!错误信息:'.$e->getMessage());
}
$_pdo->query("SET NAMES utf8"); // $_pdo->exec('SET NAMES utf8'); //设置数据库编码,两种方法都可以
return $_pdo;
}
}
?>
Run code
Cut to clipboard
(2)在实例化 PDO 的第四个参数添加 MYSQL_ATTR_INIT_COMMAND 属性:
<?php
class DB {
static public function getDB() {
try {
$_opts_values = array(PDO::ATTR_PERSISTENT=>true,PDO::ATTR_ERRMODE=>2,PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES utf8');
$_pdo = new PDO(DB_DSN, DB_NAME, DB_PASS, $_opts_values);
} catch (PDOException $e) {
exit('数据库连接错误!错误信息:'.$e->getMessage());
}
return $_pdo;
}
}
?>
Run code
Cut to clipboard
(支付宝)给作者钱财以资鼓励 (微信)→
有过 6 条评论 »
PHP PDO 预处理语句与存储过程
$db = new PDO(DB_DSN, DB_USERNAME, DB_PASSWORD, $pdo_options); $query = $database->prepare('SELECT * FROM table WHERE name LIKE :name'); $query->bindValue(':name', '%'.$name.'%', PDO::PARAM_STR); $query->execute(); while ($results = $query->fetch()) { echo $results['name']; }
如果字段名需要动态参与sql执行,动态字段名由自己控制不存在安全问题,直接写在sql串里吧,拼接起来,字段名没必要绑定
PHP-PDO-MySQL-Class
要对数据库的安全做出更加全面的考量,以下两种方式任选其一:
A. 通过添加(php 5.3.6以前版本):$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
B. 升级到php 5.3.6 (不用设置PDO::ATTR_EMULATE_PREPARES也可以)
为了程序移植性和统一安全性,建议使用$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false)方法
$dbh = new PDO("mysql:host=localhost; dbname=demo", "user", "pass"); $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); //禁用prepared statements的仿真效果 $dbh->exec("set names 'utf8'");
使用PDO访问MySQL数据库时,真正的real prepared statements 默认情况下是不使用的。为了解决这个问题,你必须禁用 prepared statements的仿真效果。
当调用 prepare() 时,查询语句已经发送给了数据库服务器,此时只有占位符 ? 发送过去,没有用户提交的数据;当调用到 execute()时,用户提交过来的值才会传送给数据库,他们是分开传送的,两者独立的,SQL攻击者没有一点机会。
但是我们需要注意的是以下几种情况,PDO并不能帮助你防范SQL注入
1、你不能让占位符 ? 代替一组值,如:
SELECT * FROM blog WHERE userid IN ( ? );
2、你不能让占位符代替数据表名或列名,如:
SELECT * FROM blog ORDER BY ?;
3、你不能让占位符 ? 代替任何其他SQL语法,如:
SELECT EXTRACT( ? FROM datetime_column) AS variable_datetime_element FROM blog;
知道以上几点之后,我们就可以总结使用PDO杜绝SQL注入的几个注意事项:
1. php升级到5.3.6+,生产环境强烈建议升级到php 5.3.9+ php 5.4+,php 5.3.8存在致命的hash碰撞漏洞。
2. 若使用php 5.3.6+, 请在在PDO的DSN中指定charset属性
3. 如果使用了PHP 5.3.6及以前版本,设置PDO::ATTR_EMULATE_PREPARES参数为false(即由MySQL进行变量处理),php 5.3.6以上版本已经处理了这个问题,无论是使用本地模拟prepare还是调用mysql server的prepare均可。在DSN中指定charset是无效的,同时set names <charset>的执行是必不可少的。
4. 如果使用了PHP 5.3.6及以前版本, 因Yii框架默认并未设置ATTR_EMULATE_PREPARES的值,请在数据库配置文件中指定emulatePrepare的值为false。
那么,有个问题,如果在DSN中指定了charset, 是否还需要执行set names <charset>呢?
是的,不能省。set names <charset>其实有两个作用:
A. 告诉mysql server, 客户端(PHP程序)提交给它的编码是什么
B. 告诉mysql server, 客户端需要的结果的编码是什么
也就是说,如果数据表使用gbk字符集,而PHP程序使用UTF-8编码,我们在执行查询前运行set names utf8, 告诉mysql server正确编码即可,无须在程序中编码转换。这样我们以utf-8编码提交查询到mysql server, 得到的结果也会是utf-8编码。省却了程序中的转换编码问题,不要有疑问,这样做不会产生乱码。
那么在DSN中指定charset的作用是什么? 只是告诉PDO, 本地驱动转义时使用指定的字符集(并不是设定mysql server通信字符集),设置mysql server通信字符集,还得使用set names <charset>指令。
$st->execute(); $result = $st->fetchAll();
在php5.3.6之后,pdo不会在本地对sql进行拼接然后将拼接后的sql传递给mysql server处理(也就是不会在本地做转义处理)。pdo的处理方法是在prepare函数调用时,将预处理好的sql模板(包含占位符)通过mysql协议传递给mysql server,告诉mysql server模板的结构以及语义。当调用execute时,将两个参数传递给mysql server。由mysql server完成变量的转移处理。将sql模板和变量分两次传递,即解决了sql注入问题。
但无法对表或字段的名字进行变量绑定。
因此当表名或字段名不确定(即可能从用户输入中获取),而需要动态组装SQL语句时,即使是用了PDO变量绑定来提供字段的值,应当注意表名和字段名部分的过滤。
方法 bindParam() 和 bindValue() 非常相似。
唯一的区别就是前者使用一个PHP变量绑定参数,而后者使用一个值。
所以使用bindParam是第二个参数只能用变量名,而不能用变量值,而bindValue至可以使用具体值。
$stm = $pdo->prepare("select * from users where user = :user"); $user = "jack"; //正确 $stm->bindParam(":user",$user); //错误 //$stm->bindParam(":user","jack"); //正确 $stm->bindValue(":user",$user); //正确 $stm->bindValue(":user","jack");
另外在存储过程中,bindParam可以绑定为input/output变量,如下面
$stm = $pdo->prepare("call func(:param1)"); $param1 = "abcd"; $stm->bindParam(":param1",$param1); //正确 $stm->execute();
存储过程执行过后的结果可以直接反应到变量上。
对于那些内存中的大数据块参数,处于性能的考虑,应优先使用前者bindParam()
PDOStatement::bindParam — 绑定一个参数到指定的变量名。
绑定一个PHP变量到用作预处理的SQL语句中的对应命名占位符或问号占位符。 不同于 PDOStatement::bindValue() ,此变量作为引用被绑定,并只在 PDOStatement::execute() 被调用的时候才取其值。
PDOStatement::bindValue — 把一个值绑定到一个参数。
绑定一个值到用作预处理的 SQL 语句中的对应命名占位符或问号占位符。
<?php $stm = $pdo->prepare("select * from users where user = :user"); $user = "jack"; //正确 $stm->bindParam(":user",$user); //错误 $stm->bindParam(":user","jack"); //正确 $stm->bindValue(":user",$user); //正确 $stm->bindValue(":user","jack"); //所以使用bindParam是第二个参数只能用变量名,而不能用变量值,而bindValue至可以使用具体值。 ?>
PDOStatement::bindColumn — 绑定一列到一个 PHP 变量。
安排一个特定的变量绑定到一个查询结果集中给定的列。每次调用 PDOStatement::fetch() 或 PDOStatement::fetchAll() 都将更新所有绑定到列的变量。
<?php function readData ( $dbh ) { $sql = 'SELECT name, colour, calories FROM fruit' ; try { $stmt = $dbh -> prepare ( $sql ); $stmt -> execute (); /* 通过列号绑定 */ $stmt -> bindColumn ( 1 , $name ); $stmt -> bindColumn ( 2 , $colour ); /* 通过列名绑定 */ $stmt -> bindColumn ( 'calories' , $cals ); while ( $row = $stmt -> fetch ( PDO :: FETCH_BOUND )) { $data = $name . "\t" . $colour . "\t" . $cals . "\n" ; print $data ; } } catch ( PDOException $e ) { print $e -> getMessage (); } } readData ( $dbh ); ?>