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

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

作者: 梅先森森森森森森 | 来源:发表于2019-10-13 12:26 被阅读0次

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

    本期要点

    还是PDO 中文文档


    Connector.php

    • 负责与数据库通信,增删改读(CRUD)

    首先, 建一个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,
        ];
    }
    
    

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

    • buildConnectString - 生成DSN连接串
        protected function buildConnectString() {
            return "$this->driver:host=$this->host;dbname=".self::$db;
        }
    
    

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

    "mysql:host=localhost;dbname=sakila;"
    
    
    • connect - 连接数据库
        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());
            }
        }
    
    
    • setDatabase - 切换数据库
        public function setDatabase($db) {
            self::$db = $db;
            return $this;
        }
    
    
    • _construct - 生成实例后第一步要干嘛
        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)
    
    
    • read - 读取数据
      1. 将例子中的 sql 语句 (select * from actor where first_name = ? and last_name = ?) 代入 $sql
      2. 两个问号分别对应了两个需要筛选的条件, 将之带入自定义函数bindValues之中循环调用PDO的bindValue函数
      3. 最后执行并返回Object类型的数据
        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);
        }
    
    
    • bindValues - 将附带参数带入pdo实例

      1. 在read函数的第2步中将两个条件 ['PENELOPE', 'GUINESS'] 带入
      2. 循环调用PDO的bindValue函数

      从例子中可以看出, 我用在预处理的变量为?, 而不是变量 (:first_name), 这是因为PDO的局限性, bindValue函数并不支持首字符数字或符号, 如果使用变量绑定, 需要写一整段的判定来去除数字和符号, 因此效率至上, 选择?绑定

        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
                );
            }
        }
    
    
    • update - 改写数据
        // 与read不同的地方在于, read返回数据, update返回boolean(true/false)
        public function update($sql, $bindings) {
            $statement = $this->connection->prepare($sql);
            $this->bindValues($statement, $bindings);
            return $statement->execute();
        }
    
    
    • delete - 删除数据
        // 逻辑与update完全一致, 分开是因为方便日后维护制定
        public function delete($sql, $bindings) {
            $statement = $this->connection->prepare($sql);
            $this->bindValues($statement, $bindings);
            return $statement->execute();
        }
    
    
    • create - 插入数据
        // 返回最新的自增ID, 如果有, 否则返回null
        public function create($sql, $bindings) {
            $statement = $this->connection->prepare($sql);
            $this->bindValues($statement, $bindings);
            $statement->execute();
            return $this->lastInsertId();
        }
    
    
    • lastInsertId - 返回新增id, 如果有, 否则返回null

      仅仅封装了PDO自带的函数, 因此存在PDO自身的问题(或是说缺陷?)

      在处理业务逻辑的时候, 批量插入数据可能需要返回最后一条插入的数据, 实际上PDO自带的 lastInsertId 函数返回的是第一条数据的ID

      比方说一个已存在三条数据的表, ID => 1, 2, 3

      插入3条新数据, 自增ID => 4, 5, 6

      lastInsertId 函数 返回 4

        public function lastInsertId() {
            $id = $this->connection->lastInsertId();
            return empty($id) ? null : $id;
        }
    
    

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

    • exec - 适用于增删改(建议)
        public function exec($sql) {
            return $this->connection->exec($sql);
        }
    
    
    • query - 适用于读(建议)
        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) - 查询 - 入门篇

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/rivhmctx.html