web编程之路程序员代码改变世界

php&mysqli框架准备之数据库底层封装

2016-11-04  本文已影响506人  hopevow

当今流行的框架基本都是遵从MVC来进行构造的,前而介绍过了模板,也对路由的一些基础进行了详细的解析,VC都接触了,就差M层了,M即模型,其实就是数据库的相关操作,在php中就是对数据库的操作进行良好封装,便于操作。所以今天的内容是对mysqli类进行进一步封装扩展。


当今所有框架中都会有一个对curd进行封装的类,基本思想就是将操作简化,所以可以将所有的sql语句传给统一的函数处理,当然 ,我们先定义一个类,继承自mysqli,定义好初始化变量和连接数据库函数

class DBmysqli extends mysqli {
public $debug;
    public $sqlHistory;
    public $_stmt;
    public $querynum;

    private $_ini;
    private $dbcharset;

    function __construct($host, $username, $passwd, $dbname, $port = 3306, $dbcharset = 'utf-8') {
        parent::init();
        $this->dbcharset = $dbcharset;
        
        $this->_ini = [$host, $username, $passwd, $dbname, $port];
        $this->connect();
    }

    function connect($host = NULL, $user = NULL, $password = NULL, $database = NULL, $prot = NULL, $socket = NULL) {
        $this->real_connect($this->_ini[0], $this->_ini[1], $this->_ini[2], $this->_ini[3], $this->_ini[4]);

        if ($this->server_info > '4.1') {
            $serverset = $this->dbcharset ? 'character_set_connection=' . $this->dbcharset . ', character_set_results=' . $this->dbcharset . ', character_set_client=binary' : '';
            $serverset .= $this->server_info > '5.0.1' ? ((empty($serverset)?'':',') . 'sql_mode=\'\'') : '';
            $this->query("SET $serverset");
        }

        $this->_stmt = array();
        $this->mysqlName = "{$this->_ini[0]}:{$this->_ini[3]}";
        $this->debug = 1;
        $this->querynum = 0;
        $this->sqlHistory = [];
    }
}

接下来就是执行原生的mysql语句了。方法如下,我们设计它可以如下执行
$db = new DBmysqli('127.0.0.1', 'root', '', 'dbname');
$db->connect();
$sql = "select * from tablename";
$db->cmd($sql);

public function cmd($query, $arg = false, $effect = 0, $slient = 0) {

        if (isset($_GET['debug'])) {
            $t = get_microtime();
            $this->querynum++;
        }
        
        $q = $this->query($query);

        if (!$arg) {
            
            if (isset($_GET['debug'])) {
                $this->sqlHistory[$this->querynum] = array($query, round(get_microtime() - $t, 4));
            }

            return $q;
        }

        $stmt = $this->prepare($query);
        $array = [''];
        foreach($arg as $k => $var) {
            if (is_array($var)) {
                $array[0] .= $var[0];
                $array[] = &$arg[$k][1];
            } else {
                $array[0] .= is_numeric($var) ? 'i' : 's';
                $array[] = &$arg[$k];
            }
        }

        call_user_func_array(array($stmt, 'bind_param'), $array);
        if ($r = $stmt->execute()) {
            if (isset($_GET['debug'])) {
                $this->sqlHistory[$this->querynum] = array($query, round(get_microtime() - $t, 4), $this->mysqlName);
            }

            return $stmt->get_result();
        }
        return false;
    }

我们在cmd中进行了一定的操作,例如可以通过get传参来让其运行在两种不同的状态,调试状态会记录执行的mysql语句和执行时间。在这里面调用了mysqli::query来执行mysql语句,注意这里还有一条分支调用了DBmysqli::prepare方法,这个我们之后会说到。接下来我们进行CURD的逐步封装。

public function queryInsert($query, $arg = false, $return_id = 1, $slient = 0) {
        $stmt = $this->cmd($query, $arg);
        if ($stmt) {
            if ($return_id == 1) {
                return $this->insert_id;
            }
            return true;
        }

        if (!$slient && $this->errno) {
            throw new sqlExcption("<h3>" . $this->error . "(错误代码:{$this->errno}) </h3>\n\t sql:{$query}\n <hr/>", $this->errno);
        }
    }
public function queryUpdate($query, $arg = false, $effect = 1, $slient = 0) {
        if (defined('SQLBIN')) {
            $fp = fopen('./sql.bin', 'a+');
            fwrite($fp, $query. ";\n");
            fclose($fp);
        }

        $stmt = $this->cmd($query, $arg);

        if ($stmt) {
            if ($effect = 1) {
                return $this->affected_rows;
            }
            
            return true;
        }

        if (!$slient) {
            if ($this->errno) {
                throw new sqlException("<h3>" . $this->error . "(错误代码:{$this->errno})</h3>\n \t sql:{$query}\n <hr/>", $this->errno);
            }
        }

        return $stmt;
    }

public function queryResult($query, $arg = false, $slient = 0) {
        $stmt = $this->cmd($query, $arg);

        if ($stmt) {
            return $stmt;
        } elseif (!$slient or 1) {
            if ($this->errono) {
                throw new sqlException("<h3>" . $this->error . "(错误代码:{$this->errono})</h3>\n \t sql:{$query}\n<hr />", 4001);
            }
            return $stmt;
        }
    }

这样已经可以对数据库进行基本的操作了,包括增、删、改、查。然而 样操作还可以继续细化,在查找DBmysqli::queryResult的基础上还可以有多种操作。

public function fetchVar($sql, $arg = false) {
        $q = $this->queryResult($sql, $arg);
        return $q->fetch_row()[0];
    }
public function fetchOne($sql, $arg = false) {
        $q = $this->queryResult($sql, $arg);
        return $q->fetch_Object();
    }
public function fetchAll($sql, $arg = false, $returnobj = 1, $resulttype = MYSQLI_ASSOC) {
        $a = [];
    

        foreach($this->iter($sql, $arg, $returnobj, $resulttype) as $v) {
            $a[] = $v;
        }

        return $a;
    }

这里面的iter方法,利用了yield构造了一个迭代器,对于这个yield,有时间我会专门来介绍。现在只需要知道他iter方法返回的一个可以迭代的数据结构。

public function iter ($sql, $arg = false, $returnobj = 1, $resulttype = MYSQLI_ASSOC) {
        $q = $this->queryResult($sql, $arg);

        if ($returnobj) {
            while($res = $q->fetch_object()) {
                yield $res;
            }
        } else {
            while($res = $q->fetch_array($resulttype)) {
                yield $res;
            }
        }
    }

mysqli支持预处理操作,预处理语句用于执行多个相同的 SQL 语句,并且执行效率更高。

  1. 预处理:创建 SQL 语句模板并发送到数据库。预留的值使用参数 "?" 标记 。例如:INSERT INTO MyGuests (firstname, lastname, email) VALUES(?, ?, ?)
  2. 数据库解析,编译,对SQL语句模板执行查询优化,并存储结果不输出
  3. 执行:最后,将应用绑定的值传递给参数("?" 标记),数据库执行语句。应用可以多次执行语句,如果参数的值不一样。
原理图

相比于直接执行SQL语句,预处理语句有两个主要优点:
预处理语句大大减少了分析时间,只做了一次查询(虽然语句多次执行)
绑定参数减少了服务器带宽,你只需要发送查询的参数,而不是整个语句
预处理语句针对SQL注入是非常有用的,因为 参数值发送后使用不同的协议,保证了数据的合法性。

在DBmyqli::prepaer分支操作中封装了对这个的支持,这样再来看刚刚的DB::mysqli就很清楚了,让我们先来看看预处理的原生操作

$sql = "INSERT INTO USER (email, username, pwd) VALUES (?, ?, ?)"; 
$mysqli = new mysqli('127.0.0.1', 'root', '', 'modou');

$stmt = $mysqli->stmt_init();
$stmt->prepare($sql);
$stmt->bind_param('sss', $email, $username, $pwd);
$email = 'new email';
$username = 'new name';
$pwd = 'new pwd';
$stmt->execute();

所以可以有DBmysqli::prepare方法

public function prepare($query) {
        if (isset($this->_stmt[$query])) {
            return $this->_stmt[$query];
        }

        $this->_stmt[$query] = $this->stmt_init();
        $this->_stmt[$query]->prepare($query);
    }
}

到此,我们对mysqli的进一步封装完毕。

上一篇 下一篇

猜你喜欢

热点阅读