tp3.2源碼解析——入口文件

  若是有人讀這篇文章並跟着作的話,但願你能使用支持函數跳轉的編輯器,還要善用var_dump和exit,對着源碼去調試着看。跟着入口文件讀,執行到哪裏你看到哪裏,對於那些不能一眼看出來的配置,則要記錄下來,可能一個比較簡單的功能會寫出很長的代碼,這個時候不免會看到後面忘了前面。php

  那麼進入正題,從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對象,系統便會自動引入該類文件的目的)session

 

 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引入;app

 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.phptp的單字母函數都是在這裏定義的,也是比較簡單,經過靜態變量來存儲配置)

 

  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中把鉤子函數叫作行爲)。打開這個文件來看一下(這裏我就不整篇貼代碼了):

  1. 一開始定義了一個$tags變量,用來存儲須要執行的方法。
  2. Add方法添插件行爲(就是鉤子函數),import方法批量導入,
  3. get獲取插件數組,
  4. exec方法執行插件,
  5. 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框架最複雜最麻煩的前置準備工做已經所有完成了。

相關文章
相關標籤/搜索