zoukankan      html  css  js  c++  java
  • PHP开发-PDO

    PHP 支持多种数据库,如 MySQL、PostgreSQL、SQLite 和 Oracle 等,并且这些数据库都提供了用于 PHP 和相应数据库之间通信的扩展,如 mysqli、sqlite3 等。这样造成的一个问题是如果项目中使用了多种数据库,需要安装并使用多种 PHP 数据库扩展和接口,增加了学习和维护的成本。为此,从 PHP 5.1 开始引入了一个新的扩展 —— PDO。

    PDO 扩展

    PDO(PHP Data Objects)是一系列 PHP 类,抽象了不同数据库的具体实现,只通过同一套接口就可以与不同的数据库通信,极大地降低了学习成本,同时提高开发效率。

    注:尽管 PDO 为不同数据库提供了统一接口,但是仍然必须自己编写 SQL 语句,这是 PDO 的劣势所在。这样存在的一个隐患是不同的数据库 SQL 语句语法可能略有出入,所以在切换数据系统的时候需要注意这一点,建议尽可能编写符合 ANSI/ISO 标准的 SQL 语句。

    数据库连接和DSN

    PDO 的构造函数中有一个字符串参数,用于指定 DSN(Data Source Name)来提供数据库连接的详细信息:

    <?php
    try {
        $pdo = new PDO(
            'mysql:host=127.0.0.1;dbname=test;port=3306;charset=utf8',
            'username',
            'password'
        );
    } catch (PDOException $ex) {
        echo 'database connection failed';
        exit();
    }
    

    可见 DSN 主要包含信息包括:数据库主机名/IP地址、端口号、数据库名称、字符集。PDO构造函数的第二个参数是数据库用户名,第三个参数是该用户名对应密码。

    Laravel 框架的数据库组件就是基于PDO实现的,底层使用工厂模式为不同的数据库创建相应的 PDO 实例,Laravel 默认使用 MySQL 数据库,对应的 PDO 实例通过 MySqlConnector 类的 connect() 方法创建,并且最终落地到 createPdoConnection 方法实现:

    保证数据库凭证的安全:

    为了保证数据库凭证(用户名/密码)的安全,不能将其硬编码在代码中,尤其是可以公开访问的 PHP 文件。我们应该将其存放在一个位于文档根目录之外的配置文件中,然后在需要使用凭证的地方引入,正如 Laravel 所实现的那样(用户名密码信息位于 .env,隔离在版本控制之外):

    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=homestead
    DB_USERNAME=homestead
    DB_PASSWORD=secret
    

    然后在 config/database.php 中引入:

    'mysql' => [
        'driver' => 'mysql',
        'host' => env('DB_HOST', '127.0.0.1'),
        'port' => env('DB_PORT', '3306'),
        'database' => env('DB_DATABASE', 'forge'),
        'username' => env('DB_USERNAME', 'forge'),
        'password' => env('DB_PASSWORD', ''),
        'unix_socket' => env('DB_SOCKET', ''),
        'charset' => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
        'prefix' => '',
        'strict' => true,
        'engine' => null,
    ],
    

    预处理语句

    现在我们建立了一个连接到数据库的 PDO 实例,通过这个实例就可以使用 SQL 语句从数据库读取数据,或者将数据写入数据库。

    开发 PHP 应用时,我们经常需要从 HTTP 请求中获取动态值来定制 SQL 语句,如使用 /user?name=nonfu 来显示具体账户的资料信息,对应的 SQL 语句可能是:

    SELECT * FROM user WHERE name = "nonfu";
    

    初级 PHP 开发者可能会像这样构建这个 SQL 语句:

    $sql = sprintf(
        'SELECT * FROM user WHERE name = "%s"', 
        filter_input(INPUT_GET, 'name')
    );
    

    这样做就会有 SQL 注入隐患。所以,在基于用户请求参数构建 SQL 语句时,一定要过滤用户输入参数值。幸运的是,在 PDO 中,我们可以通过预处理语句和参数绑定来实现用户输入过滤,从而避免 SQL 注入。

    预处理语句是一个 PDOStatement 实例,我们可以通过 prepare() 方法来返回该实例:

    <?php
    $sql = 'SELECT * FROM user WHERE name = :name';
    $statement = $pdo->prepare($sql);
    $name = filter_input(INPUT_GET, 'name');
    $statement->bindValue(':name', $name, PDO::PARAM_STR);
    

    预处理语句会自动过滤 $name 的值,防止数据库遭受 SQL 注入攻击。

    Laravel 框架中相应的底层实现位于 MySqlConnetion 的父类 Connectionselect 方法中:

    查询结果

    有了预处理语句之后,就可以在数据库中执行 SQL 查询了,调用预处理语句的 execute() 方法后就会使用绑定的所有数据执行 SQL 语句,如果执行的是 INSERT、UPDATE 或 DELETE 语句,执行完 execute() 方法工作结束了,如果执行的是 SELECT 语句,我们还期望数据库能返回匹配的结果。我们可以使用以下方法获取查询结果:

    • fetch()
    • fetchAll()
    • fetchColumn()
    • fetchObject()

    fetch() 方法用于获取结果集的下一行,我们可以使用这个方法迭代大型结果集:

    $sql = 'SELECT * FROM user WHERE name = :name';
    $statement = $pdo->prepare($sql);
    $name = filter_input(INPUT_GET, 'name');
    $statement->bindValue(':name', $name, PDO::PARAM_STR);
    $statement->execute();
    
    while (($result = $statement->fetch(PDO::FETCH_ASSOC)) !== FALSE) {
        echo $result['name'];
    }
    

    我们在调用 fetch() 方法时,传入了 PDO::FETCH_ASSOC 参数,该参数决定如何返回查询结果,该参数支持以下常量:

    • PDO::FETCH_ASSOC:返回关联数组,数组的键是数据表的列名
    • PDO::FETCH_NUM:返回键为数字的数组
    • PDO::FETCH_BOTH:顾名思义,返回一个既有键为列名又有键为数字的数组
    • PDO::FETCH_OBJ:返回一个对象,对象的属性是数据表的列名

    如果处理的小型结果集合,可以使用 fetchAll() 方法获取所有查询结果,Laravel 框架底层的 select() 方法中就使用了该方法来获取返回结果集,所以在获取大量结果时不能使用该方法:

    $sql = 'SELECT * FROM user WHERE name = :name';
    $statement = $pdo->prepare($sql);
    $name = filter_input(INPUT_GET, 'name');
    $statement->bindValue(':name', $name, PDO::PARAM_STR);
    $statement->execute();
    
    $results = $statement->fetchAll(PDO::FETCH_ASSOC);
    if ($results) {
        foreach ($results as $result) {
            echo $result['name'];
        }
    }
    

    如果只关心查询结果中的一列,可以使用 fetchColumn() 方法:

    $sql = 'SELECT id, name FROM user WHERE name = :name';
    $statement = $pdo->prepare($sql);
    $name = filter_input(INPUT_GET, 'name');
    $statement->bindValue(':name', $name, PDO::PARAM_STR);
    $statement->execute();
    
    while (($name = $statement->fetchColumn(1)) !== FALSE) {
        echo $name;
    }
    

    我们还可以使用 fetchObject() 方法获取查询结果中的行,这个方法把行当做对象:

    $sql = 'SELECT id, name FROM user WHERE name = :name';
    $statement = $pdo->prepare($sql);
    $name = filter_input(INPUT_GET, 'name');
    $statement->bindValue(':name', $name, PDO::PARAM_STR);
    $statement->execute();
    
    while (($result = $statement->fetchObject()) !== FALSE) {
        echo $result->name;
    }
    

    事务

    事务是把一系列数据库操作当作一个逻辑单元执行,也就是说,事务中的一系列 SQL 语句要么都执行成功,要么都失败,事务的原子性能保证数据的一致性、安全性和持久性。
    PDO 扩展中使用事务很容易,只需把想要执行的 SQL 语句放在 PDO 实例的 beginTransaction() 方法和 commit() 方法之间即可。

    在 Laravel 框架中对事务操作进行了封装,我们可以这样调用:

    DB::transaction(function () {
        DB::table('users')->update(['votes' => 1]);
    
        DB::table('posts')->delete();
    }, 5);
    

    相应的底层位于 ManagesTransactions trait 中:

    还可以这样调用:

    DB::beginTransaction();
    try {
        DB::table('users')->update(['votes' => 1]);
        DB::table('posts')->delete();
        DB::commit();
    } catch (PDOException $ex) {
        var_dump($ex);
        DB::rollback();
    }
    

    相应的底层实现同样位于 ManagesTransactions,可自行查看。

  • 相关阅读:
    程序员的7中武器
    需要强化的知识
    微软中国联合小i推出MSN群Beta 不需任何插件
    XML Notepad 2006 v2.0
    Sandcastle August 2006 Community Technology Preview
    [推荐] TechNet 广播 SQL Server 2000完结篇
    《太空帝国 4》(Space Empires IV)以及 xxMod 英文版 中文版 TDM Mod 英文版 中文版
    IronPython 1.0 RC2 更新 1.0.60816
    Microsoft .NET Framework 3.0 RC1
    《Oracle Developer Suite 10g》(Oracle Developer Suite 10g)V10.1.2.0.2
  • 原文地址:https://www.cnblogs.com/stringarray/p/12894238.html
Copyright © 2011-2022 走看看