为什么 PHP 应该使用 PDO 方式访问数据库 已翻译 100%

冻鱼 投递于 2013/06/07 11:56 (共 14 段, 翻译完成于 06-17)
阅读 11259
收藏 187
PHP
20
加载中

很多程序员都学习过如何使用 MySQL 或 MySQLi 扩展访问数据库。在 PHP 5.1 中,有一个更好的方法。 PHP Data Objects (PDO) 提供了很多预处理语句的方法,且使用对象将使你的工作更有成效!

PDO 介绍

“PDO – PHP Data Objects – 是一个对多种数据库提供统一操作方法的数据库访问层。”

它并不具备数据库特有的语法,但它将使切换数据库和平台更加容易,多数情况下,只需要简单修改链接字符串。

PDO - db abstraction layer

这并非一篇完整教导如何使用SQL的教程。它重要为那些现今仍在使用 mysql 或 mysqli 扩展的人,帮助他们跃至更具可移植性和强力的 PDO。

K6F
K6F
翻译于 2013/06/17 10:13
2

数据库支持

此扩展可以使用 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());  
K6F
K6F
翻译于 2013/06/17 10:16
4

连接

不同数据库的连接方法可能稍有不同,下面是一些较为流行的数据库连接方法。你将注意到,虽然数据库类型不同,前三种数据库的连接方式是相同的——而 SQLite 使用自己的语法。

Connection String
    try {  
      # 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");  
      
      # MySQL with PDO_MYSQL  
      $DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);  
      
      # SQLite Database  
      $DBH = new PDO("sqlite:my/database/path/database.db");  
    }  
    catch(PDOException $e) {  
        echo $e->getMessage();  
    }  

注意 try/catch 块——你应该总是使用 try/catch 包装你的 PDO 操作,并使用异常机制——这里只是简单的示例。通常,你只需要一个连接——有很多可以教你语法的列表。 $DBH 代表“数据库句柄”,这将贯穿全文。

通过将句柄设置为 NULL,你可以关闭任一连接。

# close the connection  
$DBH = null;  

你可以在PHP.net找到更多数据库特定选项和/或其它数据库连接字符串的信息。

K6F
K6F
翻译于 2013/06/17 10:43
2

异常与 PDO

PDO 可以使用异常处理错误,这意味着你的所有 PDO 操作都应当包装在一个 try/catch 块中。你可以通过设定错误模式属性强制 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 编程。

PDO::ERRMODE_WARNING

此方法将会发出一个标准PHP警告,并允许程序继续运行。这对调试很有帮助。

PDO::ERRMODE_EXCEPTION

这是多数情况下你所希望的方式。它生成异常,允许你更容易的处理错误,隐藏可能导致它人了解你系统的信息。下面是一个充分利用异常的示例:

    # connect to the database  
    try {  
      $DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);  
      $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );  
      
      # UH-OH! Typed DELECT instead of SELECT!  
      $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 语句中有一个故意留下的错误;这将导致一个异常。异常错误细节保存至一个 log 文件,并生成一段友好的(或不怎么友好的)信息於用户。

K6F
K6F
翻译于 2013/06/17 11:10
3

插入和更新

插入新数据,更新已存数据是一种非常常见的数据库操作。使用 PDO,这通常需要两个步骤。本节中所述的所有内容对更新插入都有效。

2 to 3 step insert and update

这里有一个最基本的插入示例:

# STH means "Statement Handle"
$STH = $DBH->prepare("INSERT INTO folks ( first_name ) values ( 'Cathy' )");
$STH->execute();

你也可以使用 exec() 完成相同的操作,这将减少调用。多数情况下,你会使用调用多的方法,以充分利用语句预处理的优势。即使你只用它一次,使用语句预处理,帮助你保护你的 SQL 免于注入攻击。

K6F
K6F
翻译于 2013/06/17 11:30
4

预处理语句

使用语句预处理将帮助你免于SQL注入攻击。

一条预处理语句是一条预编译的 SQL 语句,它可以使用多次,每次只需将数据传至服务器。其额外优势在于可以对使用占位符的数据进行安全处理,防止SQL注入攻击。

你通过在 SQL 语句中使用占位符的方法使用预处理语句。下面是三个例子:一个没有占位符,一个使用无名占位符,一个使用命名占位符。

# no placeholders - ripe for SQL Injection!
$STH = $DBH->("INSERT INTO folks (name, addr, city) values ($name, $addr, $city)");

# unnamed placeholders
$STH = $DBH->("INSERT INTO folks (name, addr, city) values (?, ?, ?);

# named placeholders
$STH = $DBH->("INSERT INTO folks (name, addr, city) value (:name, :addr, :city)");

你希望避免第一种方法。选择命名我无名占位符将会对你对语句中数据的设置产生影响。

K6F
K6F
翻译于 2013/06/17 11:38
3

无名占位符

# assign variables to each place holder, indexed 1-3
$STH->bindParam(1, $name);
$STH->bindParam(2, $addr);
$STH->bindParam(3, $city);

# insert one row
$name = "Daniel"
$addr = "1 Wicked Way";
$city = "Arlington Heights";
$STH->execute();

# insert another row with different values
$name = "Steve"
$addr = "5 Circle Drive";
$city = "Schaumburg";
$STH->execute();

这里有两步。首先,我们对各个占位符指定变量(2-4行)。然后,我们对各个占位符指定数据,并执行语句。要发送另一组数据,只需改变这些变量的值并再次执行语句。

这种方法看上去对拥有很多参数的语句很笨拙吧?的确。然而,当数据保存于数组中时,这非常容易简略:

# the data we want to insert
$data = array('Cathy', '9 Dark and Twisty Road', 'Cardiff');

$STH = $DBH->("INSERT INTO folks (name, addr, city) values (?, ?, ?);
$STH->execute($data);

容易吧!

数组中的数据按顺序填入占位符中。 $data[0]是第一个,$data[1]是第二个,依次。不过,要是数组中数据的次序不正确,这将不能正常运行,你需要先对数组排序。

K6F
K6F
翻译于 2013/06/17 11:46
2

命名占位符

你可能已经开始猜测语法了,不过下面就是示例:

# the first argument is the named placeholder name - notice named
# placeholders always start with a colon.
$STH->bindParam(':name', $name);

你可以看使用快捷方式,但它需使用关联数组。下面是示例:

# the data we want to insert
$data = array( 'name' => 'Cathy', 'addr' => '9 Dark and Twisty', 'city' => 'Cardiff' );

# the shortcut!
$STH = $DBH->("INSERT INTO folks (name, addr, city) value (:name, :addr, :city)");
$STH->execute($data);

数组中的键不需要以冒号开头,但其它部分需要同占位符匹配。如果你有一个二维数组,你只需遍历它,并对遍历的每个数组执行语句。

命名占位符的另一个好的功能是直接将对象插入到你的数据库中,只要属性同命名字段匹配。下面是一个示例对象,以及如何将它插入到数据库中的示例:

# a simple object
class person {
    public $name;
    public $addr;
    public $city;

    function __construct($n,$a,$c) {
        $this->name = $n;
        $this->addr = $a;
        $this->city = $c;
    }
    # etc ...
}

$cathy = new person('Cathy','9 Dark and Twisty','Cardiff');

# here's the fun part:
$STH = $DBH->("INSERT INTO folks (name, addr, city) value (:name, :addr, :city)");
$STH->execute((array)$cathy);

通过在执行时将对象转换为数组,输将将会同数组的键一样对待。

K6F
K6F
翻译于 2013/06/17 11:55
2

Selecting Data

Fetch data into arrays or objects

数据通过语句句柄的->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:返回一个匿名对象,属性名称对应列名。

在实际应用中,三个就能涵盖大多数情况:FETCH_ASSOC、FETCH_CLASS 和 FETCH_OBJ。要设定 fetch 方法,使用如下语法:

$STH->setFetchMode(PDO::FETCH_ASSOC);

你也可以在调用 ->fetch() 方法时直接设定。

K6F
K6F
翻译于 2013/06/17 13:53
2

FETCH_ASSOC

这个 fetch 创建一个关联数组,通过列的名称索引。这对使用过 mysql/mysqli 扩展的人应该相当熟悉。下面是通过此方法获取数据的示例:

# using the shortcut ->query() method here since there are no variable
# values in the select statement.
$STH = $DBH->query('SELECT name, addr, city from folks');

# setting the fetch mode
$STH->setFetchMode(PDO::FETCH_ASSOC);

while($row = $STH->fetch()) {
    echo $row['name'] . "\n";
    echo $row['addr'] . "\n";
    echo $row['city'] . "\n";
}

while 循环将继续逐行遍历结果集,直到遍历完毕。

FETCH_OBJ

此 fetch 将为返回数据的每一行创建一个标准对象。示例如下:

# creating the statement
$STH = $DBH->query('SELECT name, addr, city from folks');

# setting the fetch mode
$STH->setFetchMode(PDO::FETCH_OBJ);

# showing the results
while($row = $STH->fetch()) {
    echo $row->name . "\n";
    echo $row->addr . "\n";
    echo $row->city . "\n";
}
K6F
K6F
翻译于 2013/06/17 13:57
3
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(38)

李惟
李惟

引用来自“wwek”的评论

引用来自“李惟”的评论

引用来自“伏伟”的评论

引用来自“李惟”的评论

引用来自“Raynor1”的评论

引用来自“简单代码”的评论

PDO单次执行效率可能不如mysqli,但大吞吐量下稳定性优于mysqli,已经有大量测试证明了一点。

我现在比较郁闷的是php没有连接池,长连接到底是个什么东西呢?没有一个透彻的讲解,求科普!

PHP的手册里面有写,可以看一下他们官方的文档,写得还是很不错的。。

我也不用PDO,数据库没机会基础那么多,我只用mysql,根本就不需要PDO。

灰常有趣的答案 ,不用PDO 之用MYSQL,这句话 只能够说 你都没有看文章,PDO又不是数据库,仅仅只是一个抽象类。就好比如你写一个MYSQL的数据库驱动类一样。

你想多了,我说我没机会用到mysql之外的数据库,pdo对我来说意义不大

PDO能根本杜绝 SQL注入少年~

原来如此
wwek
wwek

引用来自“李惟”的评论

引用来自“伏伟”的评论

引用来自“李惟”的评论

引用来自“Raynor1”的评论

引用来自“简单代码”的评论

PDO单次执行效率可能不如mysqli,但大吞吐量下稳定性优于mysqli,已经有大量测试证明了一点。

我现在比较郁闷的是php没有连接池,长连接到底是个什么东西呢?没有一个透彻的讲解,求科普!

PHP的手册里面有写,可以看一下他们官方的文档,写得还是很不错的。。

我也不用PDO,数据库没机会基础那么多,我只用mysql,根本就不需要PDO。

灰常有趣的答案 ,不用PDO 之用MYSQL,这句话 只能够说 你都没有看文章,PDO又不是数据库,仅仅只是一个抽象类。就好比如你写一个MYSQL的数据库驱动类一样。

你想多了,我说我没机会用到mysql之外的数据库,pdo对我来说意义不大

PDO能根本杜绝 SQL注入少年~
philo_xiie
philo_xiie
不错,学习了
李惟
李惟

引用来自“伏伟”的评论

引用来自“李惟”的评论

引用来自“Raynor1”的评论

引用来自“简单代码”的评论

PDO单次执行效率可能不如mysqli,但大吞吐量下稳定性优于mysqli,已经有大量测试证明了一点。

我现在比较郁闷的是php没有连接池,长连接到底是个什么东西呢?没有一个透彻的讲解,求科普!

PHP的手册里面有写,可以看一下他们官方的文档,写得还是很不错的。。

我也不用PDO,数据库没机会基础那么多,我只用mysql,根本就不需要PDO。

灰常有趣的答案 ,不用PDO 之用MYSQL,这句话 只能够说 你都没有看文章,PDO又不是数据库,仅仅只是一个抽象类。就好比如你写一个MYSQL的数据库驱动类一样。

你想多了,我说我没机会用到mysql之外的数据库,pdo对我来说意义不大
伏伟
伏伟

引用来自“李惟”的评论

引用来自“Raynor1”的评论

引用来自“简单代码”的评论

PDO单次执行效率可能不如mysqli,但大吞吐量下稳定性优于mysqli,已经有大量测试证明了一点。

我现在比较郁闷的是php没有连接池,长连接到底是个什么东西呢?没有一个透彻的讲解,求科普!

PHP的手册里面有写,可以看一下他们官方的文档,写得还是很不错的。。

我也不用PDO,数据库没机会基础那么多,我只用mysql,根本就不需要PDO。

灰常有趣的答案 ,不用PDO 之用MYSQL,这句话 只能够说 你都没有看文章,PDO又不是数据库,仅仅只是一个抽象类。就好比如你写一个MYSQL的数据库驱动类一样。
李惟
李惟

引用来自“Raynor1”的评论

引用来自“简单代码”的评论

PDO单次执行效率可能不如mysqli,但大吞吐量下稳定性优于mysqli,已经有大量测试证明了一点。

我现在比较郁闷的是php没有连接池,长连接到底是个什么东西呢?没有一个透彻的讲解,求科普!

PHP的手册里面有写,可以看一下他们官方的文档,写得还是很不错的。。

我也不用PDO,数据库没机会基础那么多,我只用mysql,根本就不需要PDO。
喻恒春
喻恒春
PDO提供了一致性访问接口,prepare 解决SQL注入。毫无疑问PHP开发应该使用。这个词非常精确。PDO已经出现很多年了。不是新生实物。前面竟然有为数不少的奇怪回复。如果他们是PHP程序员的化,那确实很丢人。如果他们不是PHP程序员,喷子而已。
奇葩100
奇葩100
这是微软当年的oledb那套理论和技术吗?
月影又无痕
月影又无痕
使用PDO的prepare的参数化查询,可以从根本上解决SQL注入的问题
这是使用PDO最重要的原因。

其实你提到可更换数据库,这个其实在真实的项目中,几乎是不现实的。绝大多数项目,当更换数据库后,程序基本都得重构。
K6F
K6F

引用来自“路扬”的评论

引用来自“Khiyuan”的评论

额,说实话,我更喜欢active recode 的方式

ar 就是一个坑

我觉得 ar 的访问方式很有趣,不过我在用的ar是自己模仿写的,其实还是通过 PDO 进行操作的
返回顶部
顶部