如果有人讀這篇文章并跟着做的話,希望你能使用支援函數跳轉的編輯器,還要善用var_dump和exit,對着源碼去調試着看。跟着入口檔案讀,執行到哪裡你看到哪裡,對于那些不能一眼看出來的配置,則要記錄下來,可能一個比較簡單的功能會寫出很長的代碼,這個時候難免會看到後面忘了前面。
那麼進入正題,從index.php檔案可以看到入口檔案隻定義了幾項常量作為配置,緊接着就引入了require './ThinkPHP/ThinkPHP.php';
1 // 檢測PHP環境
2 if(version_compare(PHP_VERSION,'5.3.0','<')) die('require PHP > 5.3.0 !');
3
4 // 開啟調試模式 建議開發階段開啟 部署階段注釋或者設為false
5 define('APP_DEBUG',True);
6
7 // 定義應用目錄
8 define('APP_PATH','./Application/');
9
10 // 引入ThinkPHP入口檔案
11 require './ThinkPHP/ThinkPHP.php';
在ThinkpPHP檔案依然比較簡單,tp定義了一些常量配置項(defined函數的寫法讓之前在index入口檔案裡定義的配置項不會被重置)記錄了運作時間和記憶體使用資訊,進行了php版本的判斷,以及cli指令行模式的判斷。并在檔案末尾再次引入了Think核心類,并進行了初始化。
require CORE_PATH.'Think'.EXT;路徑為ThinkPHP\Library\Think\Think.class.php
這個think類就比較長了,一開始就定義了$_map , $_instance兩個數組,其中$_map作為映射數組使用,think類在會把核心子產品的路勁存在這個數組裡。$_instance則存儲了系統運作時所執行個體化的對象
1 // 類映射
2 private static $_map = array();
3
4 // 執行個體化對象
5 private static $_instance = array();
剛剛的入口檔案執行了start方法來運作系統。這個start方法則一開始就通過spl_autoload_register方法注冊了自動加載函數。(php本身有一些魔術方法,在執行某些方法,或變量時,如果它找不到這個方法或變量,就會執行相應的魔術方法,tp便是用自己的自動引入方法替換了該方法,達到不需要引入,直接new對象,系統便會自動引入該類檔案的目的)
1 static public function start() {
2 // 注冊AUTOLOAD方法
3 spl_autoload_register('Think\Think::autoload');
4 // 設定錯誤和異常處理
5 register_shutdown_function('Think\Think::fatalError');
6 set_error_handler('Think\Think::appError');
7 set_exception_handler('Think\Think::appException');
8
9 // 初始化檔案存儲方式
10 Storage::connect(STORAGE_TYPE);
11 ················
我們往下翻看,autoload方法傳入了一個$class類名,然後便在類的$_map裡檢測是否存在該類的映射,如果有,則表明是系統核心子產品直接通過存儲的位址include引入;反之則會判斷$class是否為帶有命名空間的路徑字元串,然後通過strstr函數分割字元串獲得命名空間字首,判斷是否為tp系統定義的命名空間,然後便通過之前定義的常量來确定檔案路徑。同時判斷是否為win環境,進行大小寫的比對,然後include引入;
1 public static function autoload($class) {
2 // 檢查是否存在映射
3 if(isset(self::$_map[$class])) {
4 include self::$_map[$class];
5 }elseif(false !== strpos($class,'\\')){
6 $name = strstr($class, '\\', true);
7 if(in_array($name,array('Think','Org','Behavior','Com','Vendor')) || is_dir(LIB_PATH.$name)){
8 // Library目錄下面的命名空間自動定位
9 $path = LIB_PATH;
10 }else{
11 // 檢測自定義命名空間 否則就以子產品為命名空間
12 $namespace = C('AUTOLOAD_NAMESPACE');
13 $path = isset($namespace[$name])? dirname($namespace[$name]).'/' : APP_PATH;
14 }
15 $filename = $path . str_replace('\\', '/', $class) . EXT;
16 if(is_file($filename)) {
17 // Win環境下面嚴格區分大小寫
18 if (IS_WIN && false === strpos(str_replace('/', '\\', realpath($filename)), $class . EXT)){
19 return ;
20 }
21 include $filename;
22 }
23 }elseif (!C('APP_USE_NAMESPACE')) {
24 // 自動加載的類庫層
25 foreach(explode(',',C('APP_AUTOLOAD_LAYER')) as $layer){
26 if(substr($class,-strlen($layer))==$layer){
27 if(require_cache(MODULE_PATH.$layer.'/'.$class.EXT)) {
28 return ;
29 }
30 }
31 }
32 // 根據自動加載路徑設定進行嘗試搜尋
33 foreach (explode(',',C('APP_AUTOLOAD_PATH')) as $path){
34 if(import($path.'.'.$class))
35 // 如果加載類成功則傳回
36 return ;
37 }
38 }
39 }
autoload
通過APP_USE_NAMESPACE判斷如果配置項裡未使用命名空間,那麼通過APP_AUTOLOAD_LAYER配置項循環判斷為控制器或模型,調用公共方法判斷路徑并require。若引入失敗則再按照配置檔案中的APP_AUTOLOAD_PATH路徑尋找引入;
注冊了autoload方法後,tp系統又注冊了錯誤及異常處理方法,接管了報錯時的資訊提示功能(這些方法與autoload差不多,有興趣的朋友自己研究,在這裡就不一一贅述了)。
然後它通過Storage::connect(STORAGE_TYPE);類初始化了自己的檔案存儲類。ThinkPHP\Library\Think\Storage.class.php該類簡單的定義了一個操作句柄,一個初始化方法,将各種不同方式的操作對象賦予本類,(通過傳入不同參數,可以确定為SAE環境不同類型的存儲操作,預設為file檔案操作類)
1 namespace Think;
2 // 分布式檔案存儲類
3 class Storage {
4
5 /**
6 * 操作句柄
7 * @var string
8 * @access protected
9 */
10 static protected $handler ;
11
12 /**
13 * 連接配接分布式檔案系統
14 * @access public
15 * @param string $type 檔案類型
16 * @param array $options 配置數組
17 * @return void
18 */
19 static public function connect($type='File',$options=array()) {
20 $class = 'Think\\Storage\\Driver\\'.ucwords($type);
21 self::$handler = new $class($options);
22 }
23
24 static public function __callstatic($method,$args){
25 //調用緩存驅動的方法
26 if(method_exists(self::$handler, $method)){
27 return call_user_func_array(array(self::$handler,$method), $args);
28 }
29 }
30 }
file檔案位于ThinkPHP\Library\Think\Storage\Driver\File.class.php(tp通過不同的driver驅動層,來适應不同環境,不同類型的動态操作)這個檔案也是寫的相當簡單,類方法裡定義了寫入,删除,加載,讀取,等基本操作。
回到start方法,Tp通過APP_DEBUG配置來判斷是否有runtime緩存檔案,通過Storage::has方法來讀取,或unlink方法删除該緩存檔案。
1 $runtimefile = RUNTIME_PATH.APP_MODE.'~runtime.php';
2 if(!APP_DEBUG && Storage::has($runtimefile)){
3 Storage::load($runtimefile);
4 }else{
5 if(Storage::has($runtimefile))
6 Storage::unlink($runtimefile);
然後它讀取了應用模式:
1 $content = '';
2 // 讀取應用模式
3 $mode = include is_file(CONF_PATH.'core.php')?CONF_PATH.'core.php':MODE_PATH.APP_MODE.'.php';
4 // 加載核心檔案
5 foreach ($mode['core'] as $file){
6 if(is_file($file)) {
7 include $file;
8 if(!APP_DEBUG) $content .= compile($file);
9 }
10 }
1 is_file(CONF_PATH.'core.php')? //判斷是否有隐含應用模式檔案
2 CONF_PATH.'core.php':MODE_PATH.APP_MODE.'.php';
3 //yes,讀取Application/Common/Conf/core.php ; 否,讀取/ThinkPHP/Mode/common.php
大家打開ThinkPHP/Common/functions.php檔案,這個檔案裡通過數組定義了配置檔案和行文擴充,其中core裡面存儲了tp的核心檔案。(大家在檢視源碼的時候,可以多用var_dump和exit這兩個函數來檢視一些變量常量的值)這裡放一下這個配置檔案的路徑
1 //載入配置清單
2 Array
3 (
4 [config] => Array
5 (
6 [0] => D:\wamp\www\tp\ThinkPHP/Conf/convention.php
7 [1] => ./Application/Common/Conf/config.php
8 )
9 //增加為映射
10 [alias] => Array
11 (
12 [Think\Log] => D:\wamp\www\tp\ThinkPHP\Library/Think/Log.class.php
13 [Think\Log\Driver\File] => D:\wamp\www\tp\ThinkPHP\Library/Think/Log/Driver/File.class.php
14 [Think\Exception] => D:\wamp\www\tp\ThinkPHP\Library/Think/Exception.class.php
15 [Think\Model] => D:\wamp\www\tp\ThinkPHP\Library/Think/Model.class.php
16 [Think\Db] => D:\wamp\www\tp\ThinkPHP\Library/Think/Db.class.php
17 [Think\Template] => D:\wamp\www\tp\ThinkPHP\Library/Think/Template.class.php
18 [Think\Cache] => D:\wamp\www\tp\ThinkPHP\Library/Think/Cache.class.php
19 [Think\Cache\Driver\File] => D:\wamp\www\tp\ThinkPHP\Library/Think/Cache/Driver/File.class.php
20 [Think\Storage] => D:\wamp\www\tp\ThinkPHP\Library/Think/Storage.class.php
21 )
22
23 //加載核心類
24 [core] => Array
25 (
26 [0] => D:\wamp\www\tp\ThinkPHP/Common/functions.php
27 [1] => ./Application/Common/Common/function.php
28 [2] => D:\wamp\www\tp\ThinkPHP\Library/Think/Hook.class.php
29 [3] => D:\wamp\www\tp\ThinkPHP\Library/Think/App.class.php
30 [4] => D:\wamp\www\tp\ThinkPHP\Library/Think/Dispatcher.class.php
31 [5] => D:\wamp\www\tp\ThinkPHP\Library/Think/Route.class.php
32 [6] => D:\wamp\www\tp\ThinkPHP\Library/Think/Controller.class.php
33 [7] => D:\wamp\www\tp\ThinkPHP\Library/Think/View.class.php
34 [8] => D:\wamp\www\tp\ThinkPHP\Library/Behavior/BuildLiteBehavior.class.php
35 [9] => D:\wamp\www\tp\ThinkPHP\Library/Behavior/ParseTemplateBehavior.class.php
36 [10] => D:\wamp\www\tp\ThinkPHP\Library/Behavior/ContentReplaceBehavior.class.php
37 )
38 //加載到Think/Hook->tags裡
39 [tags] => Array
40 (
41 [app_init] => Array
42 (
43 [0] => Behavior\BuildLiteBehavior
44 )
45
46 [app_begin] => Array
47 (
48 [0] => Behavior\ReadHtmlCacheBehavior
49 )
50
51 [app_end] => Array
52 (
53 [0] => Behavior\ShowPageTraceBehavior
54 )
55
56 [view_parse] => Array
57 (
58 [0] => Behavior\ParseTemplateBehavior
59 )
60
61 [template_filter] => Array
62 (
63 [0] => Behavior\ContentReplaceBehavior
64 )
65
66 [view_filter] => Array
67 (
68 [0] => Behavior\WriteHtmlCacheBehavior
69 )
70
71 )
72
73 )
在include核心檔案後,通過C方法加載了應用模式的配置。(C方法位于ThinkPHP\Common\functions.php,tp的單字母函數都是在這裡定義的,也是比較簡單,通過靜态變量來存儲配置)
function C($name=null, $value=null,$default=null)這裡解釋下auto自動變量會随着函數被調用和退出而存在和消失,而static類局部變量不會,它不管其所在的函數是否被調用,都将一直存在;不過,盡管該變量還繼續存在,但不能使用它。倘若再次調用定義它的函數時,它又可繼續使用,而且儲存了前次被調用後留下的值。
加載了配置項,又通過map定義了模式的别名。
然後加載了應用行為定義,這裡的行為比較關鍵,解釋一下這個概念。
行為就是鈎子函數,有些了解鈎子函數的同學可能已經知道這是幹嘛的了,這裡解釋一下,鈎子函數就是系統在運作過程中,挂在某一段代碼中的方法,在代碼執行到鈎子方法那裡的時候就會執行這個鈎子上所綁的函數了,不了解的同學可以了解為方法間的調用,比如我有一個a方法,一個b方法,a方法裡顯示的寫了b();這樣來調用b方法,這樣雖然能起到調用的目的,但是一旦程式需要改動,要把調用b方法換成調用c方法則需要改動所有寫了b();的地方,十分繁瑣,還可能出錯,于是,如果我們在需要調用b方法的地方,不顯示的調用b方法,而是讀取一個配置變量,或配置檔案,調用配置裡定義的方法,那現在這樣寫就把a方法和b方法的耦合給解開了,以後要改變b方法為c方法d方法的時候我都可以隻改動配置檔案,是不是很友善呢?
1 Function a(){
2 ````
3 B();
4 `````
5 }
6
7 Function b(){
8 Echo ‘我是鈎子函數b’;
9 }

如果你已經了解了鈎子函數,那麼ThinkPHP\Library\Think\Hook.class.php這就是tp裡的鈎子類,用來挂載行為(tp中把鈎子函數叫做行為)。打開這個檔案來看一下(這裡我就不整篇貼代碼了):
- 一開始定義了一個$tags變量,用來存儲需要執行的方法。
- Add方法添插件行為(就是鈎子函數),import方法批量導入,
- get擷取插件數組,
- exec方法執行插件,
- listen方法則是線判斷了是否為debug模式,如果是debug模式,則通過G方法記錄了插件的執行,再調用exec方法,最後通過trace記錄了日志。
看完了hook類,再回到think類來,加載應用行為就很好了解了,通過Hook::import将tags.php裡配置的鈎子數組導入了hook類裡(tp裡定義鈎子函數通過config裡建立tags.php,不清楚的朋友自行翻閱手冊)。Tp的行為在上面放出的tags配置數組裡可以看到。
加載完行為,又加載了語言包、引入debug檔案、讀取應用狀态的配置檔案、建立基本目錄結構、記錄加載檔案時間。
1 // 讀取目前應用模式對應的配置檔案
2 if('common' != APP_MODE && is_file(CONF_PATH.'config_'.APP_MODE.CONF_EXT))
3 C(load_config(CONF_PATH.'config_'.APP_MODE.CONF_EXT));
4
5 // 加載模式别名定義
6 if(isset($mode['alias'])){
7 self::addMap(is_array($mode['alias'])?$mode['alias']:include $mode['alias']);
8 }
9
10 // 加載應用别名定義檔案
11 if(is_file(CONF_PATH.'alias.php'))
12 self::addMap(include CONF_PATH.'alias.php');
13
14 // 加載模式行為定義
15 if(isset($mode['tags'])) {
16 Hook::import(is_array($mode['tags'])?$mode['tags']:include $mode['tags']);
17 }
18
19 // 加載應用行為定義
20 if(is_file(CONF_PATH.'tags.php'))
21 // 允許應用增加開發模式配置定義
22 Hook::import(include CONF_PATH.'tags.php');
23
24 // 加載架構底層語言包
25 L(include THINK_PATH.'Lang/'.strtolower(C('DEFAULT_LANG')).'.php');
26
27 if(!APP_DEBUG){
28 $content .= "\nnamespace { Think\\Think::addMap(".var_export(self::$_map,true).");";
29 $content .= "\nL(".var_export(L(),true).");\nC(".var_export(C(),true).');Think\Hook::import('.var_export(Hook::get(),true).');}';
30 Storage::put($runtimefile,strip_whitespace('<?php '.$content));
31 }else{
32 // 調試模式加載系統預設的配置檔案
33 C(include THINK_PATH.'Conf/debug.php');
34 // 讀取應用調試配置檔案
35 if(is_file(CONF_PATH.'debug'.CONF_EXT))
36 C(include CONF_PATH.'debug'.CONF_EXT);
37 }
38 }
39
40 // 讀取目前應用狀态對應的配置檔案
41 if(APP_STATUS && is_file(CONF_PATH.APP_STATUS.CONF_EXT))
42 C(include CONF_PATH.APP_STATUS.CONF_EXT);
43
44 // 設定系統時區
45 date_default_timezone_set(C('DEFAULT_TIMEZONE'));
46
47 // 檢查應用目錄結構 如果不存在則自動建立
48 if(C('CHECK_APP_DIR')) {
49 $module = defined('BIND_MODULE') ? BIND_MODULE : C('DEFAULT_MODULE');
50 if(!is_dir(APP_PATH.$module) || !is_dir(LOG_PATH)){
51 // 檢測應用目錄結構
52 Build::checkDir($module);
53 }
54 }
55
56 // 記錄加載檔案時間
57 G('loadTime');
58 // 運作應用
59 App::run();
60 }
View Code
最後app:run()又調用了另一個類,整個配置完成,開始運作了。ThinkPHP\Library\Think\App.class.php
Run方法一開始就通過剛剛說的Hook::listen方法調用了一個行為,然後執行了init方法來初始化系統。
1 static public function run() {
2 // 應用初始化标簽
3 Hook::listen('app_init');
4 App::init();
5 // 應用開始标簽
6 Hook::listen('app_begin');
7 // Session初始化
8 if(!IS_CLI){
9 session(C('SESSION_OPTIONS'));
10 }
11 // 記錄應用初始化時間
12 G('initTime');
13 App::exec();
14 // 應用結束标簽
15 Hook::listen('app_end');
16 return ;
17 }
Init方法也是一開始就加載了公共配置,配置了日志路徑,以及定義了不少常量,都是與伺服器接到的請求有關。
1 static public function init() {
2 // 加載動态應用公共檔案和配置
3 load_ext_file(COMMON_PATH);
4
5 // 日志目錄轉換為絕對路徑 預設情況下存儲到公共子產品下面
6 C('LOG_PATH', realpath(LOG_PATH).'/Common/');
7
8 // 定義目前請求的系統常量
9 define('NOW_TIME', $_SERVER['REQUEST_TIME']);
10 define('REQUEST_METHOD',$_SERVER['REQUEST_METHOD']);
11 define('IS_GET', REQUEST_METHOD =='GET' ? true : false);
12 define('IS_POST', REQUEST_METHOD =='POST' ? true : false);
13 define('IS_PUT', REQUEST_METHOD =='PUT' ? true : false);
14 define('IS_DELETE', REQUEST_METHOD =='DELETE' ? true : false);
15
16 // URL排程
17 Dispatcher::dispatch();
18 ``````````````
Dispatcher::dispatch(); 這個方法就是用來解析路由并進行控制器調用的。檔案位于ThinkPHP\Library\Think\ Dispatcher.class.php
這個方法一上來就擷取了不少配置,接着對get到的參數進行判斷是否為相容模式或cli指令行模式。又對子域名部署進行了判斷,分析了pathinfo資訊。
1 static public function dispatch() {
2 $varPath = C('VAR_PATHINFO');
3 $varAddon = C('VAR_ADDON');
4 $varModule = C('VAR_MODULE');
5 $varController = C('VAR_CONTROLLER');
6 $varAction = C('VAR_ACTION');
7 $urlCase = C('URL_CASE_INSENSITIVE');
8 if(isset($_GET[$varPath])) { // 判斷URL裡面是否有相容模式參數
9 $_SERVER['PATH_INFO'] = $_GET[$varPath];
10 unset($_GET[$varPath]);
11 }elseif(IS_CLI){ // CLI模式下 index.php module/controller/action/params/...
12 $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
13 }
14 // 開啟子域名部署
15 if(C('APP_SUB_DOMAIN_DEPLOY')) {
16 ````````
17 }
18 // 分析PATHINFO資訊
19 if(!isset($_SERVER['PATH_INFO'])) {
20 ````````
21 }
然後開始進入正題了,先通過URL_PATHINFO_DEPR擷取url的分隔符,然後是從$_SERVER裡擷取PATH_INFO用作路由截取,存入__INFO__常量,這裡有個判斷未綁定模,未開啟路由配置,以及路由檢測的判斷,Route::check()
這個動态路由處理部分我也不是很清楚,不過最後它傳回了false,滿足了外面的if條件。接下來就通過配置變量對url進行了拆解,建議大家把這些變量配置都列印出來,看一眼便知道了。
1 $depr = C('URL_PATHINFO_DEPR');
2 define('MODULE_PATHINFO_DEPR', $depr);
3
4 if(empty($_SERVER['PATH_INFO'])) {
5 $_SERVER['PATH_INFO'] = '';
6 define('__INFO__','');
7 define('__EXT__','');
8 }else{
9 define('__INFO__',trim($_SERVER['PATH_INFO'],'/'));
10 // URL字尾
11 define('__EXT__', strtolower(pathinfo($_SERVER['PATH_INFO'],PATHINFO_EXTENSION)));
12 $_SERVER['PATH_INFO'] = __INFO__;
13 if(!defined('BIND_MODULE') && (!C('URL_ROUTER_ON') || !Route::check())){
14 if (__INFO__ && C('MULTI_MODULE')){ // 擷取子產品名
15 $paths = explode($depr,__INFO__,2);
16 $allowList = C('MODULE_ALLOW_LIST'); // 允許的子產品清單
17 $module = preg_replace('/\.' . __EXT__ . '$/i', '',$paths[0]);
18 if( empty($allowList) || (is_array($allowList) && in_array_case($module, $allowList))){
19 $_GET[$varModule] = $module;
20 $_SERVER['PATH_INFO'] = isset($paths[1])?$paths[1]:'';
21 }
22 }
23 }
24 }
25 `````````````
真正擷取到路由的是define('__SELF__',strip_tags($_SERVER[C('URL_REQUEST_URI')]));這一句,通過系統$_SERVER全局變量中的REQUEST_URI項擷取到完整的url路由。
再剩下的都是根據配置常量來對url進行處理,加載相應子產品的配置了,費點時間依次列印記錄就能很清晰的看出來隻是對字元串進行拆分、過濾、拼接。
至于路由傳參則是由這一段正則進行比對的。preg_replace_callback('/(\w+)\/([^\/]+)/', function($match) use(&$var){$var[$match[1]]=strip_tags($match[2]);}, implode('/',$paths));
擷取到mca及自定義參數後,url解析完成,調回init方法,開啟url鈎子行為
回到APP類,init方法初始化完成,開啟app_begin鈎子行為,記錄session配置,調用exec執行方法。
Exec方法也是一開始就又對控制器名進行了過濾,然後正常情況會直接跳到$module = controller(CONTROLLER_NAME,CONTROLLER_PATH);這裡建立控制器對象。我們跳到controller函數這裡,也是簡單的字元拼接後就直接new出了對象,得到對象以後又是跳轉到了invokeAction方法這裡。
1 static public function exec() {
2
3 if(!preg_match('/^[A-Za-z](\/|\w)*$/',CONTROLLER_NAME)){ // 安全檢測
4 $module = false;
5 }elseif(C('ACTION_BIND_CLASS')){
6 // 操作綁定到類:子產品\Controller\控制器\操作
7 $layer = C('DEFAULT_C_LAYER');
8 if(is_dir(MODULE_PATH.$layer.'/'.CONTROLLER_NAME)){
9 $namespace = MODULE_NAME.'\\'.$layer.'\\'.CONTROLLER_NAME.'\\';
10 }else{
11 // 空控制器
12 $namespace = MODULE_NAME.'\\'.$layer.'\\_empty\\';
13 }
14 $actionName = strtolower(ACTION_NAME);
15 if(class_exists($namespace.$actionName)){
16 $class = $namespace.$actionName;
17 }elseif(class_exists($namespace.'_empty')){
18 // 空操作
19 $class = $namespace.'_empty';
20 }else{
21 E(L('_ERROR_ACTION_').':'.ACTION_NAME);
22 }
23 $module = new $class;
24 // 操作綁定到類後 固定執行run入口
25 $action = 'run';
26 }else{
27 //建立控制器執行個體
28 $module = controller(CONTROLLER_NAME,CONTROLLER_PATH);
29 }
30
31 if(!$module) {
32 if('4e5e5d7364f443e28fbf0d3ae744a59a' == CONTROLLER_NAME) {
33 header("Content-type:image/png");
34 exit(base64_decode(App::logo()));
35 }
36
37 // 是否定義Empty控制器
38 $module = A('Empty');
39 if(!$module){
40 E(L('_CONTROLLER_NOT_EXIST_').':'.CONTROLLER_NAME);
41 }
42 }
43
44 // 擷取目前操作名 支援動态路由
45 if(!isset($action)){
46 $action = ACTION_NAME.C('ACTION_SUFFIX');
47 }
48 try{
49 self::invokeAction($module,$action);
50 } catch (\ReflectionException $e) {
51 // 方法調用發生異常後 引導到__call方法處理
52 $method = new \ReflectionMethod($module,'__call');
53 $method->invokeArgs($module,array($action,''));
54 }
55 return ;
56 }
invokeAction方法在一開始進行安全過濾之後,就利用了ReflectionMethod這個反射類,對我們剛剛的控制器對象進行了檢測,有無_before前置操作啊,通過反射類擷取方法參數清單,并将擷取到的參數指派并執行,判斷後置方法執行之類的。到這裡,我們的控制器方法就已經開始執行了。
1 public static function invokeAction($module,$action){
2 if(!preg_match('/^[A-Za-z](\w)*$/',$action)){
3 // 非法操作
4 throw new \ReflectionException();
5 }
6 //執行目前操作
7 $method = new \ReflectionMethod($module, $action);
8 if($method->isPublic() && !$method->isStatic()) {
9 $class = new \ReflectionClass($module);
10 // 前置操作
11 if($class->hasMethod('_before_'.$action)) {
12 $before = $class->getMethod('_before_'.$action);
13 if($before->isPublic()) {
14 $before->invoke($module);
15 }
16 }
17 // URL參數綁定檢測
18 if($method->getNumberOfParameters()>0 && C('URL_PARAMS_BIND')){
19 switch($_SERVER['REQUEST_METHOD']) {
20 case 'POST':
21 $vars = array_merge($_GET,$_POST);
22 break;
23 case 'PUT':
24 parse_str(file_get_contents('php://input'), $vars);
25 break;
26 default:
27 $vars = $_GET;
28 }
29 $params = $method->getParameters();
30 $paramsBindType = C('URL_PARAMS_BIND_TYPE');
31 foreach ($params as $param){
32 $name = $param->getName();
33 if( 1 == $paramsBindType && !empty($vars) ){
34 $args[] = array_shift($vars);
35 }elseif( 0 == $paramsBindType && isset($vars[$name])){
36 $args[] = $vars[$name];
37 }elseif($param->isDefaultValueAvailable()){
38 $args[] = $param->getDefaultValue();
39 }else{
40 E(L('_PARAM_ERROR_').':'.$name);
41 }
42 }
43 // 開啟綁定參數過濾機制
44 if(C('URL_PARAMS_SAFE')){
45 $filters = C('URL_PARAMS_FILTER')?:C('DEFAULT_FILTER');
46 if($filters) {
47 $filters = explode(',',$filters);
48 foreach($filters as $filter){
49 $args = array_map_recursive($filter,$args); // 參數過濾
50 }
51 }
52 }
53 array_walk_recursive($args,'think_filter');
54 $method->invokeArgs($module,$args);
55 }else{
56 $method->invoke($module);
57 }
58 // 後置操作
59 if($class->hasMethod('_after_'.$action)) {
60 $after = $class->getMethod('_after_'.$action);
61 if($after->isPublic()) {
62 $after->invoke($module);
63 }
64 }
65 }else{
66 // 操作方法不是Public 抛出異常
67 throw new \ReflectionException();
68 }
69 }
好的,到這裡。tp架構最複雜最麻煩的前置準備工作已經全部完成了。
轉載于:https://www.cnblogs.com/wyycc/p/8922430.html