(菜鸟篇) 浅谈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 )去加载 \source\class\table目录下的table_common_domain.php文件。

来到了table_common_domain类,这个类继承自discuz_table  class table_common_domain extends discuz_table   我们再去打开 source\class\discuz\discuz_table.php,又发现了

discuz_table原来是从discuz_base类继承下来的,那么discuz_base类又写了什么呢?

打开source\class\discuz\discuz_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

source\class\discuz\discuz_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, "\n\r\\'\"\032") . '\'';

		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方法。
















  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值