你现在的位置:首页 > PHP网站建设知识库 > Discuz > 正文

discuz X网站开发之源码分析

discuz网站开发以前引用的是公用文件./include/common.inc.php,而在X系列中引用的是核心文件'./source/class/class_core.php',从中我们也看到了discuz与以前的不同之处。
//老样子,设置安全变量,防止外部引用
define('IN_DISCUZ', true);
//获取网站根目录绝对路径,以后会在多处使用DISCUZ_ROOT来包含文件
define('DISCUZ_ROOT', substr(dirname(__FILE__), 0, -12));
//调试模式
define('DISCUZ_CORE_DEBUG', false);
//扩展表,不太清楚干什么的
define('DISCUZ_TABLE_EXTENDABLE', false);
//PHP自带函数 使用 core::handleException 来执行异常处理可以是单个函数名为参数,也可以是类和函数组成的数组
set_exception_handler(array('core', 'handleException'));if(DISCUZ_CORE_DEBUG) {
//PHP自带函数 使用 core::h
 set_error_handler(array('core', 'handleError'));
      //设置致命错误处理函数,中断执行后我们可能看不到任何内容,设置这个函数后,可以在PHP停止执行时调用这个函数。
 register_shutdown_function(array('core', 'handleShutdown'));
}
//刚刚我们说过DISCUZ现在已经倾向于面向对象的编程方式了,面向对象中我们就会用到很多的类,若是我们要在一个PHP文件中调用很多类,就会引用很多的文件(就是写很多的include,或者require),在PHP5.0以后,就不需要这么做了,在php5中,试图使用尚未定义的类时会自动调用__autoload函数,所以我们可以通过编写__autoload函数来让php自动加载类,而不必写一个长长的包含文件列表。
而spl_autoload_register()函数会将Zend Engine中的__autoload函数取代为spl_autoload()或
spl_autoload_call()。因此先判断spl_autoload_register 函数是否存在,若不存在,再使用_autoload函数。(就是为了兼容所有的PHP环境,我就是喜欢DISCUZ这一点,把一切底层处理的好好的,没有后顾之忧)


if(function_exists('spl_autoload_register')) {spl_autoload_register(array('core', 'autoload'));} else {function __autoload($class) {return core::autoload($class);}}


C::creatapp();
这里的C是继承core类,在下边定义了C extended core,可以理解为C就是core


再看core::createapp()函数如下:
public static function creatapp() {
  if(!is_object(self::$_app)) {
   //discuz_application主要定义了mem session misic mobile setting等数组
   //在这里直接调用discuz_application类,会触发core::autoload('discuz_application')执行,因为spl_autoload_register()函数设置了
   self::$_app = discuz_application::instance();//由于执行了spl_autoload_register自动加载类
   //instance执行了$object = new self();这样就执行了__construct函数
  }
  return self::$_app;
 }


以下部分是DISCUZ 类文件自动包含代码的分析。


这里会执行discuz_application::instance,discuz_aplication这个类并没有包含到当前文件,所以会调用_autoload去加载,及core类中的autoload
public static function autoload($class) {
$class = strtolower($class);
if(strpos($class, '_') !== false) {
list($folder) = explode('_', $class);
$file = 'class/'.$folder.'/'.substr($class, strlen($folder) + 1);
} else {
$file = 'class/'.$class;
}


try {


self::import($file);
return true;


} catch (Exception $exc) {


$trace = $exc->getTrace();
foreach ($trace as $log) {
if(empty($log['class']) && $log['function'] == 'class_exists') {
return false;
}
}
discuz_error::exception_error($exc);
}
}


这里我们看到autoload函数会判断$class中是否有"_",然后根据具体情况获得路径,接着尝试import该文件,再看import
public static function import($name, $folder = '', $force = true) {
$key = $folder.$name;
if(!isset(self::$_imports[$key])) {
$path = DISCUZ_ROOT.'/source/'.$folder;
if(strpos($name, '/') !== false) {
$pre = basename(dirname($name));
$filename = dirname($name).'/'.$pre.'_'.basename($name).'.php';
} else {
$filename = $name.'.php';
}


if(is_file($path.'/'.$filename)) {
include $path.'/'.$filename;
self::$_imports[$key] = true;


return true;
} elseif(!$force) {
return false;
} else {
throw new Exception('Oops! System file lost: '.$filename);
}
}
return true;
}
此时传递给core::import()的参数为class/discuz/discuz_application.php,第2,3个参数为默认的
判断是否已经包含过了,若没有包含过,判断文件是否存在,进行包含,并设置$_imports[$key] = true; 及包含过。通过这样实现了文件的自动包含部分。


以下代码为source\class\discuz\discuz_application.php代码
我们回到self::$_app = discuz_application::instance(); 这里执行了source\class\discuz\discuz_application.php中的instance
static function &instance() {
static $object;
if(empty($object)) {
$object = new self();
}
return $object;
}
这里创建了新的对象,$object = new self();会调用类的构造函数
public function __construct() {
$this->_init_env();
$this->_init_config();
$this->_init_input();
$this->_init_output();
}


分别是 设置环境,设置配置,设置输入,设置输出
输入:这里为浏览器传入值,GET,POST,COOKIES
输出:及PHP返回给浏览器的内容,html,css,images等


$this->_init_env();//环境设置


private function _init_env() {
  
  error_reporting(E_ERROR);
  if(PHP_VERSION < '5.3.0') {
   set_magic_quotes_runtime(0);
  }
  define('MAGIC_QUOTES_GPC', function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc());
  define('ICONV_ENABLE', function_exists('iconv'));
  define('MB_ENABLE', function_exists('mb_convert_encoding'));
  define('EXT_OBGZIP', function_exists('ob_gzhandler'));
  define('TIMESTAMP', time()); //记录开始时间,没刷新页面都会执行
  $this->timezone_set(); //设置时区
  //包含核心函数库
  if(!defined('DISCUZ_CORE_FUNCTION') && !@include(DISCUZ_ROOT.'./source/function/function_core.php')) {
   exit('function_core.php is missing');
  }
  //内存设置为128M
  if(function_exists('ini_get')) {
   $memorylimit = @ini_get('memory_limit');
   if($memorylimit && return_bytes($memorylimit) < 33554432 && function_exists('ini_set')) {
    ini_set('memory_limit', '128m');
   }
  }
  define('IS_ROBOT', checkrobot());
  //$GLOBALS为全局变量数组,比如外部定义了$f00="123",在这里通过$GLOBALS['foo']='123';
  foreach ($GLOBALS as $key => $value) {
    if (!isset($this->superglobal[$key])) {
    $GLOBALS[$key] = null; unset($GLOBALS[$key]);
//不明白为何要清空$GLOBALS,似乎是用自己的一套变量清空系统变量
  }
  }
  
  global $_G; //所有内容记录的数组
  $_G = array(
   'uid' => 0,
   'username' => '',
   'adminid' => 0,
   'groupid' => 1,
   'sid' => '',
   'formhash' => '',
   'connectguest' => 0,
   'timestamp' => TIMESTAMP,
   'starttime' => microtime(true),
   'clientip' => $this->_get_client_ip(), //获取客户端IP地址
   'referer' => '',
   'charset' => '',
   'gzipcompress' => '',
   'authkey' => '',
   'timenow' => array(),
   'widthauto' => 0,
   'disabledwidthauto' => 0,
   'PHP_SELF' => '',
   'siteurl' => '',
   'siteroot' => '',
   'siteport' => '',
   'pluginrunlist' => !defined('PLUGINRUNLIST') ? array() : explode(',', PLUGINRUNLIST),
   'config' => array(),
   'setting' => array(),
   'member' => array(),
   'group' => array(),
   'cookie' => array(),
   'style' => array(),
   'cache' => array(),
   'session' => array(),
   'lang' => array(),
   'my_app' => array(),
   'my_userapp' => array(),
   'fid' => 0,
   'tid' => 0,
   'forum' => array(),
   'thread' => array(),
   'rssauth' => '',
   'home' => array(),
   'space' => array(),
   'block' => array(),
   'article' => array(),
   'action' => array(
    'action' => APPTYPEID,
    'fid' => 0,
    'tid' => 0,
   ),
   'mobile' => '',
   'notice_structure' => array(
    'mypost' => array('post','pcomment','activity','reward','goods','at'),
    'interactive' => array('poke','friend','wall','comment','click','sharenotice'),
    'system' => array('system','myapp','credit','group','verify','magic','task','show','group','pusearticle','mod_member','blog','article'),
    'manage' => array('mod_member','report','pmreport'),
    'app' => array(),
   ),
   'mobiletpl' => array('1' => 'mobile', '2' => 'touch', '3' => 'wml','yes' => 'mobile'),
  );
  //dhtmlspecialchars 在function_core.php里
  $_G['PHP_SELF'] = dhtmlspecialchars($this->_get_script_url());
  //在运行首个PHP文件里定义了,比如member.php里定义了member
  $_G['basescript'] = CURSCRIPT;
  $_G['basefilename'] = basename($_G['PHP_SELF']);
  $sitepath = substr($_G['PHP_SELF'], 0, strrpos($_G['PHP_SELF'], '/'));
  if(defined('IN_API')) {
   $sitepath = preg_replace("/\/api\/?.*?$/i", '', $sitepath);
  } elseif(defined('IN_ARCHIVER')) {
   $sitepath = preg_replace("/\/archiver/i", '', $sitepath);
  }
  
  $_G['isHTTPS'] = ($_SERVER['HTTPS'] && strtolower($_SERVER['HTTPS']) != 'off') ? true : false;
  $_G['siteurl'] = dhtmlspecialchars('http'.($_G['isHTTPS'] ? 's' : '').'://'.$_SERVER['HTTP_HOST'].$sitepath.'/');
  $url = parse_url($_G['siteurl']);
  $_G['siteroot'] = isset($url['path']) ? $url['path'] : '';
  $_G['siteport'] = empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' || $_SERVER['SERVER_PORT'] == '443' ? '' : ':'.$_SERVER['SERVER_PORT'];
  if(defined('SUB_DIR')) {
   $_G['siteurl'] = str_replace(SUB_DIR, '/', $_G['siteurl']);
   $_G['siteroot'] = str_replace(SUB_DIR, '/', $_G['siteroot']);
  }
  
  $this->var = & $_G;
 }
?>
设置php环境,设置discuz环境,以及数组$_G,和以前的discuz不同,X中discuz把所有要用的配置变量存到了数组中,以前都是直接使用变量
设置时区
public function timezone_set($timeoffset = 0) {
if(function_exists('date_default_timezone_set')) {
@date_default_timezone_set('Etc/GMT'.($timeoffset > 0 ? '-' : '+').(abs($timeoffset)));
}
}


初始化配置
private function _init_config() {

 


$_config = array();
@include DISCUZ_ROOT.'./config/config_global.php';
if(empty($_config)) {
//判断配置是否为空
if(!file_exists(DISCUZ_ROOT.'./data/install.lock')) {
//为空的话,检查是否已安装
header('location: install');
//安装锁(及install.lock文件),不存在的话,返回安装连接,并退出程序
exit;
} else {
system_error('config_notfound');
//已经安装了的话,提示错误
}
}

 


if(empty($_config['security']['authkey'])) {
$_config['security']['authkey'] = md5($_config['cookie']['cookiepre'].$_config['db'][1]['dbname']);
//设置加密的密码,为 cookie前缀与数据库名称 的md5值
}

 


if(empty($_config['debug']) || !file_exists(libfile('function/debug'))) {
//判断是否为调试模式
define('DISCUZ_DEBUG', false);
error_reporting(0);
//非调试模式,关闭错误报告
} elseif($_config['debug'] === 1 || $_config['debug'] === 2 || !empty($_REQUEST['debug']) && $_REQUEST['debug'] === $_config['debug']) {
define('DISCUZ_DEBUG', true);
//错误报告打开设置
error_reporting(E_ERROR);
//开启错误报告,等级为E_ERROR
if($_config['debug'] === 2) {
//错误报告为2
error_reporting(E_ALL);
//开启所有错误报告
}
} else {
define('DISCUZ_DEBUG', false);
error_reporting(0);
}
define('STATICURL', !empty($_config['output']['staticurl']) ? $_config['output']['staticurl'] : 'static/');
$this->var['staticurl'] = STATICURL;

 


$this->config = & $_config;
$this->var['config'] = & $_config;

 


if(substr($_config['cookie']['cookiepath'], 0, 1) != '/') {
$this->var['config']['cookie']['cookiepath'] = '/'.$this->var['config']['cookie']['cookiepath'];
}
$this->var['config']['cookie']['cookiepre'] = $this->var['config']['cookie']['cookiepre'].substr(md5($this->var['config']['cookie']['cookiepath'].'|'.$this->var['config']['cookie']['cookiedomain']), 0, 4).'_';

 

 

 


}
初始化输入:
private function _init_input() {
if (isset($_GET['GLOBALS']) ||isset($_POST['GLOBALS']) ||  isset($_COOKIE['GLOBALS']) || isset($_FILES['GLOBALS'])) {
system_error('request_tainting');
}


if(MAGIC_QUOTES_GPC) {
$_GET = dstripslashes($_GET);
$_POST = dstripslashes($_POST);
$_COOKIE = dstripslashes($_COOKIE);
}


$prelength = strlen($this->config['cookie']['cookiepre']);
foreach($_COOKIE as $key => $val) {
if(substr($key, 0, $prelength) == $this->config['cookie']['cookiepre']) {
$this->var['cookie'][substr($key, $prelength)] = $val;
}
}

 


if($_SERVER['REQUEST_METHOD'] == 'POST' && !empty($_POST)) {
$_GET = array_merge($_GET, $_POST);
}


if(isset($_GET['page'])) {

$_GET['page'] = rawurlencode($_GET['page']);
//对页数进行URL编码
}


if(!(!empty($_GET['handlekey']) && preg_match('/^\w+$/', $_GET['handlekey']))) {
unset($_GET['handlekey']);
}


if(!empty($this->var['config']['input']['compatible'])) {
foreach($_GET as $k => $v) {
$this->var['gp_'.$k] = daddslashes($v);
}
}


$this->var['mod'] = empty($_GET['mod']) ? '' : dhtmlspecialchars($_GET['mod']);
$this->var['inajax'] = empty($_GET['inajax']) ? 0 : (empty($this->var['config']['output']['ajaxvalidate']) ? 1 : ($_SERVER['REQUEST_METHOD'] == 'GET' && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' || $_SERVER['REQUEST_METHOD'] == 'POST' ? 1 : 0));
$this->var['page'] = empty($_GET['page']) ? 1 : max(1, intval($_GET['page']));
$this->var['sid'] = $this->var['cookie']['sid'] = isset($this->var['cookie']['sid']) ? dhtmlspecialchars($this->var['cookie']['sid']) : '';


if(empty($this->var['cookie']['saltkey'])) {
$this->var['cookie']['saltkey'] = random(8);
dsetcookie('saltkey', $this->var['cookie']['saltkey'], 86400 * 30, 1, 1);
}
$this->var['authkey'] = md5($this->var['config']['security']['authkey'].$this->var['cookie']['saltkey']);


}