可能有理解不透徹的地方,歡迎回帖拍磚,會多加改進php
一、加載class_core.php可查看全局數據初始化的另一個筆記css
二、功能模塊中哦跟你的mod對應了source/forum中指定的文件。
緩存模塊根據當前所處的功能模塊,加載必需的緩存內容,默認的緩存內容通常會在操做完指定模塊以後存放在用二進制的形式序列化後存放在數據庫表中
//BBS相關的功能模塊 html
- $modarray = array('ajax','announcement','attachment','forumdisplay',
- 'group','image','index','medal','misc','modcp','notice','post','redirect',
- 'relatekw','relatethread','rss','topicadmin','trade','viewthread','tag','collection','guide'
- );
//緩存相關的數據模塊
- $modcachelist = array(
- 'index' => array('announcements', 'onlinelist', 'forumlinks',
- 'heats', 'historyposts', 'onlinerecord', 'userstats', 'diytemplatenameforum'),
- 'forumdisplay' => array('smilies', 'announcements_forum', 'globalstick', 'forums',
- 'onlinelist', 'forumstick', 'threadtable_info', 'threadtableids', 'stamps', 'diytemplatenameforum'),
- 'viewthread' => array('smilies', 'smileytypes', 'forums', 'usergroups',
- 'stamps', 'bbcodes', 'smilies', 'custominfo', 'groupicon', 'stamps',
- 'threadtableids', 'threadtable_info', 'posttable_info', 'diytemplatenameforum'),
- 'redirect' => array('threadtableids', 'threadtable_info', 'posttable_info'),
- 'post' => array('bbcodes_display', 'bbcodes', 'smileycodes', 'smilies', 'smileytypes',
- 'domainwhitelist', 'albumcategory'),
- 'space' => array('fields_required', 'fields_optional', 'custominfo'),
- 'group' => array('grouptype', 'diytemplatenamegroup'),
- );
//對於不在modarray中的值,視爲非法內容,直接替換成index。
//C::app()->var['mod']的值在加載class_core.php時初始化函數_init_input()中已經賦值,起始就是URL中的一個參數值 前端
- $mod = !in_array(C::app()->var['mod'], $modarray) ? 'index' : C::app()->var['mod'];
其中C是core的一個子類java
- C::app()->cachelist = $cachelist;
- C::app()->init();
若是說class_core.php是執行初始化的工做,或者說聲明必要的內容,那麼這裏的C::app()->init()就是把基本上須要的內容都獲取到,例如數據庫鏈接,後臺設置的內容,用戶信息,session信息等等。具體往下看。mysql
- public function init() {
- if(!$this->initated) {
- $this->_init_db();
- $this->_init_setting();
- $this->_init_user();
- $this->_init_session();
- $this->_init_mobile();
- $this->_init_cron();
- $this->_init_misc();
- }
- $this->initated = true;
- }
同樣,從各個函數中的名稱能夠稍微理解每一個都會進行些什麼相關的內容,這一點在加載class_core.php時就遇到了,core類的構造函數的操做方式就跟這個非常相似。
三、該函數的主要任務就是鏈接數據庫,重點所鏈接的時候涉及到不一樣數據庫版本對字符集的設置問題android
- private function _init_db() {
- if($this->init_db) {
- $driver = 'db_driver_mysql';
- if(count(getglobal('config/db/slave'))) {
- $driver = 'db_driver_mysql_slave';
- }
- DB::init($driver, $this->config['db']);
- }
- }
DB類在加載class_core.php的最後繼承了class DB extends discuz_database {}類discuz_database,稍微查看下該文件就能夠看出,對於數據庫相關的操做如獲取數據庫配置信息,鏈接數據庫,查詢、刪除、更新數據庫表等都從新封裝過。 init_db變量在聲明該類的時候就初始化默認爲true,而db_driver_mysql中基本上封裝了須要用到的數據庫操做,而discuz_database就能夠看成是一個代理,而正真完成操做的則爲在更爲底層的db_driver_mysql. 至於db_driver_mysql_slave暫時尚未怎麼留意到,後續補充ajax
- public static function init($driver, $config) {
- self::$driver = $driver;
- self::$db = new $driver;
- self::$db->set_config($config);
- self::$db->connect();
- }
如上所說,這裏的$driver=db_driver_mysql,其中set_config 就是爲了獲得數據的配置信息,HOST、名、用戶、密碼等包括數據庫前綴四、redis
- private function _init_setting() {
- if($this->init_setting) {
- if(empty($this->var['setting'])) {
- $this->cachelist[] = 'setting';
- }
-
- if(empty($this->var['style'])) {
- $this->cachelist[] = 'style_default';
- }
-
- if(!isset($this->var['cache']['cronnextrun'])) {
- $this->cachelist[] = 'cronnextrun';
- }
- }
-
- !empty($this->cachelist) && loadcache($this->cachelist);
-
- if(!is_array($this->var['setting'])) {
- $this->var['setting'] = array();
- }
-
- }
默認狀況下,$this->cachelist[]數組中會加入setting,style_default,cronnextrun三個元素,重點在loadcache()函數
對於loadcache()函數,其中重點則在於
- $cachedata = C::t('common_syscache')->fetch_all($caches);
文件table_common_syscache.php中的fetch_all函數。在看fetch_all()函數以前,留意下類table_common_syscache的構造函數
- public function __construct() {
-
- $this->_table = 'common_syscache';
- $this->_pk = 'cname';
- $this->_pre_cache_key = '';
- $this->_isfilecache = getglobal('config/cache/type') == 'file';
- $this->_allowmem = memory('check');
-
- parent::__construct();
- }
這裏除了獲取些基本信息以外,還會檢查系統如今默認使用的緩存方式(在配置文件config_global.php中能夠設置,默認使用了SQL的數據庫存放方式,另外還有一種文件緩存方式,下面根據默認的說下數據庫緩存。)
留心發現還有一句
- $this->_allowmem = memory('check');
這個就是PHP中用到的內存緩存的檢查工做了,這種類型的緩存方式有redis,memcache,apc,xcache,eaccelerator,wincache,要使用哪種方式,能夠了解下PHP的內存緩存機制,不一樣的緩存方式對於不一樣的站點有不同的效果,跟服務器、訪問量都有些關係。
內存緩存是要服務器支持的狀況下,在經過配置文件進行配置纔可使用。不一樣於下面將會繼續說道的discuz中用到的文件緩存和數據庫緩存,雖然同爲緩存,可是使用的方式徹底不同。
接下來看fetch_all函數的內容
- $cachenames = is_array($cachenames) ? $cachenames : array($cachenames);
discuz中不少地方均可以發現相似的寫法,目的就是爲了更直接的處理相似功能的操做,例如dhtmlspecialchars()函數,其實就是封裝了PHP中默認的htmlspecialchars()函數,除了根據PHP版本作不一樣的編碼工做,對有多是數組形式的字符串數組作編碼,這樣也就爲何上面這句比較常見的緣由了。
- if($this->_allowmem) {
- $data = memory('get', $cachenames);
- $newarray = $data !== false ? array_diff($cachenames, array_keys($data)) : $cachenames;
- if(empty($newarray)) {
- return $data;
- } else {
- $cachenames = $newarray;
- }
- }
在構造函數中檢測的結果就在這裏用上了,當發現系統支持內存緩存的時候,就根據當前使用的內存緩存方式去系統內存中獲取緩存數據。拋開封裝相關的類函數不說,直接看source/class/memory/memory_driver_memcache.php當前目錄的這些腳本,就是根據不一樣的內存緩存方式執行不一樣的操做,基本就涵蓋了設置set,get,delete等操做。
- if($this->_isfilecache) {
- $lostcaches = array();
- foreach($cachenames as $cachename) {
- if(!@include_once(DISCUZ_ROOT.'./data/cache/cache_'.$cachename.'.php')) {
- $lostcaches[] = $cachename;
- } elseif($this->_allowmem) {
- memory('set', $cachename, $data[$cachename]);
- }
- }
- if(!$lostcaches) {
- return $data;
- }
- $cachenames = $lostcaches;
- unset($lostcaches);
- }
若使用的是文件緩存方式,拿理所固然的所去緩存文件中獲取數據了。緩存的數據根據緩存名稱放在了data/cache目錄下。留心發現還能找到在支持內存緩存的時候,代碼會有個斷定並加入緩存的操做。
- memory('set', $cachename, $data[$cachename]);
- $query = DB::query('SELECT * FROM '.DB::table($this->_table).' WHERE '.DB::field('cname', $cachenames));
- while($syscache = DB::fetch($query)) {
- $data[$syscache['cname']] = $syscache['ctype'] ? unserialize($syscache['data']) : $syscache['data'];
- $this->_allowmem && (memory('set', $syscache['cname'], $data[$syscache['cname']]));
- if($this->_isfilecache) {
- $cachedata = '$data[\''.$syscache['cname'].'\'] = '.var_export($data[$syscache['cname']], true).";\n\n";
- if(($fp = @fopen(DISCUZ_ROOT.'./data/cache/cache_'.$syscache['cname'].'.php', 'wb'))) {
- fwrite($fp, "<?php\n//Discuz! cache file, DO NOT modify me!\n//Identify: ".md5($syscache['cname'].$cachedata.getglobal('config/security/authkey'))."\n\n$cachedata?>");
- fclose($fp);
- }
- }
- }
這段就是默認的是使用了數據庫緩存所獲得的緩存數據了,緩存數據被存放在表pre_common_syscache表中。通常狀況下都是將經常使用的、不變的數據都會放在裏面,例如後臺的設置信息基本上都會有,存放的方式是有對應關係的數組以二進制的形式存放。
經過以上內存緩存、文件緩存、和數據庫緩存,這塊的緩存工做就基本上結束了,固然這裏指的是讀取緩存的工做,至於存儲緩存的工做請看以下function_core.php文件中的savecache()函數:
- function savecache($cachename, $data) {
- C::t('common_syscache')->insert($cachename, $data);
- }
使用到了同個類table_common_syscache中的insert函數
- public function insert($cachename, $data) {
-
- parent::insert(array(
- 'cname' => $cachename,
- 'ctype' => is_array($data) ? 1 : 0,
- 'dateline' => TIMESTAMP,
- 'data' => is_array($data) ? serialize($data) : $data,
- ), false, true);
-
- if($this->_allowmem && memory('get', $cachename) !== false) {
- memory('set', $cachename, $data);
- }
- $this->_isfilecache && @unlink(DISCUZ_ROOT.'./data/cache/cache_'.$cachename.'.php');
- }
函數理所固然的包括了數據庫存儲,內存緩存設置,將舊的緩存文件刪除。
- $cachedata = C::t('common_syscache')->fetch_all($caches);
- foreach($cachedata as $cname => $data) {
- if($cname == 'setting') {
- $_G['setting'] = $data;
- } elseif($cname == 'usergroup_'.$_G['groupid']) {
- $_G['cache'][$cname] = $_G['group'] = $data;
- } elseif($cname == 'style_default') {
- $_G['cache'][$cname] = $_G['style'] = $data;
- } elseif($cname == 'grouplevels') {
- $_G['grouplevels'] = $data;
- } else {
- $_G['cache'][$cname] = $data;
- }
- }
緩存獲取的操做作完以後,DISCUZ系統習慣性的將這些數據存放進了$_G這個全局變量中了。
五、函數_init_user(),從名字能夠大概知道其主要執行的是用戶相關的操做。
- if($auth = getglobal('auth', 'cookie')) {
- $auth = daddslashes(explode("\t", authcode($auth, 'DECODE')));
- }
- list($discuz_pw, $discuz_uid) = empty($auth) || count($auth) < 2 ? array('', '') : $auth;
-
- if($discuz_uid) {
- $user = getuserbyuid($discuz_uid, 1);
- }
這一步是爲了識別、並經過函數getuserbyuid()獲取用戶信息的,從名爲auth的cookie中獲取內容,同時經過具有加密解密功能的authcode()函數,對cookie的字符串auth進行解密獲得用戶的uid和密碼。仔細觀察客戶端中的cookie值,auth是包含前綴名的,沒錯,往前面看的話別忘了class_core類中的init_input函數,以前講過,對於discuz的cookie都會默認加上前綴,在獲取到客戶端數據的時候,就已經在init_input函數中去掉了前綴,這樣獲得的名稱就更有識別性。
-
- if(!empty($user) && $user['password'] == $discuz_pw) {
- if(isset($user['_inarchive'])) {
- C::t('common_member_archive')->move_to_master($discuz_uid);
- }
- $this->var['member'] = $user;
- } else {
- $user = array();
- $this->_init_guest();
- }
-
- if($user && $user['groupexpiry'] > 0 && $user['groupexpiry'] < TIMESTAMP && (getgpc('mod') != 'spacecp' || CURSCRIPT != 'home')) {
- dheader('location: home.php?mod=spacecp&ac=usergroup&do=expiry');
- }
-
- $this->cachelist[] = 'usergroup_'.$this->var['member']['groupid'];
- if($user && $user['adminid'] > 0 && $user['groupid'] != $user['adminid']) {
- $this->cachelist[] = 'admingroup_'.$this->var['member']['adminid'];
- }
- if(empty($this->var['cookie']['lastvisit'])) {
- $this->var['member']['lastvisit'] = TIMESTAMP - 3600;
- dsetcookie('lastvisit', TIMESTAMP - 3600, 86400 * 30);
- } else {
- $this->var['member']['lastvisit'] = $this->var['cookie']['lastvisit'];
- }
-
- setglobal('uid', getglobal('uid', 'member'));
- setglobal('username', getglobal('username', 'member'));
- setglobal('adminid', getglobal('adminid', 'member'));
- setglobal('groupid', getglobal('groupid', 'member'));
-
- !empty($this->cachelist) && loadcache($this->cachelist);
剩下的這些基本上都是爲了從新設置用戶信息相關的,而這些信息主要爲了設置幾個在全局變量$_G中的一級元素uid,username,adminid,groupid,和member下的二級元素的值。
同時根據當前用戶是否爲遊客則執行遊客信息的初始化工做_init_guest(),其實這個過程也就是將幾個默認的數據放入$_G['member']中;
若是是已登陸用戶,首先判斷用戶所在用戶組groupexpiry是否已通過期,是的話則跳轉到我的設置的用戶組頁面;
獲得用戶所在用戶組關係後,在以usergroup爲前綴的緩存數據中獲取當前用戶組的緩存信息,獲取工做就交給接下來會執行的loadcache函數了。
六、函數init_session(),操做些session相關的東西,仔細研究DISCUZ的SESSION機制,會發現其跟PHP自帶的SESSION機制所不同的,這裏所謂的SESSION基本上是DISCUZ本身從新定製的機制,至於爲何要獨立使用本身的sessin機制而拋棄PHP自帶的,具體緣由就不是很清楚,可是查看DISCUZ安裝後獲得的數據庫表結構,能夠發現pre_common_session使用的數據庫引擎方式竟然是有別於其餘myisam的memory,也就是所謂的內存表,該類型的表數據是放在內存中的,默認使用了hash索引,訪問起來會很是快,這或許就是使用本身的一套機制的緣由吧。
- private function _init_session() {
-
- $sessionclose = !empty($this->var['setting']['sessionclose']);
- $this->session = $sessionclose ? new discuz_session_close() : new discuz_session();
-
- if($this->init_session) {
- $this->session->init($this->var['cookie']['sid'], $this->var['clientip'], $this->var['uid']);
- $this->var['sid'] = $this->session->sid;
- $this->var['session'] = $this->session->var;
-
- if(!empty($this->var['sid']) && $this->var['sid'] != $this->var['cookie']['sid']) {
- dsetcookie('sid', $this->var['sid'], 86400);
- }
-
- if($this->session->isnew) {
- if(ipbanned($this->var['clientip'])) {
- $this->session->set('groupid', 6);
- }
- }
-
- if($this->session->get('groupid') == 6) {
- $this->var['member']['groupid'] = 6;
- sysmessage('user_banned');
- }
-
- if($this->var['uid'] && !$sessionclose && ($this->session->isnew || ($this->session->get('lastactivity') + 600) < TIMESTAMP)) {
- $this->session->set('lastactivity', TIMESTAMP);
- if($this->session->isnew) {
- C::t('common_member_status')->update($this->var['uid'], array('lastip' => $this->var['clientip'], 'lastvisit' => TIMESTAMP));
- }
- }
-
- }
- }
接下來講的是當$sessionclose==false的狀況下的內容。關鍵操做也在類discuz_session中,如上代碼中第六行。
- public function init($sid, $ip, $uid) {
- $this->old = array('sid' => $sid, 'ip' => $ip, 'uid' => $uid);
- $session = array();
- if($sid) {
- $session = $this->table->fetch($sid, $ip, $uid);
- }
-
- if(empty($session) || $session['uid'] != $uid) {
- $session = $this->create($ip, $uid);
- }
-
- $this->var = $session;
- $this->sid = $session['sid'];
- }
會發現cookie在discuz中發揮的重要做用了吧,sid就是保存在客戶端中的該用戶當前瀏覽器對應的服務器中的session的ID,除了session的id,這裏所須要的參數還包括了用戶的UID,客戶端IP,大部分的重要信息都須要通過cookie的合做纔可以順利的進行,這裏就出現疑問,在客戶端禁止cookie的狀況下discuz的運行又會是怎樣的?後續須要瞭解。
下面的是table_common_session.php中的fetch函數
- public function fetch($sid, $ip = false, $uid = false) {
- if(empty($sid)) {
- return array();
- }
- $this->checkpk();
- $session = parent::fetch($sid);
- if($session && $ip !== false && $ip != "{$session['ip1']}.{$session['ip2']}.{$session['ip3']}.{$session['ip4']}") {
- $session = array();
- }
- if($session && $uid !== false && $uid != $session['uid']) {
- $session = array();
- }
- return $session;
- }
會調用其父類discuz_table中的fetch()函數
- public function fetch($id, $force_from_db = false){
- $data = array();
- if(!empty($id)) {
- if($force_from_db || ($data = $this->fetch_cache($id)) === false) {
- $data = DB::fetch_first('SELECT * FROM '.DB::table($this->_table).' WHERE '.DB::field($this->_pk, $id));
- if(!empty($data)) $this->store_cache($id, $data);
- }
- }
- return $data;
- }
數據庫表類的父類discuz_table的兩個基本函數fetch_cache()和store_cache()在這裏的做用就是,在獲取session的過程當中,若是在內存緩存中可以獲得session的內容,就會跳過在數據庫表格common_session中查找內容,若是沒有的話就進行查詢,這裏調試的結果是在沒有支持內存緩存的環境下,會到數據庫表格中進行查找並獲取數據,獲取到的數據會嘗試將其保存在內存緩存中,也就是store_cache()的工做了。
執行完畢回到剛纔類table_common_session中:
其一會在取得session內容的狀況下,判斷客戶端IP跟獲取到的session中的ip是否相同,不一樣的話就將session置空,由於當前用戶所使用的客戶端IP已經修改。
其二也會取得session內容的狀況下,判斷客戶端相同ip,可是session中的uid已經不一樣,也就是客戶端瀏覽器使用了另一個用戶登陸,此時對應的session也須要重置。
到這裏session的獲取工做就基本上完成,而這裏的獲取session也就是獲取了用戶相關的客戶端信息,以下面調試的數據:
- array(14) (
- [sid] => (string) cnksCz
- [ip1] => (string) 127
- [ip2] => (string) 0
- [ip3] => (string) 0
- [ip4] => (string) 1
- [uid] => (string) 1
- [username] => (string) admin
- [groupid] => (string) 1
- [invisible] => (string) 0
- [action] => (string) 2
- [lastactivity] => (string) 1378193039
- [lastolupdate] => (string) 1378193039
- [fid] => (string) 0
- [tid] => (string) 0
- )
上面說到了可能獲取的session爲空的狀況,往回看在init()函數中會有個create()函數,其實就是從新生成一個新的session數組,其中sid的值是經過random(6)隨機生成的,其餘都是根據當前狀況賦予內容。再將相關值賦值到discuz_session對象的變量中,供init_session()函數完成接下來的工做。
剩下來的會判斷當前的sid跟客戶端的sid是否相同,否的話就從新設置dsetcookie()客戶端的sid的cookie值;對於從新create出來的session,會判斷用戶訪問的ip是否在被禁止訪問的ip名單中,若是是的話會將用戶置爲groupid爲6即「用戶IP被禁止」的系統用戶組中,並拋出提醒;而沒有被禁止的用戶能夠繼續執行後面的內容,後面的也就剩下噹噹前用戶所在登陸狀態下,且超過了600秒的有效期以後,同時也就是如今也處於活動狀態,就從新更新用戶在session的最後活動時間以及用戶狀態表common_member_status中的狀態信息。
七、函數_init_mobile()
判斷在後臺是否容許使用手機頁面的訪問,是的話就初始化各類跟手機頁面訪問所須要的數據,不然就return false,沒有什麼特別的操做,其中有個頗有用的函數,在其餘項目中能夠考慮使用,就是檢測當前客戶端使用的是否爲手機客戶端。
- function checkmobile() {
- global $_G;
- $mobile = array();
- static $mobilebrowser_list =array('iphone', 'android', 'phone', 'mobile', 'wap', 'netfront', 'java', 'opera mobi', 'opera mini',
- 'ucweb', 'windows ce', 'symbian', 'series', 'webos', 'sony', 'blackberry', 'dopod', 'nokia', 'samsung',
- 'palmsource', 'xda', 'pieplus', 'meizu', 'midp', 'cldc', 'motorola', 'foma', 'docomo', 'up.browser',
- 'up.link', 'blazer', 'helio', 'hosin', 'huawei', 'novarra', 'coolpad', 'webos', 'techfaith', 'palmsource',
- 'alcatel', 'amoi', 'ktouch', 'nexian', 'ericsson', 'philips', 'sagem', 'wellcom', 'bunjalloo', 'maui', 'smartphone',
- 'iemobile', 'spice', 'bird', 'zte-', 'longcos', 'pantech', 'gionee', 'portalmmm', 'jig browser', 'hiptop',
- 'benq', 'haier', '^lct', '320x320', '240x320', '176x220');
- $pad_list = array('pad', 'gt-p1000');
-
- $useragent = strtolower($_SERVER['HTTP_USER_AGENT']);
-
- if(dstrpos($useragent, $pad_list)) {
- return false;
- }
- if(($v = dstrpos($useragent, $mobilebrowser_list, true))) {
- $_G['mobile'] = $v;
- return true;
- }
- $brower = array('mozilla', 'chrome', 'safari', 'opera', 'm3gate', 'winwap', 'openwave', 'myop');
- if(dstrpos($useragent, $brower)) return false;
-
- $_G['mobile'] = 'unknown';
- if($_GET['mobile'] === 'yes') {
- return true;
- } else {
- return false;
- }
- }
函數_init_cron()執行的是discuz系統中的計劃任務; 函數_init_misc()執行的是discuz系統除了上述以外的其餘雜七雜八的初始化工做,無特殊之處;而前面的計劃任務或許能夠深究。
到這裏論壇的初始化工做就基本上完成了,至於每一個地方有什麼用,都會很詳盡地將須要的數據提供出來,從這個過程也能夠看出DISCUZ的人在這麼多年的積累仍是作到很是之不錯的,其中也有不少值得咱們去學習的。
例如在使用某個變量的時候,能夠發現它都會在該類的最前端初始化該變量;在服務端不須要使用到的數據,DISCUZ會直接過濾掉而不容許出現任何可能形成不安全的影響;其整個discuz的框架也分的很清楚,雖然這裏沒有說到,多瞭解下能夠看到整個系統的執行過程按部就班、各個功能塊都分得很清楚,這裏說到的數據初始化、緩存的定製、插件的自定義工做以及模板上所帶有的MVC結構等。