該類文件在:thinkphp/library/think/think.class.php
?????? 該類可以說(shuō)是ThinkPHP框架最為核心的類庫(kù),負(fù)責(zé)諸多配置加載,注冊(cè)核心系統(tǒng)擴(kuò)展(自動(dòng)加載類庫(kù)、異常處理、錯(cuò)誤處理等),管理和維護(hù)類實(shí)例、別名映射,可以一說(shuō)是一個(gè)框架的工廠(該類有些許面向?qū)ο蟊锥?,比如:違背了面向?qū)ο髥我宦氊?zé),其負(fù)責(zé)功能復(fù)雜,關(guān)聯(lián)類庫(kù)和文件較多,有動(dòng)一牽百的憂慮)。類中遇到的函數(shù)會(huì)在該類分析之后徹底分析,所涉及的其它類庫(kù)會(huì)專門講解。
一、類結(jié)構(gòu)?
namespace Think;//定義命名空間 class Think { private static $_map = array();//類庫(kù)別名映射 private static $_instance = array();//保存類實(shí)例(這么說(shuō)也不合理,等會(huì)分析該功能時(shí)具體說(shuō)明) static public function start() {}//應(yīng)用程序初始化 static public function addMap($class, $map=''){}// 注冊(cè)classmap static public function getMap($class=''){}// 獲取classmap public static function autoload($class) {}//類庫(kù)自動(dòng)加載 static public function instance($class,$method='') {}//取得對(duì)象實(shí)例 支持調(diào)用類的靜態(tài)方法 static public function appException($e) {}//自定義異常處理 static public function appError($errno, $errstr, $errfile, $errline) {}//自定義錯(cuò)誤處理 static public function fatalError() {} // 致命錯(cuò)誤捕獲 static public function halt($error) {}//錯(cuò)誤輸出 static public function trace($value='[think]',$label='',$level='DEBUG',$record=false) {}//添加和獲取頁(yè)面Trace記錄 }
二、應(yīng)用程序初始化start()方法分析,該方法包含一套錯(cuò)誤和異常處理機(jī)制,非常受用。該方法作為ThinkPHP框架的引導(dǎo)接口,實(shí)現(xiàn)錯(cuò)誤、異常處理,配置加載,別名映射,行為注冊(cè),包含運(yùn)行緩存的生成,網(wǎng)站應(yīng)用目錄檢測(cè),自動(dòng)類庫(kù)加載行為注冊(cè)。
/** * 應(yīng)用程序初始化 * @access public * @return void */ static public function start() { //使用spl標(biāo)準(zhǔn)庫(kù)中提供__autoload()函數(shù)的默認(rèn)實(shí)現(xiàn),比__autoload()效率更高,更加靈活 //一下可以使用spl_autoload_register(array('ThinkThink','autoload')); //建議使用spl_autoload_register(__NAMESPACE__.'Think::autoload');實(shí)現(xiàn) //一下所有注冊(cè)方式均可以使用上面3中形式傳遞參數(shù) spl_autoload_register('ThinkThink::autoload'); //注冊(cè)全局腳本"析構(gòu)函數(shù)",使用該方式注冊(cè)的函數(shù),會(huì)在腳本結(jié)束前調(diào)用,大多數(shù)情況用來(lái)處理致命錯(cuò)誤 register_shutdown_function('ThinkThink::fatalError'); //設(shè)置自定義錯(cuò)誤處理函數(shù),用于處理錯(cuò)誤信息 set_error_handler('ThinkThink::appError'); //設(shè)置未異常處理函數(shù) set_exception_handler('ThinkThink::appException'); //可以把register_shutdown_function(),set_error_handler(),set_error_handler()3個(gè)函數(shù)組合完成自定義、多元化的錯(cuò)誤處理模塊 //根據(jù)STORAGE_TYPE的值設(shè)置分布式文件存儲(chǔ)方案,Storage是一個(gè)工廠類,用于管理和維護(hù)分布式文件存儲(chǔ)組件 //后面會(huì)詳細(xì)講解Storage類,并指出設(shè)計(jì)缺陷 Storage::connect(STORAGE_TYPE); //根據(jù)運(yùn)行模式在運(yùn)行緩存目錄下生成編譯緩存文件APP_MODE.'~runtime.php',從而減少IO開(kāi)銷 //下面會(huì)詳細(xì)介紹生成緩存文件的方式 $runtimefile = RUNTIME_PATH.APP_MODE.'~runtime.php'; //如果不是在調(diào)試模式,并且編譯緩存文件存在,直接加載編譯緩存 if(!APP_DEBUG && Storage::has($runtimefile)){ Storage::load($runtimefile); }else{ //判斷編譯緩存文件是否存在,存在就刪除 if(Storage::has($runtimefile)) Storage::unlink($runtimefile); //預(yù)編譯內(nèi)容變量 $content = ''; //判斷是否存在運(yùn)行模式配置文件,如果不存在就加載MODE_PATH.APP_MODE.'.php',運(yùn)行模式配置文件會(huì)影響下列加載不同的類庫(kù)和配置 //運(yùn)行配置文件后期會(huì)詳細(xì)講解 $mode = include is_file(CONF_PATH.'core.php')?CONF_PATH.'core.php':MODE_PATH.APP_MODE.'.php'; //以下所有配置項(xiàng)加載都會(huì)根據(jù)加載的先后順序覆蓋之前的配置項(xiàng),一般都是先加載ThinkPHP默認(rèn)配置,再加載應(yīng)用配置 //core下標(biāo)決定要加載的核心類和函數(shù)文件 foreach ($mode['core'] as $file){ if(is_file($file)) { include $file; //如果不是調(diào)試模式,則編譯該文件內(nèi)容并儲(chǔ)存到預(yù)編譯內(nèi)容變量中 if(!APP_DEBUG) $content .= compile($file); } } //config下標(biāo)決定要加載的核心配置文件 foreach ($mode['config'] as $key=>$file){ //判斷下標(biāo)是否為數(shù)字,如果不是就會(huì)把該配置文件中的配置項(xiàng)加載到對(duì)應(yīng)的鍵下面,相當(dāng)于給配置項(xiàng)增加一個(gè)緯度 is_numeric($key)?C(include $file):C($key,include $file); } //如果不是普通運(yùn)行模式,則判斷是否存在運(yùn)行模式應(yīng)用配置文件 if('common' != APP_MODE && is_file(CONF_PATH.'config_'.APP_MODE.'.php')) C(include CONF_PATH.'config_'.APP_MODE.'.php'); //alias下標(biāo)記錄類庫(kù)別名映射規(guī)則,ThinkPHP獨(dú)創(chuàng)別名機(jī)制,用于提升自動(dòng)加載的效率 if(isset($mode['alias'])){ //由這句代碼可以看出alias規(guī)則可以是一個(gè)數(shù)組,或者將規(guī)則數(shù)組單獨(dú)作為一個(gè)文件 self::addMap(is_array($mode['alias'])?$mode['alias']:include $mode['alias']); } //加載應(yīng)用中定義的別名配置 if(is_file(CONF_PATH.'alias.php')) self::addMap(include CONF_PATH.'alias.php'); //tags下標(biāo)用于標(biāo)識(shí)系統(tǒng)行為,行為擴(kuò)展具體由Hook鉤子類實(shí)現(xiàn) if(isset($mode['tags'])) { //由這句代碼可以看出tags規(guī)則可以是一個(gè)數(shù)組,或者將規(guī)則數(shù)組單獨(dú)作為一個(gè)文件 Hook::import(is_array($mode['tags'])?$mode['tags']:include $mode['tags']); } //加載應(yīng)用中的行為擴(kuò)展配置 if(is_file(CONF_PATH.'tags.php')) // 允許應(yīng)用增加開(kāi)發(fā)模式配置定義 Hook::import(include CONF_PATH.'tags.php'); //加載框架底層語(yǔ)言包,有核心配置文件中的DEFAULT_LANG配置項(xiàng)決定 L(include THINK_PATH.'Lang/'.strtolower(C('DEFAULT_LANG')).'.php'); //如果不是調(diào)試模式則生成編譯緩存文件 if(!APP_DEBUG){ //namespace {}這種方式用于聲明代碼塊中的命名空間屬于全局命名空間 //這句代碼用于生成加載別名映射的php代碼 $content .= " namespace { ThinkThink::addMap(".var_export(self::$_map,true).");"; //L(".var_export(L(),true).");生成語(yǔ)言加載代碼 //C(".var_export(C(),true).');生成配置項(xiàng)加載代碼 //ThinkHook::import('.var_export(Hook::get(),true).');生成鉤子加載代碼 $content .= " L(".var_export(L(),true)."); C(".var_export(C(),true).');ThinkHook::import('.var_export(Hook::get(),true).');}'; //將$content變量?jī)?nèi)容去除注釋和換行、空隔之后寫入到運(yùn)行時(shí)編譯緩存文件 Storage::put($runtimefile,strip_whitespace('<?php '.$content)); }else{ // 調(diào)試模式加載系統(tǒng)默認(rèn)的配置文件 C(include THINK_PATH.'Conf/debug.php'); // 讀取應(yīng)用調(diào)試配置文件 if(is_file(CONF_PATH.'debug.php')) C(include CONF_PATH.'debug.php'); } } //根據(jù)APP_STATUS讀取當(dāng)前部署環(huán)境配置文件,常用在上線前數(shù)據(jù)庫(kù)連接配置等,用于覆蓋默認(rèn)配置行為 if(APP_STATUS && is_file(CONF_PATH.APP_STATUS.'.php')) C(include CONF_PATH.APP_STATUS.'.php'); // 設(shè)置系統(tǒng)時(shí)區(qū) //建議可以寫成date_default_timezone_set(C('DEFAULT_TIMEZONE',null,'PRC'));防止配置項(xiàng)讀取問(wèn)題導(dǎo)致的時(shí)區(qū)設(shè)置錯(cuò)誤 date_default_timezone_set(C('DEFAULT_TIMEZONE')); // 檢查應(yīng)用目錄結(jié)構(gòu) 如果不存在則自動(dòng)創(chuàng)建 if(C('CHECK_APP_DIR') && !is_dir(LOG_PATH)) { //build.php負(fù)責(zé)創(chuàng)建應(yīng)用目錄結(jié)構(gòu) require THINK_PATH.'Common/build.php'; } // 記錄加載文件時(shí)間 G('loadTime'); // 運(yùn)行應(yīng)用 App::run(); }
三、類庫(kù)別名映射機(jī)制實(shí)現(xiàn)addMap()和getMap()方法分析;該機(jī)制使用Think::_map變量存儲(chǔ)別名映射記錄,通過(guò)Think::addMap()添加或修改別名映射記錄,使用Think:getMap()獲取別名映射記錄。
立即學(xué)習(xí)“PHP免費(fèi)學(xué)習(xí)筆記(深入)”;
/** * 注冊(cè)或修改類庫(kù)別名映射記錄 * @access public * @param class String|Array 如果為數(shù)組鍵為類庫(kù)別名,鍵值為類庫(kù)實(shí)際位置;如果字符串代表類庫(kù)別名 * @param map String 如果class為字符串,該參數(shù)代表類庫(kù)實(shí)際位置,否則沒(méi)有意義 * @return void */ static public function addMap($class, $map=''){ //判斷class是否為數(shù)組,如果是就合并當(dāng)前類庫(kù)別名記錄,如果有相同記錄就會(huì)覆蓋 if(is_array($class)){ self::$_map = array_merge(self::$_map, $class); }else{ //如果class不是數(shù)組,就作為別名儲(chǔ)存在類庫(kù)別名記錄中,并把map作為實(shí)際類庫(kù)位置 self::$_map[$class] = $map; } } /** * 根據(jù)別名獲取類庫(kù)實(shí)際地址 * @param class String 類庫(kù)別名 * @return Array|String|NULL 如果class為空則獲取所有類庫(kù)別名記錄,否者返回別名對(duì)應(yīng)的類庫(kù)位置 */ static public function getMap($class=''){ //如果class為空,直接返回所有類庫(kù)別名記錄 if(''===$class){ return self::$_map; //判斷對(duì)應(yīng)別名是否在別名映射記錄中,如果存在返回類庫(kù)實(shí)際地址,否者返回null }elseif(isset(self::$_map[$class])){ return self::$_map[$class]; }else{ return null; } }
四、ThinkPHP類庫(kù)自動(dòng)加載機(jī)制autoload()方法分析,該方法是由Think::start()方法中的第一句代碼注冊(cè)實(shí)現(xiàn)spl_autoload_register('ThinkThink::autoload');
/** * 類庫(kù)自動(dòng)加載 * @param string $class 對(duì)象類名 * @return void */ public static function autoload($class) { //判斷是否存在別名映射 //標(biāo)記一處bug,如果使用Think::addMap('ThinkTest');注冊(cè)別名就完了,程序邏輯不嚴(yán)謹(jǐn),不會(huì)有大的安全問(wèn)題,可以無(wú)視 //具體可以看Think::addMap()方法的實(shí)現(xiàn) if(isset(self::$_map[$class])) { include self::$_map[$class]; //建議在這里renturn; //判斷是否存在符號(hào),存在則使用命名空間加載機(jī)制 //此處與配置說(shuō)明不符'APP_AUTOLOAD_PATH' => '', // 自動(dòng)加載的路徑 關(guān)閉APP_USE_NAMESPACE后有效 //現(xiàn)在判斷的是否在類名中使用命名規(guī)則而不是使用APP_USE_NAMESPACE配置,當(dāng)然該項(xiàng)配置主要作用在路由模塊,后續(xù)會(huì)講解 }elseif(strpos($class,'\')){ //獲取命名空間的第一個(gè)命名范圍 $name = strstr($class, '\', true); //判斷該命名空間的第一個(gè)命名范圍是否在Think約定范圍類,并在Library目錄下存在該目錄 if(in_array($name,array('Think','Org','Behavior','Com','Vendor')) || is_dir(LIB_PATH.$name)){ // Library目錄下面的命名空間自動(dòng)定位 $path = LIB_PATH; }else{ //檢測(cè)自定義命名空間 否則就以模塊為命名空間 $namespace = C('AUTOLOAD_NAMESPACE'); $path = isset($namespace[$name])? dirname($namespace[$name]).'/' : APP_PATH; } //這里可以看出ThinkPHP命名空間的命名規(guī)則是以LIB_PATH和APP_PATH作為根目錄的目錄原則,也可以為AUTOLOAD_NAMESPACE配置項(xiàng)來(lái)自定義命名規(guī)則根目錄 $filename = $path . str_replace('\', '/', $class) . EXT; //判斷文件是否存在,為啥在注冊(cè)別名的時(shí)候不去判斷文件是否存在呢?那樣是否可以無(wú)視一些有問(wèn)題的別名映射,而使用正確的加載規(guī)則呢? if(is_file($filename)) { //如果在windows環(huán)境下運(yùn)行,使用strpos來(lái)檢測(cè)是否大小寫一致問(wèn)題,如果不一致直接返回 if (IS_WIN && false === strpos(str_replace('/', '\', realpath($filename)), $class . EXT)){ return ; } //導(dǎo)入類文件 include $filename; //這里建議return; } }else{ //不用按照配置文件中的APP_USE_NAMESPACE的值來(lái)決定是否APP_AUTOLOAD_LAYER配置該項(xiàng) //只要在類庫(kù)加載中沒(méi)有使用命名空間就會(huì)調(diào)用以下規(guī)則來(lái)查找類庫(kù)(要實(shí)例化的類不能聲明命名規(guī)則) foreach(explode(',',C('APP_AUTOLOAD_LAYER')) as $layer){ //判斷類名最后幾位是否符合APP_AUTOLOAD_LAYER配置項(xiàng) if(substr($class,-strlen($layer))==$layer){ //加載當(dāng)前模塊下對(duì)應(yīng)的類文件,這個(gè)其實(shí)可以直接判斷文件是否存在,并用include加載即可,沒(méi)有必要調(diào)用require_cache函數(shù) //以上是個(gè)人見(jiàn)解,緣由是因?yàn)樯厦娴募虞d機(jī)制都沒(méi)有使用,我覺(jué)得是否應(yīng)該統(tǒng)一,而且自動(dòng)加載類文件,不用限制加載一次,如果已經(jīng)加載了,也不會(huì)調(diào)用這個(gè)方法了。 if(require_cache(MODULE_PATH.$layer.'/'.$class.EXT)) { //加載文件成功直接返回 return ; } } } //根據(jù)APP_AUTOLOAD_PATH配置設(shè)置的路徑規(guī)則自動(dòng)搜索并加載文件 foreach (explode(',',C('APP_AUTOLOAD_PATH')) as $path){ //這里同上,是否也可以不用調(diào)用import()方法加載,或者統(tǒng)一了呢? if(import($path.'.'.$class)) // 如果加載類成功則返回 return ; } } }
五、管理類實(shí)例或者'緩存'類靜態(tài)方法調(diào)用結(jié)果instance()方法分析,之前在類結(jié)構(gòu)分析中說(shuō)$_instance變量保存類實(shí)例并不合理,因?yàn)樵擃愡€可以調(diào)用類的靜態(tài)方法,并'緩存'結(jié)果。我在該方法注釋中提出一些個(gè)人見(jiàn)解,并不是說(shuō)該方法設(shè)計(jì)的不合理,這個(gè)是畢竟是ThinkPHP專有方法,任何一個(gè)項(xiàng)目的設(shè)計(jì)都不會(huì)像我那般去考慮一個(gè)方法的諸多問(wèn)題,首要問(wèn)題是解決是否符合項(xiàng)目應(yīng)用足矣。同樣看到這篇文章的朋友可以思考,在項(xiàng)目是否有很多類并不需要多個(gè)實(shí)例(沒(méi)有強(qiáng)制約束的情況下),如果有那可以設(shè)計(jì)一個(gè)適合自己項(xiàng)目的偽單例工廠來(lái)管理這些類的實(shí)例。
/** * 取得對(duì)象實(shí)例 支持調(diào)用類的靜態(tài)方法 * 解析:我把該類看成一個(gè)偽單例工廠,不是嚴(yán)格要求類不許是單例,統(tǒng)一使用該方法獲取類對(duì)象,可以實(shí)現(xiàn)單例模式(極其適合php這種較為靈活的語(yǔ)言) * 說(shuō)這是一個(gè)單例模式工廠不合格,因?yàn)闆](méi)有嚴(yán)格要求所管理類必須符合單例模式約束。 * 問(wèn)題:該方法說(shuō)此類可以調(diào)用類的靜態(tài)方法,并沒(méi)有約定靜態(tài)方法必須返回類的實(shí)例(self),可以返回任意結(jié)果,這個(gè)讓我很詫異 * 如果是想要緩存類方法調(diào)用結(jié)果,是否應(yīng)該提供給方法傳遞參數(shù)選項(xiàng)呢? * 如果僅僅為了管控類的實(shí)例(比如嚴(yán)格按照單例模式設(shè)計(jì)的類,如果要管理,必須使用靜態(tài)方法),是否應(yīng)該說(shuō)明或者在方法中檢測(cè)返回值呢? * 解惑:這個(gè)畢竟不是提供給應(yīng)用的方法(當(dāng)然可以使用,設(shè)計(jì)初衷肯定不是,這算是ThinkPHP開(kāi)發(fā)團(tuán)隊(duì)約定俗成的規(guī)定(口頭約束使用方法)吧?) * 這至少給我們一個(gè)啟示,可以這么管理偽單例模式(口頭約束的方式,當(dāng)然比之更可靠),之前我在一個(gè)項(xiàng)目中設(shè)計(jì)過(guò)這樣一個(gè)工廠類(那時(shí)候還沒(méi)有分析過(guò)任何產(chǎn)品的源碼) * @param string $class 對(duì)象類名 * @param string $method 類的靜態(tài)方法名 * @return object */ static public function instance($class,$method='') { //生成實(shí)例管理標(biāo)識(shí) $identify = $class.$method; //判斷是否已經(jīng)存在改類實(shí)例標(biāo)識(shí) if(!isset(self::$_instance[$identify])) { //判斷類是否存在,這里反應(yīng)不能使用自動(dòng)加載機(jī)制,必須在調(diào)用該方法前加載類文件 if(class_exists($class)){ //實(shí)例化類,這里可以看出并不能管理嚴(yán)格意義上的單例類 $o = new $class(); //判斷是否要調(diào)用靜態(tài)方法,并確定該類是否存在該方法 if(!empty($method) && method_exists($o,$method)) //返回調(diào)用結(jié)果(不明確是什么) self::$_instance[$identify] = call_user_func(array(&$o, $method)); else //存儲(chǔ)類實(shí)例對(duì)象 self::$_instance[$identify] = $o; } else //輸出錯(cuò)誤信息 self::halt(L('_CLASS_NOT_EXIST_').':'.$class); } //返回實(shí)例對(duì)象 return self::$_instance[$identify]; }
六、ThinkPHP內(nèi)置錯(cuò)誤處理和異常處理實(shí)現(xiàn)分析。appException()方法由Think::start()中set_exception_handler('ThinkThink::appException');語(yǔ)句實(shí)現(xiàn)。appError()方法由Think::start()中set_error_handler('ThinkThink::appError');語(yǔ)句實(shí)現(xiàn),fatalError()方法由Think::start()中register_shutdown_function('ThinkThink::fatalError');語(yǔ)句實(shí)現(xiàn)。halt()方法用來(lái)輸出重要錯(cuò)誤信息和異常,并終止程序執(zhí)行。trace()方法用來(lái)記錄并管理Trace調(diào)試工具中的錯(cuò)誤信息。
/** * 自定義異常處理 * @access public * @param mixed $e 異常對(duì)象 * @param void */ static public function appException($e) { $error = array(); //獲取異常錯(cuò)誤信息 $error['message'] = $e->getMessage(); //獲取backtrace()回溯信息 $trace = $e->getTrace(); //判斷是否由異常處理方法拋出,ThinkPHP自定義拋出異常處理函數(shù) if('E'==$trace[0]['function']) { $error['file'] = $trace[0]['file'];//獲取錯(cuò)誤文件 $error['line'] = $trace[0]['line'];//獲取錯(cuò)誤行號(hào) }else{ $error['file'] = $e->getFile();//獲取錯(cuò)誤文件 $error['line'] = $e->getLine();//獲取錯(cuò)誤行號(hào) } //已格式錯(cuò)誤回溯信息 $error['trace'] = $e->getTraceAsString(); //寫入到錯(cuò)誤日志 Log::record($error['message'],Log::ERR); // 發(fā)送404信息 header('HTTP/1.1 404 Not Found'); header('Status:404 Not Found'); //顯示錯(cuò)誤信息 self::halt($error); } /** * 自定義錯(cuò)誤處理 * @access public * @param int $errno 錯(cuò)誤類型 * @param string $errstr 錯(cuò)誤信息 * @param string $errfile 錯(cuò)誤文件 * @param int $errline 錯(cuò)誤行數(shù) * @return void */ static public function appError($errno, $errstr, $errfile, $errline) { switch ($errno) { //一些重要的錯(cuò)誤信息,會(huì)影響之后的程序執(zhí)行 case E_ERROR: case E_PARSE: case E_CORE_ERROR: case E_COMPILE_ERROR: case E_USER_ERROR: //清空輸出緩沖(不知道在哪里開(kāi)啟了,后面分析會(huì)遇到) ob_end_clean();//其實(shí)就是把php默認(rèn)輸出的錯(cuò)誤信息清除掉 //錯(cuò)誤信息 $errorStr = "$errstr ".$errfile." 第 $errline 行."; //根據(jù)LOG_RECORD是否記錄錯(cuò)誤信息,決定是否寫入錯(cuò)誤日志 if(C('LOG_RECORD')) Log::write("[$errno] ".$errorStr,Log::ERR); //輸出錯(cuò)誤信息 self::halt($errorStr); break; //可以忽略的錯(cuò)誤信息,不會(huì)輸出,會(huì)記錄到trace當(dāng)中,使用SHOW_PAGE_TRACE配置可以查看的錯(cuò)誤信息 default: //這里程序還要繼續(xù)執(zhí)行,不能清空輸出緩沖,如果不希望顯示這類錯(cuò)誤信息,應(yīng)當(dāng)在php.ini中調(diào)節(jié),或者使用ini_set()的函數(shù)改變 //錯(cuò)誤信息 $errorStr = "[$errno] $errstr ".$errfile." 第 $errline 行."; //記錄到trace當(dāng)中 self::trace($errorStr,'','NOTIC'); break; } } // 致命錯(cuò)誤捕獲 static public function fatalError() { //致命錯(cuò)誤必須保存到日志中 Log::save(); //獲取上一個(gè)錯(cuò)誤信息,沒(méi)有錯(cuò)誤就跳過(guò)了(⊙0⊙) if ($e = error_get_last()) { //處理致命錯(cuò)誤信息 switch($e['type']){ case E_ERROR: case E_PARSE: case E_CORE_ERROR: case E_COMPILE_ERROR: case E_USER_ERROR: //清空輸出緩存,都導(dǎo)致程序停止了,還顯示什么呀 ob_end_clean(); //輸出錯(cuò)誤信息 self::halt($e); break; } } } /** * 錯(cuò)誤輸出 * @param mixed $error 錯(cuò)誤 * @return void */ static public function halt($error) { $e = array(); //判斷是否是調(diào)試模式,或者命令行模式 if (APP_DEBUG || IS_CLI) { //調(diào)試模式下輸出錯(cuò)誤信息 //如果錯(cuò)誤信息不是一個(gè)數(shù)組,就回溯最后一次執(zhí)行方法的信息 if (!is_array($error)) { $trace = debug_backtrace(); $e['message'] = $error; $e['file'] = $trace[0]['file']; $e['line'] = $trace[0]['line']; ob_start();//開(kāi)始輸出緩存 debug_print_backtrace();//輸出一條回溯信息 $e['trace'] = ob_get_clean();//獲取輸出緩沖信息,并清空 } else { $e = $error; } if(IS_CLI){ //命令行模式,轉(zhuǎn)換為gbk編碼,終止程序執(zhí)行并輸出錯(cuò)誤信息 exit(iconv('UTF-8','gbk',$e['message']).PHP_EOL.'FILE: '.$e['file'].'('.$e['line'].')'.PHP_EOL.$e['trace']); } } else { //不是調(diào)試模式,重定向到錯(cuò)誤頁(yè)面 $error_page = C('ERROR_PAGE');//獲取設(shè)置的錯(cuò)誤頁(yè)面 if (!empty($error_page)) { //重定向到錯(cuò)誤頁(yè)面 redirect($error_page); } else { //根據(jù)SHOW_ERROR_MSG配置決定是否顯示詳細(xì)的錯(cuò)誤信息,還是采用ERROR_MESSAGE設(shè)定的錯(cuò)誤信息 $message = is_array($error) ? $error['message'] : $error; $e['message'] = C('SHOW_ERROR_MSG')? $message : C('ERROR_MESSAGE'); } } //根據(jù)TMPL_EXCEPTION_FILE配置決定調(diào)用錯(cuò)誤信息顯示模版,否者采用ThinkPHP默認(rèn)模版 $exceptionFile = C('TMPL_EXCEPTION_FILE',null,THINK_PATH.'Tpl/think_exception.tpl'); include $exceptionFile; exit;//終止程序運(yùn)行,很重要的。 } /** * 添加和獲取頁(yè)面Trace記錄 * @param string $value 變量 * @param string $label 標(biāo)簽 * @param string $level 日志級(jí)別(或者頁(yè)面Trace的選項(xiàng)卡) * @param boolean $record 是否記錄日志 * @return void */ static public function trace($value='[think]',$label='',$level='DEBUG',$record=false) { //采用靜態(tài)變量存儲(chǔ)Trace記錄 static $_trace = array(); if('[think]' === $value){ // 獲取trace信息 return $_trace; }else{ //錯(cuò)誤信息 $info = ($label?$label.':':'').print_r($value,true); $level = strtoupper($level);//將錯(cuò)誤級(jí)別轉(zhuǎn)換為大寫 //如果是AjAX請(qǐng)求或者不顯示TRACE調(diào)試工具,或者$record要求記錄日志,就不會(huì)記錄該條錯(cuò)誤信息 if((defined('IS_AJAX') && IS_AJAX) || !C('SHOW_PAGE_TRACE') || $record) { Log::record($info,$level,$record);//將錯(cuò)誤信息寫入到日志文件 }else{ //判斷錯(cuò)誤等級(jí)是否存在或者該類錯(cuò)誤信息是否達(dá)到錯(cuò)誤類型記錄上限,由TRACE_MAX_RECORD配置 if(!isset($_trace[$level]) || count($_trace[$level])>C('TRACE_MAX_RECORD')) { //這里有個(gè)我很詫異的地方,當(dāng)錯(cuò)誤類別記錄達(dá)到錯(cuò)誤類型記錄上限的是否為什么要重置該類型錯(cuò)誤記錄 //而不是不記錄當(dāng)前錯(cuò)誤信息,或者刪除最先一條的錯(cuò)誤信息,追加到最后 //我建議是不理會(huì)比較合理,因?yàn)檎{(diào)試錯(cuò)誤,也有先后嗎,先把之前遇到的錯(cuò)誤解決,就會(huì)看到新的錯(cuò)誤了(這是否有點(diǎn)坑⊙0⊙) $_trace[$level] = array(); } //按錯(cuò)類別記錄錯(cuò)誤信息 $_trace[$level][] = $info; } } }
七、總結(jié):對(duì)該類分析,主要掌控php錯(cuò)誤處理和異常處理方面的知識(shí),并了解基于命名空間自動(dòng)加載的規(guī)則定義基礎(chǔ),同樣接觸了ThinkPHP運(yùn)行時(shí)編譯緩存機(jī)制帶來(lái)的IO優(yōu)化思路以及類庫(kù)別名機(jī)制對(duì)與類自動(dòng)加載帶來(lái)的優(yōu)化。在分析該類時(shí)站在ThinkPHP應(yīng)用外對(duì)該類提出幾處質(zhì)疑,僅為個(gè)人對(duì)面向?qū)ο笤O(shè)計(jì)的理解和認(rèn)知,不作為詳細(xì)參考。
PHP怎么學(xué)習(xí)?PHP怎么入門?PHP在哪學(xué)?PHP怎么學(xué)才快?不用擔(dān)心,這里為大家提供了PHP速學(xué)教程(入門到精通),有需要的小伙伴保存下載就能學(xué)習(xí)啦!
微信掃碼
關(guān)注PHP中文網(wǎng)服務(wù)號(hào)
QQ掃碼
加入技術(shù)交流群
Copyright 2014-2025 http://m.miracleart.cn/ All Rights Reserved | php.cn | 湘ICP備2023035733號(hào)