(菜鸟篇) 浅谈DISCUZ X系列 数据库的操作
还记的曾经写DISCUZ插件时,凡是要用到数据库的时候,必然会写一大堆的数据库处理语句。只有一个数据库的操作类 dbstuff,我们只能用到原始的操作方法query,fetch_array等去检索数据库。写数据库语句是一件头疼的事情。
而在X系列中,DISCUZ是如何操作数据库的呢?
我们打开一个文件,这里我打开index.php,会发现类似这个样子的代码:
C::t("common_domain")->fetch_by_domain_domainroot($_ENV["prefixdomain"], $_ENV["domainroot"]);
这就是DISCUZ X系列的数据库操作了,完全不同于7.0以及以前的操作方式,那么它究竟是如何执行的呢?
按照从左到有的顺序,我们一步一步的拆分,首先是C,这个类在我之前的文章中提到过,他是core class的继承类,也可以认为就是core 类,我们看到他有一个静态的方法。
public static function t($name) { return self::_make_obj($name, "table", DISCUZ_TABLE_EXTENDABLE); }
在这个方法中,又调用了_make_obj方法,继续跟进:
protected static function _make_obj($name, $type, $extendable = false, $p = array()) { $pluginid = null; if($name[0] === "#") { list(, $pluginid, $name) = explode("#", $name); } $cname = $type."_".$name; if(!isset(self::$_tables[$cname])) { if(!class_exists($cname, false)) { self::import(($pluginid ? "plugin/".$pluginid : "class")."/".$type."/".$name); } if($extendable) { self::$_tables[$cname] = new discuz_container(); switch (count($p)) { case 0: self::$_tables[$cname]->obj = new $cname();break; case 1: self::$_tables[$cname]->obj = new $cname($p[1]);break; case 2: self::$_tables[$cname]->obj = new $cname($p[1], $p[2]);break; case 3: self::$_tables[$cname]->obj = new $cname($p[1], $p[2], $p[3]);break; case 4: self::$_tables[$cname]->obj = new $cname($p[1], $p[2], $p[3], $p[4]);break; case 5: self::$_tables[$cname]->obj = new $cname($p[1], $p[2], $p[3], $p[4], $p[5]);break; default: $ref = new ReflectionClass($cname);self::$_tables[$cname]->obj = $ref->newInstanceArgs($p);unset($ref);break; } } else { self::$_tables[$cname] = new $cname(); } } return self::$_tables[$cname]; }
这是一个protected静态方法,首先方法设置$pluginid为NULL,接着判断$name[0] === "#",(注意:===恒等计算符,和比较运算符号“==”的区别是 “==”不会检查条件式的表达式的类型,恒等计算符会同时检查表达式的值与类型。比如 ( 1==true ) 为true 但是 (1 === true) 就是false了,因为1是整形,true为bool型)
这里$name 是common_domain,那么$name[1]就是‘c’,得到cname,table_common_domain,判断是否存在self::$_table[table_common_domain](这里就是为了防止重复加载),第一次执行,可定不存在,执行if(!class_exists(table_common_domain,false)),判断class是否不存在,然后取反,得到false,继续执行。
到这里if($extendable) ,$extendable默认为false(这里的$extendable大概为扩展表的意思吧),因此执行self::$_tables[$cname] = new $cname(); 创建table_common_domain的类,程序会调用_autoload(不懂的autoload的同学,可以看看我的上一篇文章 (菜鸟篇)从Discuz X系列中学PHP core )去加载 sourceclass able目录下的table_common_domain.php文件。
来到了table_common_domain类,这个类继承自discuz_table class table_common_domain extends discuz_table 我们再去打开 sourceclassdiscuzdiscuz_table.php,又发现了
discuz_table原来是从discuz_base类继承下来的,那么discuz_base类又写了什么呢?
打开sourceclassdiscuzdiscuz_base.php
abstract class discuz_base { private $_e; private $_m; public function __construct() { } public function __set($name, $value) { $setter="set".$name; if(method_exists($this,$setter)) { return $this->$setter($value); } elseif($this->canGetProperty($name)) { throw new Exception("The property "".get_class($this)."->".$name."" is readonly"); } else { throw new Exception("The property "".get_class($this)."->".$name."" is not defined"); } } public function __get($name) { $getter="get".$name; if(method_exists($this,$getter)) { return $this->$getter(); } else { throw new Exception("The property "".get_class($this)."->".$name."" is not defined"); } } public function __call($name,$parameters) { throw new Exception("Class "".get_class($this)."" does not have a method named "".$name.""."); } public function canGetProperty($name) { return method_exists($this,"get".$name); } public function canSetProperty($name) { return method_exists($this,"set".$name); } public function __toString() { return get_class($this); } public function __invoke() { return get_class($this); } } ?>
discuz_base原来是一个抽象类,这个类也是discuz的基类,设置了魔术方法____construct、__set、__get、__call、__tostring、__invoke,以及判断是否存在get,set方法的函数。
__construct 构造方法,没有具体内容,悄悄飘过。
__set方法,这个方法,当我们调用类的setXXXXXX方法的时候会触发,同样__get方法也是当我们调用getXXXXXX方法的时候会触发,注意是在get,set之前触发。这里是判断调用的setXXXXXX方法,或者getXXXXXX方法是否存在。__call方法会在调用该类的方法前调用,同样也是判断方法是否存在,若不存在 抛出异常。在__set,__get中变脸$name就是set或者get之后跟的字符串,而__call中,直接就是调用的方法名称。__tostring,过去该类的字符串描述,学java的同学经常使用,不多解释。而当尝试以调用函数的方式调用一个对象时,__invoke方法会被自动调用。比如 $a = new mysql(); $a()这样的方式就会触发__invoke函数。
get_class会返回当前调用该方法的类名,是子类调用的话,返回的就是子类的名称。
再回到discuz_table类:
首先看它的属性:
public $data = array(); public $methods = array(); protected $_table; protected $_pk; protected $_pre_cache_key; protected $_cache_ttl; protected $_allowmem;
从上到下分别是数据,方法,表,键名,缓存,以及是否允许缓存系统。
再返回看table_common_domain类,这下终于回到了这个”孙子“类了,哎,看类就是头大,一会儿就绕晕了,说不定哪个变量就在父类或者子类中。
首先会调用table_common_domain类的构造函数
public function __construct() { $this->_table = "common_domain"; $this->_pk = ""; parent::__construct(); }
这里两个赋值,然后调用父类的构造函数:discuz_table的构造函数
public function __construct($para = array()) { if(!empty($para)) { $this->_table = $para["table"]; $this->_pk = $para["pk"]; } if(isset($this->_pre_cache_key) && (($ttl = getglobal("setting/memory/".$this->_table)) !== null || ($ttl = $this->_cache_ttl) !== null) && memory("check")) { $this->_cache_ttl = $ttl; $this->_allowmem = true; } $this->_init_extend(); parent::__construct(); }
上边是基本的赋值,我们暂时不考虑,主要就是缓存极致了,主要看最后两句,$this->_init_extend(); 与 parent::__construct();,前者初始化了扩展信息,后者调用父类构造函数,在当前文件中_init_extend方法为空,但是不要以为就什么都不做了,因为在其子类中init_extend可能会被覆盖,所以有可能会调用到子类的这个方法。这里我们在写扩展表的时候 ,就可以把相关的初始化操作写到这个方法里边。
构造函数完毕,回到最上边的语句,该执行红色字体部分了:
C::t("common_domain")->fetch_by_domain_domainroot($_ENV["prefixdomain"], $_ENV["domainroot"]);
下边这个方法:
public function fetch_by_domain_domainroot($domain, $droot) { return DB::fetch_first("SELECT * FROM %t WHERE domain=%s AND domainroot=%s", array($this->_table, $domain, $droot)); }
我们又看到了一个新的类DB,其实也不算新类,这个类在core类中提到过,class_core.php文件中:class DB extends discuz_database {}
可以看到这个类是discuz_database类的一个子类,继续去看discuz_database
sourceclassdiscuzdiscuz_database.php
class discuz_database { public static $db; public static $driver; public static function init($driver, $config) { self::$driver = $driver; self::$db = new $driver; self::$db->set_config($config); self::$db->connect(); } public static function object() { return self::$db; } public static function table($table) { return self::$db->table_name($table); } public static function delete($table, $condition, $limit = 0, $unbuffered = true) { if (empty($condition)) { return false; } elseif (is_array($condition)) { if (count($condition) == 2 && isset($condition["where"]) && isset($condition["arg"])) { $where = self::format($condition["where"], $condition["arg"]); } else { $where = self::implode_field_value($condition, " AND "); } } else { $where = $condition; } $limit = dintval($limit); $sql = "DELETE FROM " . self::table($table) . " WHERE $where " . ($limit > 0 ? "LIMIT $limit" : ""); return self::query($sql, ($unbuffered ? "UNBUFFERED" : "")); } public static function insert($table, $data, $return_insert_id = false, $replace = false, $silent = false) { $sql = self::implode($data); $cmd = $replace ? "REPLACE INTO" : "INSERT INTO"; $table = self::table($table); $silent = $silent ? "SILENT" : ""; return self::query("$cmd $table SET $sql", null, $silent, !$return_insert_id); } public static function update($table, $data, $condition, $unbuffered = false, $low_priority = false) { $sql = self::implode($data); if(empty($sql)) { return false; } $cmd = "UPDATE " . ($low_priority ? "LOW_PRIORITY" : ""); $table = self::table($table); $where = ""; if (empty($condition)) { $where = "1"; } elseif (is_array($condition)) { $where = self::implode($condition, " AND "); } else { $where = $condition; } $res = self::query("$cmd $table SET $sql WHERE $where", $unbuffered ? "UNBUFFERED" : ""); return $res; } public static function insert_id() { return self::$db->insert_id(); } public static function fetch($resourceid, $type = "MYSQL_ASSOC") { return self::$db->fetch_array($resourceid, $type); } public static function fetch_first($sql, $arg = array(), $silent = false) { $res = self::query($sql, $arg, $silent, false); $ret = self::$db->fetch_array($res); self::$db->free_result($res); return $ret ? $ret : array(); } public static function fetch_all($sql, $arg = array(), $keyfield = "", $silent=false) { $data = array(); $query = self::query($sql, $arg, $silent, false); while ($row = self::$db->fetch_array($query)) { if ($keyfield && isset($row[$keyfield])) { $data[$row[$keyfield]] = $row; } else { $data[] = $row; } } self::$db->free_result($query); return $data; } public static function result($resourceid, $row = 0) { return self::$db->result($resourceid, $row); } public static function result_first($sql, $arg = array(), $silent = false) { $res = self::query($sql, $arg, $silent, false); $ret = self::$db->result($res, 0); self::$db->free_result($res); return $ret; } public static function query($sql, $arg = array(), $silent = false, $unbuffered = false) { if (!empty($arg)) { if (is_array($arg)) { $sql = self::format($sql, $arg); } elseif ($arg === "SILENT") { $silent = true; } elseif ($arg === "UNBUFFERED") { $unbuffered = true; } } self::checkquery($sql); $ret = self::$db->query($sql, $silent, $unbuffered); if (!$unbuffered && $ret) { $cmd = trim(strtoupper(substr($sql, 0, strpos($sql, " ")))); if ($cmd === "SELECT") { } elseif ($cmd === "UPDATE" || $cmd === "DELETE") { $ret = self::$db->affected_rows(); } elseif ($cmd === "INSERT") { $ret = self::$db->insert_id(); } } return $ret; } public static function num_rows($resourceid) { return self::$db->num_rows($resourceid); } public static function affected_rows() { return self::$db->affected_rows(); } public static function free_result($query) { return self::$db->free_result($query); } public static function error() { return self::$db->error(); } public static function errno() { return self::$db->errno(); } public static function checkquery($sql) { return discuz_database_safecheck::checkquery($sql); } public static function quote($str, $noarray = false) { if (is_string($str)) return """ . addcslashes($str, " \"" 32") . """; if (is_int($str) or is_float($str)) return """ . $str . """; if (is_array($str)) { if($noarray === false) { foreach ($str as &$v) { $v = self::quote($v, true); } return $str; } else { return """"; } } if (is_bool($str)) return $str ? "1" : "0"; return """"; } public static function quote_field($field) { if (is_array($field)) { foreach ($field as $k => $v) { $field[$k] = self::quote_field($v); } } else { if (strpos($field, "`") !== false) $field = str_replace("`", "", $field); $field = "`" . $field . "`"; } return $field; } public static function limit($start, $limit = 0) { $limit = intval($limit > 0 ? $limit : 0); $start = intval($start > 0 ? $start : 0); if ($start > 0 && $limit > 0) { return " LIMIT $start, $limit"; } elseif ($limit) { return " LIMIT $limit"; } elseif ($start) { return " LIMIT $start"; } else { return ""; } } public static function order($field, $order = "ASC") { if(empty($field)) { return ""; } $order = strtoupper($order) == "ASC" || empty($order) ? "ASC" : "DESC"; return self::quote_field($field) . " " . $order; } public static function field($field, $val, $glue = "=") { $field = self::quote_field($field); if (is_array($val)) { $glue = $glue == "notin" ? "notin" : "in"; } elseif ($glue == "in") { $glue = "="; } switch ($glue) { case "=": return $field . $glue . self::quote($val); break; case "-": case "+": return $field . "=" . $field . $glue . self::quote((string) $val); break; case "|": case "&": case "^": return $field . "=" . $field . $glue . self::quote($val); break; case ">": case "<": case "<>": case "<=": case ">=": return $field . $glue . self::quote($val); break; case "like": return $field . " LIKE(" . self::quote($val) . ")"; break; case "in": case "notin": $val = $val ? implode(",", self::quote($val)) : """"; return $field . ($glue == "notin" ? " NOT" : "") . " IN(" . $val . ")"; break; default: throw new DbException("Not allow this glue between field and value: "" . $glue . """); } } public static function implode($array, $glue = ",") { $sql = $comma = ""; $glue = " " . trim($glue) . " "; foreach ($array as $k => $v) { $sql .= $comma . self::quote_field($k) . "=" . self::quote($v); $comma = $glue; } return $sql; } public static function implode_field_value($array, $glue = ",") { return self::implode($array, $glue); } public static function format($sql, $arg) { $count = substr_count($sql, "%"); if (!$count) { return $sql; } elseif ($count > count($arg)) { throw new DbException("SQL string format error! This SQL need "" . $count . "" vars to replace into.", 0, $sql); } $len = strlen($sql); $i = $find = 0; $ret = ""; while ($i <= $len && $find < $count) { if ($sql{$i} == "%") { $next = $sql{$i + 1}; if ($next == "t") { $ret .= self::table($arg[$find]); } elseif ($next == "s") { $ret .= self::quote(is_array($arg[$find]) ? serialize($arg[$find]) : (string) $arg[$find]); } elseif ($next == "f") { $ret .= sprintf("%F", $arg[$find]); } elseif ($next == "d") { $ret .= dintval($arg[$find]); } elseif ($next == "i") { $ret .= $arg[$find]; } elseif ($next == "n") { if (!empty($arg[$find])) { $ret .= is_array($arg[$find]) ? implode(",", self::quote($arg[$find])) : self::quote($arg[$find]); } else { $ret .= "0"; } } else { $ret .= self::quote($arg[$find]); } $i++; $find++; } else { $ret .= $sql{$i}; } $i++; } if ($i < $len) { $ret .= substr($sql, $i); } return $ret; } }
在这个类中,我们终于看到了熟悉的变量 $db,对了,这个变量就是我们在discuz 7.X时代经常使用的dbstuff类。
同时找到DB::fetch_first 方法,
public static function fetch_first($sql, $arg = array(), $silent = false) { $res = self::query($sql, $arg, $silent, false); $ret = self::$db->fetch_array($res); self::$db->free_result($res); return $ret ? $ret : array(); }
看到这里我们也大概能整理出一点思路:
Discuz X中 使用discuz_database类将以前的dbstuff类进行了新的封装,使其变成已经完全由静态方法操作数据库的类,并且添加了一些新的功能,比如SQL安全检查。该类执行基础的SQL语句,
然后再discuz_table类中有进行了第二次包装,使得discuz_table具有了一些简单的操作,比如updata,delete,比以前操作更简单,不用写语句,直接传入数组,以数组键名作为数据库键名,在函数内部重组SQL语句进行操作,大大简化了程序员二次开发的工作量,并且更加安全。
若是基本的操作不够用,那么discuz_table类进行继承,得到一个新类,在其中扩展该类,使其拥有自定义的更加复杂的操作,比如fetch_by_domain_domainroot方法。
- 上一篇:没有了
- 下一篇:没有了