为什么你应该使用PHP PDO访问数据库
许多PHP程序员学习过如歌使用MySQL或MySQL扩展来访问数据库. 不过,自PHP 5.1版本以来,一个更好的解决方案出现了. PHP Data Objects (PDO) 提供了让你更有{productive}的准备{statements},处理对象的方法.
PDO 简介
“PDO – PHP Data Objects – is a database access layer providing a uniform method of access to multiple databases.”
它不受数据库特定语法限制, 但它可以让切换数据库和平台的过程更无痛,更简洁的切换数据库连接字符串.
这个教程并是一个完全的SQL入门. 它主要是为了帮助那些已经在使用MySQL或MySQLi扩展的人们过渡到更加强大、兼容性更好的PDO.
数据库支持
这个扩展能支持任何为PDO设计了驱动的数据库. 在写这篇文章的时候,以下数据库已经被支持:
- PDO_DBLIB ( FreeTDS / Microsoft SQL Server / Sybase )
- PDO_FIREBIRD ( Firebird/Interbase 6 )
- PDO_IBM ( IBM DB2 )
- PDO_INFORMIX ( IBM Informix Dynamic Server )
- PDO_MYSQL ( MySQL 3.x/4.x/5.x )
- PDO_OCI ( Oracle Call Interface )
- PDO_ODBC ( ODBC v3 (IBM DB2, unixODBC and win32 ODBC) )
- PDO_PGSQL ( PostgreSQL )
- PDO_SQLITE ( SQLite 3 and SQLite 2 )
- PDO_4D ( 4D )
所有这些驱动都没有被您的系统预装,这里有一种快速的方式来找到您需要的驱动:
print_r(PDO::getAvailableDrivers());
连接
不同的数据库可能在连接方法上有那么一点点的不同. 下面,我们将介绍几种常见的数据库的连接方法. 你将会注意到前三种看起来差不多, 不过像SQLite之类的语言就有他自己独特的语法.
# MS SQL Server and Sybase with PDO_DBLIB
$DBH = new PDO(“mssql:host=$host;dbname=$dbname, $user, $pass”);
$DBH = new PDO(“sybase:host=$host;dbname=$dbname, $user, $pass”);
$DBH = new PDO(“mysql:host=$host;dbname=$dbname”, $user, $pass);
$DBH = new PDO(“sqlite:my/database/path/database.db”);
}
catch(PDOException $e) {
echo $e->getMessage();
}
请注意try/catch代码块 – 您应该始终将您 PDO 的操作封装在一个 try/catch 代码块内并使用异常机制 .通常你只会使用单个连接 – 下面将为您介绍它的语法.
下文中出现的 $DBH 意思是 ‘database handle’.
你可以通过把handle设置为null来关闭任何数据库连接.
# close the connection
$DBH = null;
你可以从 PHP.net 获取更多关于特点数据库的选项和连接字串(connection strings)的信息
异常处理
PDO 可以使用异常(Exceptions)来处理错误,这意味你需要把{处理PDO的}包括在一个try/catch代码块. 你也可以通过设置错误模式(error mode attribute)强制PDO在您最近创建的数据库连接上使用这三种错误模式中的一种. 以下提供了语法:
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
不过不管您设置什么错误模式, 错误的连接总会产生一个异常, 所以您应该在创建一个数据库时包含一个try/catch代码块.
PDO::ERRMODE_SILENT
这是默认的错误模式. 如果您使用了这种错误模式, 你将像您用mysql或mysqli扩展的时候那样自己检查错误. 这里还有两种更理想的符合[[DRY programming]]思想的方法.
PDO::ERRMODE_WARNING
这种模式将会发出(issue)一个标准的PHP warning,然后继续执行程序. 这种方法在调试时会很有用.
PDO::ERRMODE_EXCEPTION
这也许是人们在大多数情况下希望使用的模式. 它抛出(fire)一个异常, 允许你优雅的处理错误并且隐藏那些可能会导致安全风险的数据. 这里是一个处理异常的实例:
try {
$DBH = new PDO(“mysql:host=$host;dbname=$dbname”, $user, $pass);
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$DBH->prepare(‘DELECT name FROM people’);
}
catch(PDOException $e) {
echo “I’m sorry, Dave. I’m afraid I can’t do that.”;
file_put_contents(‘PDOErrors.txt’, $e->getMessage(), FILE_APPEND);
}
上面是一个在select statement中的内部错误; 这将会引发一个异常. 这段异常处理代码将会把错误详情发送到一个日志文件中, 然后显示一个友好的(当然,友不友好随你便)消息给用户.
插入、更新数据
插入新数据、或更新一个已经存在的数据,是最常见的数据库操作之一. PDO提供了一种[[normally a two-step process]]. 本节中介绍的所有内容同样适用于 UPDATE 和 INSERT 操作.
下面是一个最基本的插入的例子:
# STH means “Statement Handle”
$STH = $DBH->prepare(“INSERT INTO folks ( first_name ) values ( ‘Cathy’ )”);
$STH->execute();
您也可以直接使用exec()方法完成相同的操作(PS:使用exec()方法可以减少一个调用). 在大多数情况下,您可能会多次调用这个方法, 所以呢, 您可以享受到prepared statements带来的好处. 甚至如果您只想调用它一次, 使用prepared statements will 也会帮您挡住 SQL injection 攻击.
Prepared Statements
使用 prepared statements 将帮助您防止 SQL injection 的危险.
Prepared statement的语句是只需要发送数据到服务器的预编译 SQL 语句, 它具有自动处理数据以免受 SQL injection 攻击的优点。
您可以通过在您的SQL语句中包含占位符来使用 prepared statement . 这里有三个例子: 一个没有占位符的, 一个有未命名占位符(Unnamed Placeholders)的, 和一个有命名占位符(Named Placeholders)的.
$STH = $DBH->(“INSERT INTO folks (name, addr, city) values ($name, $addr, $city)”);
$STH = $DBH->(“INSERT INTO folks (name, addr, city) values (?, ?, ?);
$STH = $DBH->(“INSERT INTO folks (name, addr, city) value (:name, :addr, :city)”);
您可能想避免使用第一种方法; 下面为您提供了它们直接的比较. 选择未命名占位符或命名占位符将会影响您如何为这些语句设置数据.
未命名占位符(Unnamed Placeholders)
$STH->bindParam(1, $name);
$STH->bindParam(2, $addr);
$STH->bindParam(3, $city);
$name = “Daniel”
$addr = “1 Wicked Way”;
$city = “Arlington Heights”;
$STH->execute();
$name = “Steve”
$addr = “5 Circle Drive”;
$city = “Schaumburg”;
$STH->execute();
只需两步!首先,我们为不同的占位符(Placeholder)绑定变量 (lines 2-4). 然后,我们为那些占位符(Placeholder)赋值然后执行查询. 要想发送另外的一组数据,只需要改变那些变量的值,然后再执行即可.
(译注: 原文在第一步与第二布均使用了assign来描述过程)
这个使用很多参数的方法似乎有点麻烦?如果您的数据存储在数组中,有一个简单的方法:
$data = array(‘Cathy’, ’9 Dark and Twisty Road’, ‘Cardiff’);
$STH->execute($data);
这很简单,不是吗?
在数组中的数据等同于占位符。 $data[0]对应第一个占位符,$data[1]第二个,依此类推,但如果您的数组索引并未排序,这将无法正常工作,您将需要重新索引这个数组.
命名占位符(Named Placeholders)
您可能已经猜到语法了,下面给出了一个例子:
# placeholders always start with a colon.
$STH->bindParam(‘:name’, $name);
您也可以在这里使用一个快捷方式,但它可以和关联数组一起使用.
$data = array( ‘name’ => ‘Cathy’, ‘addr’ => ’9 Dark and Twisty’, ‘city’ => ‘Cardiff’ );
$STH = $DBH->(“INSERT INTO folks (name, addr, city) value (:name, :addr, :city)”);
$STH->execute($data);
你的数组中的键不需要以一个冒号开始,但是必须符合指定的占位符。如果你有一个二维数组(就是数组中的数组),您可以遍历它们,只需调用执行的每个数据的数组。
另一个命名占位符不错的特点是直接可以插入对象到您的数据库,如果命名的属性匹配字段的话.下面是一个例子对象:
class person {
public $name;
public $addr;
public $city;
$this->name = $n;
$this->addr = $a;
$this->city = $c;
}
# etc …
}
$STH = $DBH->(“INSERT INTO folks (name, addr, city) value (:name, :addr, :city)”);
$STH->execute((array)$cathy);
在执行中,对象被转换为一个数组.对象的属性被视为数组中的一个键. By casting the object to an array in the execute, the properties are treated as array keys.
选择数据
数据通过fetch()方法获得, {一种应用于陈述式句柄的方法}. 在使用fetch之间, 您最好告诉PDO您喜欢取得数据的样子. 您有以下几个选择:
- PDO::FETCH_ASSOC: 返回一个包含列名索引的数组
- PDO::FETCH_BOTH (default): 返回一个由同时包含列名和数字索引的数组
- PDO::FETCH_BOUND: 通过 ->bindColumn() 方法将列的值赋到变量上。
- PDO::FETCH_CLASS:列的值赋给指定对象的属性里。如果指定的属性不存在,会自动创建。
- PDO::FETCH_INTO: 更新一个已经存在的命名对象的实例
- PDO::FETCH_LAZY: 结合 了PDO::FETCH_BOTH,PDO::FETCH_OBJ,在它们被调用时创建对象变量
- PDO::FETCH_NUM: 返回一个由同时包含列数字索引的数组
- PDO::FETCH_OBJ: fanhuire返回一个有对应的列名的属性的匿名对象
在现实中,大多数情况下会使用以下三种: FETCH_ASSOC, FETCH_CLASS, FETCH_OBJ. 您需要使用以下语法设置获取类型:
$STH->setFetchMode(PDO::FETCH_ASSOC);
您也可以直接在fetch()方法中设置获取模式.
FETCH_ASSOC
这种模式创建一个按列名索引的关联数组.这应该会让用过MySQL/MySQLi扩展的人感到亲切.这里有一个使用这种方法选择数据的例子.
# values in the select statement.
$STH = $DBH->query(‘SELECT name, addr, city from folks’);
$STH->setFetchMode(PDO::FETCH_ASSOC);
echo $row['name'] . “n”;
echo $row['addr'] . “n”;
echo $row['city'] . “n”;
}
这个 while 循环将在获取完所有数据后停止.{The while loop will continue to go through the result set one row at a time until complete.}
FETCH_OBJ
这种模式为每一行数据创建一个标准类,下面是一个例子:
$STH = $DBH->query(‘SELECT name, addr, city from folks’);
$STH->setFetchMode(PDO::FETCH_OBJ);
while($row = $STH->fetch()) {
echo $row->name . “n”;
echo $row->addr . “n”;
echo $row->city . “n”;
}
FETCH_CLASS
您的对象的属性应该在constructor被调用前设置!这一点很重要!
这种模式允许你直接将获取的数据发送到您选择的类中.当您使用FETCH_CLASS时,您的对象的属性应该在constructor被调用前设置。读一遍,它是重要的。如果属性相匹配的列名不存在,这些属性将被创建,(公共)为您。
这意味着如果你需要转换后出来的数据,它可以通过你的对象自动为转换.
举个列子,假设的情况下该地址必须为特定格式,我们可以通过constructor上做到这一点,下面是一个例子:
public $name;
public $addr;
public $city;
public $other_data;
$this->address = preg_replace(‘/[a-z]/’, ‘x’, $this->address);
$this->other_data = $other;
}
}
OK,再让我们看看效果如何?
$STH->setFetchMode(PDO::FETCH_CLASS, ‘secret_person’);
echo $obj->addr;
}
如果地址是,‘5 Rosebud,’ 您将会在输出中看到 ‘5 Rxxxxxx’. 当然,可能有些情况下,您希望在constructor函数在数据被赋值之前调用.PDO也可以做到~
现在,当你使用这个模式(PDO::FETCH_PROPS_LATE)的地址不会被遮,constructor会被首先调用然后再赋值.
最后,如果你真的需要,你可以在使用PDO获取数据到对象时,将参数传递给构造函数:
如果你需要传递不同的数据到每个对象的构造函数,你可以设置在fetch方法内设置模式法模式:
while($rowObj = $STH->fetch(PDO::FETCH_CLASS, ‘secret_person’, array($i))) {
// do stuff
$i++
}
其他有用的方法
虽然 PDO 并没有面面俱到的(这扩展可不小!), 这里仍还还有一些您想知道的方法.
$DBH->lastInsertId();
lastInsertId()方法始终调用数据库句柄,而不是表达式的句柄,并且会返回该数据库连接上一次插入语句的自增ID.
$DBH->exec(‘DELETE FROM folks WHERE 1′);
$DBH->exec(“SET time_zone = ‘-8:00′”);
exec()方法用于那些不能返回数据或不影响行的操作. 上面是两种调用exec()方法的例子.
$safe = $DBH->quote($unsafe);
quote() 方法将字符转义为安全的字符以便在查询中使用. 如果您不使用已经准备号的语句,您可以用此方法<<*>>。
$rows_affected = $STH->rowCount();
rowCount() 方法返回一个表明被一个操作影响的行数的整数(简直是废话,难不成还是浮点数?). 更具这个错误报告(http://bugs.php.net/40822) ,在最近的一个PDO版本上这个方法不能够很好的与SELECT语句工作. 如果您遇到了这个问题而不想升级PHP的话, 你可以用以下的方法来替代它:
if ($STH = $DBH->query($sql)) {
# check the row count
if ($STH->fetchColumn() > 0) {
}
else {
echo “No rows matched the query.”;
}
}
结尾
我希望这篇文章能帮助您从mysql和mysqli扩展迁移至PDO.您有啥想法?现在想迁移到PDO么?
英文原文:http://net.tutsplus.com/tutorials/php/why-you-should-be-using-phps-pdo-for-database-access/