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

当今流行的框架基本都是遵从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的逐步封装。

  • insert
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);
        }
    }
  • update
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;
    }

  • select
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的进一步封装完毕。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容