入门客AI创业平台(我带你入门,你带我飞行)
博文笔记

(菜鸟篇) 浅谈DISCUZ X系列 数据库的操作

创建时间:2013-12-11 投稿人: 浏览次数:4422

还记的曾经写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方法。
















声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。
  • 上一篇:没有了
  • 下一篇:没有了
未上传头像