封装EncapsulationPHP实战PHP经验分享

如何写一个属于自己的数据库封装(2) - 数据库连接(修订版)

2017-03-09  本文已影响340人  幼年期程序猿

上一期 如何写一个属于自己的数据库封装(1) - 基本思路
下一期 如何写一个属于自己的数据库封装(3) - 查询 - 入门篇

本期要点

还是PDO 中文文档


Connector.php

首先, 建一个Connector类, 并且设置属性

<?php
class Connector {
    // 数据库地址前缀,常见的有mysql,slqlsrv,odbc等等等
    private $driver = 'mysql';
    // 数据库地址
    private $host = 'localhost';
    // 数据库默认名称, 设置为静态是因为有切换数据库的需求
    private static $db = 'sakila';
    // 数据库用户名
    private $username = 'root';
    // 数据库密码
    private $password = '';
    // 当前数据库连接
    protected $connection;
    // 数据库连接箱,切换已存在的数据库连接不需要重新通信,从这里取即可
    protected static $container = [];

    // PDO默认属性配置,具体请自行查看文档
    protected $options = [
        PDO::ATTR_CASE => PDO::CASE_NATURAL,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
        PDO::ATTR_STRINGIFY_FETCHES => false,
    ];
}

以上代码配合注释应该可以理解了所以不多解释了,直接进入函数

    protected function buildConnectString() {
        return "$this->driver:host=$this->host;dbname=".self::$db;
    }

以上函数依照最开始设置的类属性, 将返回字符串

"mysql:host=localhost;dbname=sakila;"
    public function connect() {
        try {
            // 连接数据库,生成pdo实例, 将之赋予$connection,并存入$container之中
            self::$container[self::$db] = $this->connection = new PDO($this->buildConnectString(), $this->username, $this->password, $this->options);
            // 返回数据库连接
            return $this->connection;
        } catch (Exception $e) {
            // 若是失败, 返回原因
            // 还记得dd()吗?这个辅助函数还会一直用上
            dd($e->getMessage());
        }
    }
    public function setDatabase($db) {
        self::$db = $db;
        return $this;
    }
    function __construct() {
        // 如果从未连接过该数据库, 那就新建连接
        if(empty(self::$container[self::$db]))
            $this->connect();
        // 反之, 从$container中提取, 无需再次通信
        $this->connection = self::$container[self::$db];
    }

接下来两个函数式配合着用的,单看可能会懵逼, 配合以下例子单步调试

$a = new Connector();

$bindValues = [
    'PENELOPE',
    'GUINESS'
];

dd($a->read('select * from actor where first_name = ? and last_name = ?', $bindValues));

返回结果

array (size=1)
  0 =>
    object(stdClass)[4]
      public 'actor_id' => string '1' (length=1)
      public 'first_name' => string 'PENELOPE' (length=8)
      public 'last_name' => string 'GUINESS' (length=7)
      public 'last_update' => string '2006-02-15 04:34:33' (length=19)
    public function read($sql, $bindings) {
        // 将sql语句放入预处理函数
        // $sql = select * from actor where first_name = ? and last_name = ?
        $statement = $this->connection->prepare($sql);
        // 将附带参数带入pdo实例
        // $bindings = ['PENELOPE', 'GUINESS']
        $this->bindValues($statement, $bindings);
        // 执行
        $statement->execute();
        // 返回所有合法数据, 以Object对象为数据类型
        return $statement->fetchAll(PDO::FETCH_OBJ);
    }
    public function bindValues($statement, $bindings) {
        // $bindings = ['PENELOPE', 'GUINESS']
        // 依次循环每一个参数
        foreach ($bindings as $key => $value) {
            // $key = 0/1
            // $value = 'PENELOPE'/'GUINESS'
            $statement->bindValue(
                // 如果是字符串类型, 那就直接使用, 反之是数字, 将其+1
                // 这里是数值, 因此返回1/2
                is_string($key) ? $key : $key + 1,
                // 直接放入值
                // 'PENELOPE'/'GUINESS'
                $value,
                // 这里直白不多说
                // PDO::PARAM_STR/PDO::PARAM_STR
                is_int($value) || is_float($value) ? PDO::PARAM_INT : PDO::PARAM_STR
            );
        }
    }
    // 与read不同的地方在于, read返回数据, update返回boolean(true/false)
    public function update($sql, $bindings) {
        $statement = $this->connection->prepare($sql);
        $this->bindValues($statement, $bindings);
        return $statement->execute();
    }
    // 逻辑与update完全一致, 分开是因为方便日后维护制定
    public function delete($sql, $bindings) {
        $statement = $this->connection->prepare($sql);
        $this->bindValues($statement, $bindings);
        return $statement->execute();
    }
    // 返回最新的自增ID, 如果有, 否则返回null
    public function create($sql, $bindings) {
        $statement = $this->connection->prepare($sql);
        $this->bindValues($statement, $bindings);
        $statement->execute();
        return $this->lastInsertId();
    }
    public function lastInsertId() {
        $id = $this->connection->lastInsertId();
        return empty($id) ? null : $id;
    }

过于高级复杂的SQL语句可能无法封装, 因此准备了可直接用RAW query通信数据库的两个函数

    public function exec($sql) {
        return $this->connection->exec($sql);
    }
    public function query($sql) {
        $q = $this->connection->query($sql);
        return $q->fetchAll(PDO::FETCH_OBJ);
    }

将数据库事务相关的函数封装起来, 直白所以没有注释

    public function beginTransaction() {
        $this->connection->beginTransaction();
        return $this;
    }

    public function rollBack() {
        $this->connection->rollBack();
        return $this;
    }

    public function commit() {
        $this->connection->commit();
        return $this;
    }

    public function inTransaction() {
        return $this->connection->inTransaction();
    }

完整代码

我放在了Coding.net

本期疑问

因为PHP本身的特性, 默认情况下运行完所有代码类会自行析构,PDO自动断开联系, 所以我没有disconnect(),让PDO断开连接, 不知这样是不是一种 bad practice?

欢迎点赞或下方评论, 看了看感觉自己写的有些不利落, 请指出

上一期 如何写一个属于自己的数据库封装(1) - 基本思路
下一期 如何写一个属于自己的数据库封装(3) - 查询 - 入门篇

上一篇下一篇

猜你喜欢

热点阅读