当今流行的框架基本都是遵从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 语句,并且执行效率更高。
- 预处理的工作原理
- 预处理:创建 SQL 语句模板并发送到数据库。预留的值使用参数 "?" 标记 。例如:INSERT INTO MyGuests (firstname, lastname, email) VALUES(?, ?, ?)
- 数据库解析,编译,对SQL语句模板执行查询优化,并存储结果不输出
- 执行:最后,将应用绑定的值传递给参数("?" 标记),数据库执行语句。应用可以多次执行语句,如果参数的值不一样。
相比于直接执行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的进一步封装完毕。